Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59185f3b87 | |||
| c708a7cc12 | |||
| ea35a37d4c | |||
| e40d25a97b | |||
| cf903aa2e5 | |||
| 0a2e0d6a8f | |||
| ea68d42886 | |||
| 65ed611c24 | |||
| f305901a18 | |||
| 645cb88074 | |||
| 6b96e474f7 | |||
| 4854e929ed | |||
| 5f88517e1d | |||
| c7d8f3af97 | |||
| 1e84f87bf5 | |||
| 70ae45b6c9 | |||
| 8d3aea969e | |||
| 96444509d0 | |||
| 078ca0b0d1 | |||
| aab91e424e | |||
| 273dc41559 | |||
| 61bc74148f | |||
| 5b14eecf82 | |||
| 30110aff65 | |||
| 1954c49ac2 | |||
| 70328ba114 | |||
| c43b2dc117 | |||
| 4a8a9096dd | |||
| 88d813b9c1 | |||
| e181a1cfb1 | |||
| 29091aab8e | |||
| fdbe3a1f6a | |||
| 5f4926dd49 | |||
| 1f132a4eaa | |||
| 097d2d51cc | |||
| a2078d997b | |||
| 558d4b899d | |||
| 4fcb98e839 | |||
| 702bb202a3 | |||
| 71ff2c04a0 | |||
| cb30384eaf | |||
| e911c2fbc0 | |||
| bb005a39f5 | |||
| ea72c37083 | |||
| a24dd8b9bc | |||
| b006c01397 | |||
| f7f2a51458 | |||
| 408396c900 | |||
| a97c913bb1 | |||
| dba5a9caef | |||
| 3e39573feb | |||
| ac312e9109 | |||
| 8a2a7dc0ce | |||
| 5b15ed6f15 | |||
| 9684b7ffbd | |||
| 0628565684 | |||
| 57703af642 | |||
| 9dd4c5d871 | |||
| 4813cf6c18 | |||
| 29f48efe49 | |||
| 280ac34115 | |||
| 83b76c6b37 | |||
| 4120234155 | |||
| 42b444ddf8 | |||
| acd832d8e5 | |||
| 75a726ebbe | |||
| b9fbab6024 | |||
| 06ed8716a1 | |||
| beddd3dcee | |||
| bdc285dbbb | |||
| 4d02dfb899 | |||
| 80d6cb5c12 | |||
| 7422ab69ba | |||
| 60c8185bea | |||
| 9d3c7c0be8 | |||
| d6048fae52 | |||
| ddb7b0e872 | |||
| 4995dde705 | |||
| 63bfe4246f | |||
| 4654b360e0 | |||
| 7c5c19986a | |||
| 46edca0bd4 | |||
| b03a1f922d | |||
| 9616c90433 | |||
| ea49f0a265 | |||
| 2470c8b9b5 | |||
| 7d001965ec | |||
| 11b1089d83 | |||
| 52699d7598 | |||
| 87443dd624 | |||
| a02a1b0385 | |||
| e0f2fa6705 | |||
| 7949b07213 | |||
| bd20fd0b1f | |||
| 57d3d6fd0f | |||
| 06eff652dd | |||
| 246decac4a | |||
| a14ae298ae | |||
| 43188051d3 | |||
| 8aa15fa467 | |||
| f77b037ef7 | |||
| 75dbc5d790 | |||
| e6bcd706ff | |||
| 9a077a0928 | |||
| 11fd42e187 | |||
| 6e499e5996 | |||
| f98cc73a1f | |||
| db9bf4d3fa | |||
| b30628529d | |||
| 3be615f74f | |||
| a8ccd2ec17 | |||
| 2d71caf50a | |||
| 4d08d62fc2 | |||
| 40e5595d65 | |||
| 6780d58a98 | |||
| ccbf06f179 | |||
| e6ecf71cc3 | |||
| c66312ee96 | |||
| 808802aeb4 | |||
| fb38f24e5c | |||
| 65f148a5ad | |||
| 2bcdc5d11e | |||
| 5ecb03ffe9 | |||
| 8505989dad | |||
| ed5e865db7 | |||
| ea3194e0c3 | |||
| 190bff4ffe | |||
| c509555d15 | |||
| f0d66fdd88 | |||
| 14847d01b7 | |||
| f4711902bd | |||
| 32ad93c57e | |||
| c4a68dbbe6 | |||
| 1140503eba | |||
| d92d6877a4 | |||
| b9fed9f455 | |||
| 5e45f7d3a5 | |||
| 1133acd8bd | |||
| 08e6f3c4b7 | |||
| 0ed546b739 | |||
| b4f2233d2b | |||
| d8369eb4c9 | |||
| db3d379219 | |||
| a746738d48 | |||
| c67f0fb7f8 | |||
| 7c12a3422c | |||
| f047209baf | |||
| cc5a69d4f1 | |||
| 776d27a899 | |||
| 829dbb1695 | |||
| 52156e0c38 | |||
| 901275fd63 | |||
| 6c4f0bc2f4 | |||
| 052a9a71c2 | |||
| da094f0208 | |||
| 254302ec38 | |||
| a0a73421d0 | |||
| b7be48a1b3 | |||
| 870c4f73a8 | |||
| c2c883e840 | |||
| 4d7e21e0f2 | |||
| c0ffbb0cb2 | |||
| 5e600de932 | |||
| 70fcb8c046 | |||
| eb07ec8580 | |||
| f6a79ce7c3 | |||
| 80c81fa3d7 | |||
| 27e6539e98 | |||
| a2d10d9956 | |||
| 4ece8b7e8f | |||
| bb557985c0 | |||
| 3ebb24b2c8 | |||
| 142a2bb26b | |||
| 677d8c7fce | |||
| 3563ad67b2 | |||
| c11a4c23fa | |||
| 97b01db23e | |||
| a020f2ad1c | |||
| ce676c2bb5 | |||
| 4b0f3cc093 | |||
| 568ba1f4d9 | |||
| a096f9d54e | |||
| 5456b16e3b | |||
| 3b5eab5342 | |||
| a7d8613c9d | |||
| c720481a45 | |||
| e18946efe1 | |||
| 47ca0d0358 | |||
| 00f17c376a | |||
| 5283329914 | |||
| 29dd121282 | |||
| cd1d9d1a0d | |||
| b694e675e6 | |||
| 39cb6f6ec0 | |||
| 7a52124d53 | |||
| 2ec5616453 | |||
| ebaf99fadb | |||
| c534c4b91d | |||
| 85515fe16e | |||
| ec60966a85 | |||
| d1df407bac | |||
| 9ff4051b0f | |||
| fea9ec0f9f | |||
| d48d094672 | |||
| e1b511e788 | |||
| 1500d27ce5 | |||
| a0eba073ec | |||
| 2c3e716ba1 | |||
| d9b6823106 | |||
| bd1d88e9e8 | |||
| 89bcb5649b | |||
| 53444cf55a | |||
| f2e460a96b | |||
| b5b7b7255b | |||
| 894a46688a | |||
| 5d3550a569 | |||
| b43dab4f83 | |||
| 1bdb58571d | |||
| a74a99d81d | |||
| c09d3af92f | |||
| 76773c58d7 | |||
| fd90175e87 | |||
| 0eb859467e |
+1
-1
@@ -14,7 +14,7 @@ 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
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-City
|
||||
# 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
|
||||
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-api
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
|
||||
+4
-1
@@ -37,4 +37,7 @@ validator-config
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
storybook-static
|
||||
envs/qwerty.env
|
||||
Cargo.lock
|
||||
nym-connect/Cargo.lock
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"mainnet":[{
|
||||
"nymd_url":"https://rpc.nyx.nodes.guru/",
|
||||
"api_url":"https://api.nyx.nodes.guru/"
|
||||
}]
|
||||
}
|
||||
"mainnet": [
|
||||
{
|
||||
"nyxd_url": "https://rpc.nyx.nodes.guru/",
|
||||
"api_url": "https://api.nyx.nodes.guru/"
|
||||
}
|
||||
]
|
||||
}
|
||||
+117
-34
@@ -2,13 +2,102 @@
|
||||
|
||||
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.1.6] (2023-01-17)
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
- streamline override_config functions -> there's a lot of duplicate if statements everywhere ([#2774])
|
||||
- clean-up nym-api startup arguments/flags to use clap 3 and its macro-derived arguments ([#2772])
|
||||
- renamed all references to validator_api to nym_api
|
||||
- renamed all references to nymd to nyxd ([#2696])
|
||||
- all-binaries: standarised argument names (note: old names should still be accepted) ([#2762]
|
||||
|
||||
### Fixed
|
||||
|
||||
- nym-api: should now correctly use `rewarding.enabled` config flag ([#2753])
|
||||
|
||||
[#2696]: https://github.com/nymtech/nym/pull/2696
|
||||
[#2753]: https://github.com/nymtech/nym/pull/2753
|
||||
[#2762]: https://github.com/nymtech/nym/pull/2762
|
||||
[#2772]: https://github.com/nymtech/nym/pull/2772
|
||||
[#2774]: https://github.com/nymtech/nym/pull/2774
|
||||
|
||||
## [v1.1.5] (2023-01-10)
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
## [v1.1.5] (2023-01-10)
|
||||
|
||||
### Added
|
||||
|
||||
- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715
|
||||
|
||||
### Changed
|
||||
|
||||
- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686
|
||||
- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695
|
||||
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670
|
||||
- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668
|
||||
- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863
|
||||
- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693
|
||||
- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700
|
||||
- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730
|
||||
- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723
|
||||
|
||||
## [v1.1.4] (2022-12-20)
|
||||
|
||||
This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies.
|
||||
At the moment this is turned off by default, but available for use by application developers.
|
||||
We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients.
|
||||
|
||||
The release also include some additional work for distributed key generation in the Coconut signing authority nodes.
|
||||
|
||||
### Changed
|
||||
|
||||
- Feature/dkg contract threshold by @neacsu in https://github.com/nymtech/nym/pull/1885
|
||||
- Multi-surbs by @jstuczyn in https://github.com/nymtech/nym/pull/2667
|
||||
- Fix multi-surb backwards compatibility in pre 1.1.4 client config files by @jstuczyn in https://github.com/nymtech/nym/pull/2703
|
||||
- fix: ignore corrupted surb storage and instead create fresh one by @jstuczyn in https://github.com/nymtech/nym/pull/2711
|
||||
- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880
|
||||
|
||||
## [v1.1.3] (2022-12-13)
|
||||
|
||||
### Changed
|
||||
|
||||
- validator-api: can recover from shutdown during DKG process ([#1872])
|
||||
- clients: deduplicate gateway initialization, part of work towards a rust-sdk
|
||||
- clients: keep all transmission lanes going at all times by making priority probabilistic
|
||||
- clients: ability to use multi-reply SURBs to send arbitrarily long messages fully anonymously whilst requesting additional reply blocks whenever they're about to run out ([#1796], [#1801], [#1804], [#1835], [#1858], [#1883]))
|
||||
|
||||
### Fixed
|
||||
|
||||
- network-requester: fix bug where websocket connection disconnect resulted in success error code
|
||||
- clients: fix a few panics handling the gateway-client
|
||||
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
|
||||
- socks5: fixed bug where connections sometimes where closed too early
|
||||
- clients: improve message logging when received message fails to get reconstructed ([#1803])
|
||||
|
||||
[#1796]: https://github.com/nymtech/nym/pull/1796
|
||||
[#1801]: https://github.com/nymtech/nym/pull/1801
|
||||
[#1803]: https://github.com/nymtech/nym/pull/1803
|
||||
[#1804]: https://github.com/nymtech/nym/pull/1804
|
||||
[#1835]: https://github.com/nymtech/nym/pull/1835
|
||||
[#1858]: https://github.com/nymtech/nym/pull/1858
|
||||
[#1872]: https://github.com/nymtech/nym/pull/1872
|
||||
[#1883]: https://github.com/nymtech/nym/pull/1883
|
||||
[#1884]: https://github.com/nymtech/nym/pull/1884
|
||||
|
||||
## [v1.1.2]
|
||||
|
||||
### Changed
|
||||
|
||||
- gateway: Renamed flag from `enabled/disabled_credentials_mode` to `only-coconut-credentials`
|
||||
- "Family" feature for node families + layers
|
||||
- Initial coconut functionality including credentials and distributed key generation
|
||||
- Initial coconut functionality including credentials and distributed key generation
|
||||
|
||||
## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29)
|
||||
|
||||
@@ -44,7 +133,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1786]: https://github.com/nymtech/nym/pull/1786
|
||||
[#1805]: https://github.com/nymtech/nym/pull/1805
|
||||
|
||||
|
||||
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
|
||||
|
||||
### Added
|
||||
@@ -56,7 +144,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- native-client/socks5-client/wasm-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
|
||||
- validator-api: add `interval_operating_cost` and `profit_margin_percent` to compute reward estimation endpoint
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558])
|
||||
- wasm-client: uses updated wasm-compatible `client-core` so that it's now capable of packet retransmission, cover traffic and poisson delay (among other things!) ([#1673])
|
||||
|
||||
### Fixed
|
||||
@@ -71,7 +159,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
|
||||
- moved `Percent` struct to `contracts-common`, change affects explorer-api
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
|
||||
@@ -99,7 +187,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#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)
|
||||
|
||||
### Added
|
||||
@@ -152,9 +239,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
|
||||
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
|
||||
- network explorer: tweak how active set probability is shown ([#1503])
|
||||
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
|
||||
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
|
||||
|
||||
- validator-api: rewarder set update fails without panicking on possible nyxd queries ([#1520])
|
||||
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
|
||||
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
[#1256]: https://github.com/nymtech/nym/pull/1256
|
||||
@@ -248,9 +334,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript_client/upload_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
|
||||
- get_current_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
|
||||
- Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch))
|
||||
- Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch))
|
||||
- Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch))
|
||||
@@ -259,7 +345,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol))
|
||||
- Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch))
|
||||
- wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol))
|
||||
- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
|
||||
- Fix delegated_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
|
||||
- Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol))
|
||||
- Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair))
|
||||
@@ -299,14 +385,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
|
||||
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
|
||||
|
||||
|
||||
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
|
||||
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
@@ -336,7 +421,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Bugfix/remove mixnode bonding overwrite [\#917](https://github.com/nymtech/nym/pull/917) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Fixes crash condition in validator API when calculating last day uptime [\#909](https://github.com/nymtech/nym/pull/909) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/monitor initial values wait [\#907](https://github.com/nymtech/nym/pull/907) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Explorer API: port test now split out address resolution and add units tests [\#755](https://github.com/nymtech/nym/pull/755) ([mmsinclair](https://github.com/mmsinclair))
|
||||
|
||||
**Closed issues:**
|
||||
@@ -351,7 +436,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- help!!! [\#712](https://github.com/nymtech/nym/issues/712)
|
||||
- UX feature request: show all delegated nodes in wallet [\#711](https://github.com/nymtech/nym/issues/711)
|
||||
- UX feature request: add current balance on wallet pages [\#710](https://github.com/nymtech/nym/issues/710)
|
||||
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
|
||||
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
|
||||
- As a wallet user, I would like to be able to log out of the wallet [\#706](https://github.com/nymtech/nym/issues/706)
|
||||
- As a wallet user, I would like to have a "receive" page where I can see my own wallet address [\#705](https://github.com/nymtech/nym/issues/705)
|
||||
- Update native client/socks client/mixnode/gateway `upgrade` command [\#689](https://github.com/nymtech/nym/issues/689)
|
||||
@@ -361,7 +446,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- nym-socks5-client crash after opening Keybase team "Browse all channels" [\#494](https://github.com/nymtech/nym/issues/494)
|
||||
- Mixed Content problem [\#400](https://github.com/nymtech/nym/issues/400)
|
||||
- Gateway disk quota [\#137](https://github.com/nymtech/nym/issues/137)
|
||||
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
|
||||
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
|
||||
- Create constants for cli argument names [\#115](https://github.com/nymtech/nym/issues/115)
|
||||
- Using Blake3 as a hash function [\#103](https://github.com/nymtech/nym/issues/103)
|
||||
- Validator should decide which layer a node is in [\#86](https://github.com/nymtech/nym/issues/86)
|
||||
@@ -417,10 +502,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/pre cosmrs updates [\#935](https://github.com/nymtech/nym/pull/935) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/client on behalf [\#934](https://github.com/nymtech/nym/pull/934) ([neacsu](https://github.com/neacsu))
|
||||
- Webpack wallet prod configuration [\#933](https://github.com/nymtech/nym/pull/933) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Adding tx\_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
|
||||
- Adding tx_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
|
||||
- Release/1.0.0 pre1 [\#931](https://github.com/nymtech/nym/pull/931) ([durch](https://github.com/durch))
|
||||
- Feature/identity verification [\#930](https://github.com/nymtech/nym/pull/930) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
|
||||
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
|
||||
- Feature/mixnet contract further adjustments [\#928](https://github.com/nymtech/nym/pull/928) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- typo copy change for nodemap [\#926](https://github.com/nymtech/nym/pull/926) ([Aid19801](https://github.com/Aid19801))
|
||||
- Feature/UI enhancements for Desktop Wallet [\#925](https://github.com/nymtech/nym/pull/925) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -433,7 +518,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/faucet page react [\#911](https://github.com/nymtech/nym/pull/911) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/mixnet contract refactor [\#910](https://github.com/nymtech/nym/pull/910) ([futurechimp](https://github.com/futurechimp))
|
||||
- Update README.md [\#905](https://github.com/nymtech/nym/pull/905) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
|
||||
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
|
||||
- Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -451,7 +536,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Reverted gateway registration handshake to its 0.11.0 version [\#882](https://github.com/nymtech/nym/pull/882) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Network Explorer [\#881](https://github.com/nymtech/nym/pull/881) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/rewarding interval updates [\#880](https://github.com/nymtech/nym/pull/880) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Put client\_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
|
||||
- Put client_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
|
||||
- remove gateway selection on delegation and undelegation pages [\#873](https://github.com/nymtech/nym/pull/873) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Set MSRV on all binaries to 1.56 [\#872](https://github.com/nymtech/nym/pull/872) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- add native window items \(copy/paste\) via tauri [\#871](https://github.com/nymtech/nym/pull/871) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -467,7 +552,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Overflow checks in release [\#846](https://github.com/nymtech/nym/pull/846) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- fix delegate success overflow [\#842](https://github.com/nymtech/nym/pull/842) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature NYM wallet webdriverio test [\#841](https://github.com/nymtech/nym/pull/841) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Update nym\_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Update nym_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/vouchers [\#837](https://github.com/nymtech/nym/pull/837) ([aniampio](https://github.com/aniampio))
|
||||
- Apply readable ids to elements on Nym Wallet [\#836](https://github.com/nymtech/nym/pull/836) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/removal of monitor good nodes [\#833](https://github.com/nymtech/nym/pull/833) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -491,8 +576,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Created getters for AccountData [\#787](https://github.com/nymtech/nym/pull/787) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/migrate hidden delegations [\#786](https://github.com/nymtech/nym/pull/786) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/persistent gateway storage [\#784](https://github.com/nymtech/nym/pull/784) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Replaced unwrap\_or\_else with unwrap\_or\_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add block\_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
|
||||
- Replaced unwrap_or_else with unwrap_or_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add block_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
|
||||
- Make fee helpers public [\#777](https://github.com/nymtech/nym/pull/777) ([durch](https://github.com/durch))
|
||||
- re-enable bonding [\#776](https://github.com/nymtech/nym/pull/776) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Explorer-api: add API resource to show the delegations for each mix node [\#774](https://github.com/nymtech/nym/pull/774) ([mmsinclair](https://github.com/mmsinclair))
|
||||
@@ -501,14 +586,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Adding deps for building the Tauri wallet under Ubuntu [\#770](https://github.com/nymtech/nym/pull/770) ([futurechimp](https://github.com/futurechimp))
|
||||
- remove alert [\#767](https://github.com/nymtech/nym/pull/767) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/consumable bandwidth [\#766](https://github.com/nymtech/nym/pull/766) ([neacsu](https://github.com/neacsu))
|
||||
- Update coconut-rs and use hash\_to\_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
|
||||
- Update coconut-rs and use hash_to_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/active sets [\#764](https://github.com/nymtech/nym/pull/764) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- add app alert banner [\#762](https://github.com/nymtech/nym/pull/762) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Updated cosmos-sdk [\#761](https://github.com/nymtech/nym/pull/761) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/bond blockstamp [\#760](https://github.com/nymtech/nym/pull/760) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/revert migration code [\#759](https://github.com/nymtech/nym/pull/759) ([neacsu](https://github.com/neacsu))
|
||||
- Bump next from 11.1.0 to 11.1.1 in /wallet-web [\#758](https://github.com/nymtech/nym/pull/758) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Add block\_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
|
||||
- Add block_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/add blockstamp [\#756](https://github.com/nymtech/nym/pull/756) ([neacsu](https://github.com/neacsu))
|
||||
- NetworkMonitorBuilder - starting the monitor after rocket has launched [\#754](https://github.com/nymtech/nym/pull/754) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Enabled validators api argument [\#753](https://github.com/nymtech/nym/pull/753) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -520,21 +605,21 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/more reliable uptime calculation [\#747](https://github.com/nymtech/nym/pull/747) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Update template toml key [\#746](https://github.com/nymtech/nym/pull/746) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/cred after handshake [\#745](https://github.com/nymtech/nym/pull/745) ([neacsu](https://github.com/neacsu))
|
||||
- Reinstate the POST method blind\_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
|
||||
- Reinstate the POST method blind_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
|
||||
- explorer-api: add pending field to port check response [\#742](https://github.com/nymtech/nym/pull/742) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/use delegation rates [\#741](https://github.com/nymtech/nym/pull/741) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/copy to clipboard [\#740](https://github.com/nymtech/nym/pull/740) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/update wallet with stake rates [\#739](https://github.com/nymtech/nym/pull/739) ([neacsu](https://github.com/neacsu))
|
||||
- Add stake reward rates and bump version of client [\#738](https://github.com/nymtech/nym/pull/738) ([neacsu](https://github.com/neacsu))
|
||||
- Bump next from 10.1.3 to 11.1.0 in /wallet-web [\#737](https://github.com/nymtech/nym/pull/737) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/nymd client integration [\#736](https://github.com/nymtech/nym/pull/736) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/nyxd client integration [\#736](https://github.com/nymtech/nym/pull/736) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bug/fix parking lot on wasm [\#735](https://github.com/nymtech/nym/pull/735) ([neacsu](https://github.com/neacsu))
|
||||
- Explorer API: add new HTTP resource to decorate mix nodes with geoip locations [\#734](https://github.com/nymtech/nym/pull/734) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/completing nymd client api [\#732](https://github.com/nymtech/nym/pull/732) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/completing nyxd client api [\#732](https://github.com/nymtech/nym/pull/732) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Explorer API - add port check and node description/stats proxy [\#731](https://github.com/nymtech/nym/pull/731) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/nymd client fee handling [\#730](https://github.com/nymtech/nym/pull/730) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/nyxd client fee handling [\#730](https://github.com/nymtech/nym/pull/730) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Update DelegationCheck.tsx [\#725](https://github.com/nymtech/nym/pull/725) ([jessgess](https://github.com/jessgess))
|
||||
- Rust nymd/cosmwasm client [\#724](https://github.com/nymtech/nym/pull/724) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Rust nyxd/cosmwasm client [\#724](https://github.com/nymtech/nym/pull/724) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Removed wasm feature bypassing cyclic dependencies [\#723](https://github.com/nymtech/nym/pull/723) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Updated used sphinx dependency to the most recent revision [\#722](https://github.com/nymtech/nym/pull/722) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- update state management and validation [\#721](https://github.com/nymtech/nym/pull/721) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -553,10 +638,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Bond and delegation alerts [\#698](https://github.com/nymtech/nym/pull/698) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Bugfix/network monitor version check [\#697](https://github.com/nymtech/nym/pull/697) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/other containers [\#692](https://github.com/nymtech/nym/pull/692) ([neacsu](https://github.com/neacsu))
|
||||
- Using validator API instead of nymd [\#690](https://github.com/nymtech/nym/pull/690) ([futurechimp](https://github.com/futurechimp))
|
||||
- Using validator API instead of nyxd [\#690](https://github.com/nymtech/nym/pull/690) ([futurechimp](https://github.com/futurechimp))
|
||||
- Hang coconut issuance off the validator-api [\#679](https://github.com/nymtech/nym/pull/679) ([durch](https://github.com/durch))
|
||||
- Update hmac and blake3 [\#673](https://github.com/nymtech/nym/pull/673) ([durch](https://github.com/durch))
|
||||
|
||||
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
|
||||
|
||||
Generated
+328
-185
File diff suppressed because it is too large
Load Diff
+5
-3
@@ -30,6 +30,7 @@ members = [
|
||||
"common/coconut-interface",
|
||||
"common/commands",
|
||||
"common/config",
|
||||
"common/build-information",
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
@@ -74,8 +75,9 @@ members = [
|
||||
"mixnode",
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"validator-api",
|
||||
"validator-api/validator-api-requests",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-outfox",
|
||||
"tools/nym-cli",
|
||||
"tools/ts-rs-cli"
|
||||
]
|
||||
@@ -87,7 +89,7 @@ default-members = [
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"mixnode",
|
||||
"validator-api",
|
||||
"nym-api",
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ clippy-coconut:
|
||||
cargo clippy --workspace --features coconut -- -D warnings
|
||||
|
||||
clippy-wasm:
|
||||
cargo clippy --workspace --features wasm -- -D warnings
|
||||
cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings
|
||||
|
||||
|
||||
clippy-all-contracts:
|
||||
@@ -49,9 +49,6 @@ test-main:
|
||||
test-coconut:
|
||||
cargo test --workspace --features coconut
|
||||
|
||||
test-wasm:
|
||||
cargo test --workspace --features wasm
|
||||
|
||||
|
||||
test-main-expensive:
|
||||
cargo test --workspace -- --ignored
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { version = "0.1.58" }
|
||||
dirs = "4.0"
|
||||
dashmap = "5.4.0"
|
||||
futures = "0.3"
|
||||
humantime-serde = "1.0"
|
||||
log = "0.4"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sled = { version = "0.34", optional = true }
|
||||
serde_json = "1.0.89"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
tokio = { version = "1.21.2", features = ["time", "macros"]}
|
||||
tokio = { version = "1.21.2", features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
|
||||
# internal
|
||||
config = { path = "../../common/config" }
|
||||
@@ -37,6 +41,15 @@ task = { path = "../../common/task" }
|
||||
version = "0.1.9"
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.21.2"
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.6.2"
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
|
||||
@@ -51,15 +64,23 @@ rev = "b9d1a54ad514c2f230a026afe0dde341e98cd7b6"
|
||||
version = "0.2.4"
|
||||
features = ["futures"]
|
||||
|
||||
#[target."cfg(not(target_arch = \"wasm32\"))".dependencies.task]
|
||||
#path = "../../common/task"
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../../common/wasm-utils"
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
|
||||
version = "0.3.17"
|
||||
features = ["wasm-bindgen"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
default = ["reply-surb"]
|
||||
default = []
|
||||
fs-surb-storage = ["sqlx"]
|
||||
wasm = ["gateway-client/wasm"]
|
||||
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
|
||||
reply-surb = ["sled"]
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
#[cfg(feature = "fs-surb-storage")]
|
||||
{
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{}/fs-surbs-example.sqlite", out_dir);
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
CREATE TABLE status
|
||||
(
|
||||
flush_in_progress INTEGER NOT NULL,
|
||||
previous_flush_timestamp INTEGER NOT NULL,
|
||||
client_in_use INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE reply_surb_storage_metadata
|
||||
(
|
||||
min_reply_surb_threshold INTEGER NOT NULL,
|
||||
max_reply_surb_threshold INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE sender_tag
|
||||
(
|
||||
recipient BLOB NOT NULL UNIQUE,
|
||||
tag BLOB NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE reply_key
|
||||
(
|
||||
key_digest BLOB NOT NULL UNIQUE,
|
||||
reply_key BLOB NOT NULL UNIQUE,
|
||||
sent_at_timestamp INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE reply_surb_sender
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
last_sent_timestamp INTEGER NOT NULL,
|
||||
tag BLOB NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE reply_surb
|
||||
(
|
||||
reply_surb_sender_id INTEGER NOT NULL,
|
||||
reply_surb BLOB NOT NULL,
|
||||
|
||||
FOREIGN KEY (reply_surb_sender_id) REFERENCES reply_surb_sender (id)
|
||||
);
|
||||
@@ -10,41 +10,45 @@ use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
ReceivedBufferRequestReceiver, ReceivedBufferRequestSender, ReceivedMessagesBufferController,
|
||||
};
|
||||
use crate::client::replies::reply_controller;
|
||||
use crate::client::replies::reply_controller::{ReplyControllerReceiver, ReplyControllerSender};
|
||||
use crate::client::replies::reply_storage::{
|
||||
CombinedReplyStorage, PersistentReplyStorage, ReplyStorageBackend, SentReplyKeys,
|
||||
};
|
||||
use crate::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use crate::config::{Config, DebugConfig, GatewayEndpointConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::spawn_future;
|
||||
use client_connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use crypto::asymmetric::identity;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use gateway_client::{
|
||||
AcknowledgementReceiver, AcknowledgementSender, GatewayClient, MixnetMessageReceiver,
|
||||
MixnetMessageSender,
|
||||
};
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tap::TapFallible;
|
||||
use task::{ShutdownListener, ShutdownNotifier};
|
||||
use task::{TaskClient, TaskManager};
|
||||
use url::Url;
|
||||
|
||||
// it's fine to do this disgusting compilation flag business here as this problem
|
||||
// is going to go away in 1.2.0
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub mod non_wasm_helpers;
|
||||
|
||||
pub struct ClientInput {
|
||||
pub shared_lane_queue_lengths: LaneQueueLengths,
|
||||
pub connection_command_sender: ConnectionCommandSender,
|
||||
pub input_sender: InputMessageSender,
|
||||
}
|
||||
|
||||
pub struct ClientOutput {
|
||||
pub shared_lane_queue_lengths: LaneQueueLengths,
|
||||
pub received_buffer_request_sender: ReceivedBufferRequestSender,
|
||||
}
|
||||
|
||||
@@ -76,35 +80,36 @@ impl ClientOutputStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BaseClientBuilder<'a> {
|
||||
pub struct BaseClientBuilder<'a, B> {
|
||||
// due to wasm limitations I had to split it like this : (
|
||||
gateway_config: &'a GatewayEndpointConfig,
|
||||
debug_config: &'a DebugConfig,
|
||||
disabled_credentials: bool,
|
||||
validator_api_endpoints: Vec<Url>,
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_surb_keys_store_path: PathBuf,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
reply_storage_backend: B,
|
||||
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
key_manager: KeyManager,
|
||||
}
|
||||
|
||||
impl<'a> BaseClientBuilder<'a> {
|
||||
impl<'a, B> BaseClientBuilder<'a, B>
|
||||
where
|
||||
B: ReplyStorageBackend + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new_from_base_config<T>(
|
||||
base_config: &'a Config<T>,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
) -> BaseClientBuilder<'a> {
|
||||
reply_storage_backend: B,
|
||||
) -> BaseClientBuilder<'a, B> {
|
||||
BaseClientBuilder {
|
||||
gateway_config: base_config.get_gateway_endpoint_config(),
|
||||
debug_config: base_config.get_debug_config(),
|
||||
disabled_credentials: base_config.get_disabled_credentials_mode(),
|
||||
validator_api_endpoints: base_config.get_validator_api_endpoints(),
|
||||
nym_api_endpoints: base_config.get_nym_api_endpoints(),
|
||||
bandwidth_controller,
|
||||
reply_storage_backend,
|
||||
key_manager,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_surb_keys_store_path: base_config.get_reply_encryption_key_store_path(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,19 +118,18 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
debug_config: &'a DebugConfig,
|
||||
key_manager: KeyManager,
|
||||
bandwidth_controller: Option<BandwidthController>,
|
||||
reply_storage_backend: B,
|
||||
disabled_credentials: bool,
|
||||
validator_api_endpoints: Vec<Url>,
|
||||
#[cfg(feature = "reply-surb")] reply_surb_keys_store_path: PathBuf,
|
||||
) -> BaseClientBuilder<'a> {
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
) -> BaseClientBuilder<'a, B> {
|
||||
BaseClientBuilder {
|
||||
gateway_config,
|
||||
debug_config,
|
||||
disabled_credentials,
|
||||
validator_api_endpoints,
|
||||
nym_api_endpoints,
|
||||
reply_storage_backend,
|
||||
bandwidth_controller,
|
||||
key_manager,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_surb_keys_store_path,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,24 +146,26 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
// future constantly pumping loop cover traffic at some specified average rate
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream(
|
||||
&self,
|
||||
debug_config: &DebugConfig,
|
||||
ack_key: Arc<AckKey>,
|
||||
self_address: Recipient,
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.debug_config.average_ack_delay,
|
||||
self.debug_config.average_packet_delay,
|
||||
self.debug_config.loop_cover_traffic_average_delay,
|
||||
ack_key,
|
||||
debug_config.average_ack_delay,
|
||||
debug_config.average_packet_delay,
|
||||
debug_config.loop_cover_traffic_average_delay,
|
||||
mix_tx,
|
||||
self.as_mix_recipient(),
|
||||
self_address,
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
if let Some(size) = self.debug_config.use_extended_packet_size {
|
||||
if let Some(size) = debug_config.use_extended_packet_size {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
@@ -169,32 +175,18 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn start_real_traffic_controller(
|
||||
&self,
|
||||
controller_config: real_messages_control::Config,
|
||||
topology_accessor: TopologyAccessor,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
reply_storage: CombinedReplyStorage,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
self.key_manager.ack_key(),
|
||||
self.debug_config.ack_wait_multiplier,
|
||||
self.debug_config.ack_wait_addition,
|
||||
self.debug_config.average_ack_delay,
|
||||
self.debug_config.message_sending_average_delay,
|
||||
self.debug_config.average_packet_delay,
|
||||
self.debug_config.disable_main_poisson_packet_distribution,
|
||||
self.as_mix_recipient(),
|
||||
);
|
||||
|
||||
if let Some(size) = self.debug_config.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(
|
||||
@@ -203,10 +195,11 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
input_receiver,
|
||||
mix_sender,
|
||||
topology_accessor,
|
||||
reply_storage,
|
||||
reply_controller_sender,
|
||||
reply_controller_receiver,
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
)
|
||||
.start_with_shutdown(shutdown);
|
||||
}
|
||||
@@ -214,19 +207,20 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
// 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,
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_receiver: MixnetMessageReceiver,
|
||||
shutdown: ShutdownListener,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(
|
||||
self.key_manager.encryption_keypair(),
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
)
|
||||
.start_with_shutdown(shutdown)
|
||||
}
|
||||
@@ -235,23 +229,23 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
&mut self,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
ack_sender: AcknowledgementSender,
|
||||
shutdown: ShutdownListener,
|
||||
) -> GatewayClient {
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient, ClientCoreError> {
|
||||
let gateway_id = self.gateway_config.gateway_id.clone();
|
||||
if gateway_id.is_empty() {
|
||||
panic!("The identity of the gateway is unknown - did you run `nym-client` init?")
|
||||
return Err(ClientCoreError::GatewayIdUnknown);
|
||||
}
|
||||
let gateway_owner = self.gateway_config.gateway_owner.clone();
|
||||
if gateway_owner.is_empty() {
|
||||
panic!("The owner of the gateway is unknown - did you run `nym-client` init?")
|
||||
return Err(ClientCoreError::GatewayOwnerUnknown);
|
||||
}
|
||||
let gateway_address = self.gateway_config.gateway_listener.clone();
|
||||
if gateway_address.is_empty() {
|
||||
panic!("The address of the gateway is unknown - did you run `nym-client` init?")
|
||||
return Err(ClientCoreError::GatwayAddressUnknown);
|
||||
}
|
||||
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
.expect("provided gateway id is invalid!");
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
// disgusting wasm workaround since there's no key persistence there (nor `client init`)
|
||||
let shared_key = if self.key_manager.gateway_key_set() {
|
||||
@@ -278,21 +272,23 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.expect("could not authenticate and start up the gateway connection");
|
||||
|
||||
gateway_client
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
})?;
|
||||
Ok(gateway_client)
|
||||
}
|
||||
|
||||
// future responsible for periodically polling directory server and updating
|
||||
// the current global view of topology
|
||||
async fn start_topology_refresher(
|
||||
&mut self,
|
||||
nym_api_urls: Vec<Url>,
|
||||
refresh_rate: Duration,
|
||||
topology_accessor: TopologyAccessor,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config = TopologyRefresherConfig::new(
|
||||
self.validator_api_endpoints.clone(),
|
||||
self.debug_config.topology_refresh_rate,
|
||||
nym_api_urls,
|
||||
refresh_rate,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
);
|
||||
let mut topology_refresher =
|
||||
@@ -302,13 +298,12 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
info!("Obtaining initial network topology");
|
||||
topology_refresher.refresh().await;
|
||||
|
||||
// TODO: a slightly more graceful termination here
|
||||
if !topology_refresher.is_topology_routable().await {
|
||||
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
|
||||
log::error!(
|
||||
"The current network topology seem to be insufficient to route any packets through \
|
||||
- check if enough nodes and a gateway are online"
|
||||
- check if enough nodes and a gateway are online - source: {err}"
|
||||
);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology);
|
||||
return Err(ClientCoreError::InsufficientNetworkTopology(err));
|
||||
}
|
||||
|
||||
info!("Starting topology refresher...");
|
||||
@@ -322,7 +317,7 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
// requests?
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_client: GatewayClient,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_client);
|
||||
@@ -330,7 +325,35 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
mix_tx
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError> {
|
||||
async fn setup_persistent_reply_storage(
|
||||
backend: B,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<CombinedReplyStorage, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
{
|
||||
let persistent_storage = PersistentReplyStorage::new(backend);
|
||||
let mem_store = persistent_storage
|
||||
.load_state_from_backend()
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future(async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.await
|
||||
});
|
||||
|
||||
Ok(mem_store)
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
where
|
||||
<B as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
{
|
||||
info!("Starting nym client");
|
||||
// channels for inter-component communication
|
||||
// TODO: make the channels be internally created by the relevant components
|
||||
@@ -351,38 +374,50 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor = TopologyAccessor::new();
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
let reply_key_storage =
|
||||
ReplyKeyStorage::load(&self.reply_surb_keys_store_path).tap_err(|err| {
|
||||
log::error!("Failed to load reply key storage - is it perhaps already in use?");
|
||||
log::error!("{:?}", err);
|
||||
})?;
|
||||
|
||||
// Shutdown notifier for signalling tasks to stop
|
||||
let shutdown = ShutdownNotifier::default();
|
||||
let task_manager = TaskManager::default();
|
||||
|
||||
// channels responsible for dealing with reply-related fun
|
||||
let (reply_controller_sender, reply_controller_receiver) =
|
||||
reply_controller::requests::new_control_channels();
|
||||
|
||||
let self_address = self.as_mix_recipient();
|
||||
|
||||
// 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())
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender, task_manager.subscribe())
|
||||
.await?;
|
||||
self.start_received_messages_buffer_controller(
|
||||
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
self.reply_storage_backend,
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Self::start_topology_refresher(
|
||||
self.nym_api_endpoints.clone(),
|
||||
self.debug_config.topology_refresh_rate,
|
||||
shared_topology_accessor.clone(),
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Self::start_received_messages_buffer_controller(
|
||||
self.key_manager.encryption_keypair(),
|
||||
received_buffer_request_receiver,
|
||||
mixnet_messages_receiver,
|
||||
shutdown.subscribe(),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage.clone(),
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
let gateway_client = self
|
||||
.start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe())
|
||||
.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, shutdown.subscribe());
|
||||
Self::start_mix_traffic_controller(gateway_client, task_manager.subscribe());
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
// controller that connections are closed.
|
||||
@@ -392,43 +427,60 @@ impl<'a> BaseClientBuilder<'a> {
|
||||
// primarily to throttle incoming connections (e.g socks5 for attached network-requesters)
|
||||
let shared_lane_queue_lengths = LaneQueueLengths::new();
|
||||
|
||||
self.start_real_traffic_controller(
|
||||
let mut controller_config = real_messages_control::Config::new(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self_address,
|
||||
);
|
||||
|
||||
if let Some(size) = self.debug_config.use_extended_packet_size {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
|
||||
Self::start_real_traffic_controller(
|
||||
controller_config,
|
||||
shared_topology_accessor.clone(),
|
||||
ack_receiver,
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
reply_storage,
|
||||
reply_controller_sender.clone(),
|
||||
reply_controller_receiver,
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
shutdown.subscribe(),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
if !self.debug_config.disable_loop_cover_traffic_stream {
|
||||
self.start_cover_traffic_stream(
|
||||
Self::start_cover_traffic_stream(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
self_address,
|
||||
shared_topology_accessor,
|
||||
sphinx_message_sender,
|
||||
shutdown.subscribe(),
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
}
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self.as_mix_recipient());
|
||||
debug!("Core client startup finished!");
|
||||
debug!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(BaseClient {
|
||||
client_input: ClientInputStatus::AwaitingProducer {
|
||||
client_input: ClientInput {
|
||||
shared_lane_queue_lengths,
|
||||
connection_command_sender: client_connection_tx,
|
||||
input_sender,
|
||||
},
|
||||
},
|
||||
client_output: ClientOutputStatus::AwaitingConsumer {
|
||||
client_output: ClientOutput {
|
||||
shared_lane_queue_lengths,
|
||||
received_buffer_request_sender,
|
||||
},
|
||||
},
|
||||
shutdown_notifier: shutdown,
|
||||
reply_controller_sender,
|
||||
task_manager,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -437,5 +489,8 @@ pub struct BaseClient {
|
||||
pub client_input: ClientInputStatus,
|
||||
pub client_output: ClientOutputStatus,
|
||||
|
||||
pub shutdown_notifier: ShutdownNotifier,
|
||||
// it feels very wrong to put this channel here, but I can't think of any other way of passing it to the native client
|
||||
pub reply_controller_sender: ReplyControllerSender,
|
||||
|
||||
pub task_manager: TaskManager,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{
|
||||
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
|
||||
};
|
||||
use crate::config::DebugConfig;
|
||||
use crate::error::ClientCoreError;
|
||||
use log::{error, info};
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
debug_config: &DebugConfig,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
info!("creating fresh surb database");
|
||||
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
|
||||
Ok(backend) => backend,
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}");
|
||||
return Err(ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// while I kinda hate that we're going to be creating `CombinedReplyStorage` twice,
|
||||
// it will only be happening on the very first run and in practice won't incur huge
|
||||
// costs since the storage is going to be empty
|
||||
let mem_store = CombinedReplyStorage::new(
|
||||
debug_config.minimum_reply_surb_storage_threshold,
|
||||
debug_config.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
.await
|
||||
.map_err(|err| ClientCoreError::SurbStorageError {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
|
||||
Ok(storage_backend)
|
||||
}
|
||||
|
||||
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
|
||||
let db_path = db_path.as_ref();
|
||||
debug_assert!(db_path.exists());
|
||||
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
|
||||
let suffix = format!("_{now}.corrupted");
|
||||
|
||||
let new_extension =
|
||||
if let Some(existing_extension) = db_path.extension().and_then(|ext| ext.to_str()) {
|
||||
format!("{existing_extension}.{}", suffix)
|
||||
} else {
|
||||
suffix
|
||||
};
|
||||
|
||||
let mut renamed = db_path.to_owned();
|
||||
renamed.set_extension(new_extension);
|
||||
|
||||
fs::rename(db_path, renamed)
|
||||
}
|
||||
|
||||
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
db_path: P,
|
||||
debug_config: &DebugConfig,
|
||||
) -> Result<fs_backend::Backend, ClientCoreError> {
|
||||
// if the database file doesnt exist, initialise fresh storage, otherwise attempt to load the existing one
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
archive_corrupted_database(db_path)?;
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setup_fresh_backend(db_path, debug_config).await
|
||||
}
|
||||
}
|
||||
@@ -161,15 +161,16 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
let topology_ref_option = topology_permit.try_get_valid_topology_ref(
|
||||
let topology_ref = match topology_permit.try_get_valid_topology_ref(
|
||||
&self.our_full_destination,
|
||||
Some(&self.our_full_destination),
|
||||
);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!("No valid topology detected - won't send any loop cover message this time");
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let cover_message = generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
@@ -191,7 +192,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
// However it's still useful to alert the user that the gateway or the link to
|
||||
// the gateway can't keep up. Either due to insufficient bandwidth on the
|
||||
// client side, or that the gateway is overloaded.
|
||||
log::warn!("Failed to send: gateway appears to not keep up");
|
||||
log::warn!("Failed to send sphinx packet - gateway or connection to gatway can't keep up");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
log::warn!("Failed to send cover message - channel closed");
|
||||
@@ -212,7 +213,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::TaskClient) {
|
||||
// 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);
|
||||
@@ -241,19 +242,4 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
log::debug!("LoopCoverTrafficStream: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
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.set_next_delay(sampled);
|
||||
|
||||
spawn_future(async move {
|
||||
debug!("Started LoopCoverTrafficStream without graceful shutdown support");
|
||||
|
||||
while self.next().await.is_some() {
|
||||
self.on_new_message().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod non_wasm;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use non_wasm::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use tokio::time::*;
|
||||
pub type IntervalStream = tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
pub(crate) fn get_time_now() -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
|
||||
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::time::Duration;
|
||||
use wasm_timer;
|
||||
|
||||
pub use wasm_timer::*;
|
||||
pub type IntervalStream = gloo_timers::future::IntervalStream;
|
||||
|
||||
pub(crate) fn get_time_now() -> Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
|
||||
gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32)
|
||||
}
|
||||
@@ -1,40 +1,80 @@
|
||||
use client_connections::TransmissionLane;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
|
||||
pub type InputMessageSender = tokio::sync::mpsc::Sender<InputMessage>;
|
||||
pub type InputMessageReceiver = tokio::sync::mpsc::Receiver<InputMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InputMessage {
|
||||
Fresh {
|
||||
/// The simplest message variant where no additional information is attached.
|
||||
/// You're simply sending your `data` to specified `recipient` without any tagging.
|
||||
///
|
||||
/// Ends up with `NymMessage::Plain` variant
|
||||
Regular {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
Reply {
|
||||
reply_surb: ReplySurb,
|
||||
|
||||
/// Creates a message used for a duplex anonymous communication where the recipient
|
||||
/// will never learn of our true identity. This is achieved by carefully sending `reply_surbs`.
|
||||
///
|
||||
/// Note that if reply_surbs is set to zero then
|
||||
/// this variant requires the client having sent some reply_surbs in the past
|
||||
/// (and thus the recipient also knowing our sender tag).
|
||||
///
|
||||
/// Ends up with `NymMessage::Repliable` variant
|
||||
Anonymous {
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||
/// to specified recipient whilst not knowing its full identity (or even gateway).
|
||||
///
|
||||
/// Ends up with `NymMessage::Reply` variant
|
||||
Reply {
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
}
|
||||
|
||||
impl InputMessage {
|
||||
pub fn new_fresh(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
lane: TransmissionLane,
|
||||
) -> Self {
|
||||
InputMessage::Fresh {
|
||||
pub fn new_regular(recipient: Recipient, data: Vec<u8>, lane: TransmissionLane) -> Self {
|
||||
InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_reply(reply_surb: ReplySurb, data: Vec<u8>) -> Self {
|
||||
InputMessage::Reply { reply_surb, data }
|
||||
pub fn new_anonymous(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
) -> Self {
|
||||
InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_reply(
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) -> Self {
|
||||
InputMessage::Reply {
|
||||
recipient_tag,
|
||||
data,
|
||||
lane,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ impl MixTrafficController {
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(e) => {
|
||||
error!("Failed to send sphinx packet(s) to the gateway! - {:?}", e);
|
||||
Err(err) => {
|
||||
error!("Failed to send sphinx packet(s) to the gateway! - {err}");
|
||||
self.consecutive_gateway_failure_count += 1;
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// todo: in the future this should initiate a 'graceful' shutdown or try
|
||||
@@ -67,11 +67,11 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
@@ -82,8 +82,9 @@ impl MixTrafficController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,14 +92,4 @@ impl MixTrafficController {
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
pub mod base_client;
|
||||
pub mod cover_traffic_stream;
|
||||
pub(crate) mod helpers;
|
||||
pub mod inbound_messages;
|
||||
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 replies;
|
||||
pub mod topology_control;
|
||||
pub(crate) mod transmission_buffer;
|
||||
|
||||
+5
-20
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{Action, ActionSender};
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use futures::StreamExt;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
@@ -16,14 +16,14 @@ use std::sync::Arc;
|
||||
pub(super) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: ActionSender,
|
||||
action_sender: AckActionSender,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: ActionSender,
|
||||
action_sender: AckActionSender,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
@@ -49,11 +49,6 @@ impl AcknowledgementListener {
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
return;
|
||||
} else if frag_id.is_reply() {
|
||||
info!("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;
|
||||
}
|
||||
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
@@ -70,7 +65,7 @@ impl AcknowledgementListener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
@@ -82,7 +77,7 @@ impl AcknowledgementListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("AcknowledgementListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -90,14 +85,4 @@ impl AcknowledgementListener {
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("AcknowledgementListener: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-38
@@ -3,7 +3,7 @@
|
||||
|
||||
use super::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::acknowledgement_control::RetransmissionRequestSender;
|
||||
use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
@@ -13,7 +13,8 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) type ActionSender = UnboundedSender<Action>;
|
||||
pub(crate) type AckActionSender = mpsc::UnboundedSender<Action>;
|
||||
pub(crate) type AckActionReceiver = mpsc::UnboundedReceiver<Action>;
|
||||
|
||||
// The actual data being sent off as well as potential key to the delay queue
|
||||
type PendingAckEntry = (Arc<PendingAcknowledgement>, Option<QueueKey>);
|
||||
@@ -95,7 +96,7 @@ pub(super) struct ActionController {
|
||||
pending_acks_timers: NonExhaustiveDelayQueue<FragmentIdentifier>,
|
||||
|
||||
/// Channel for receiving `Action`s from other modules.
|
||||
incoming_actions: UnboundedReceiver<Action>,
|
||||
incoming_actions: AckActionReceiver,
|
||||
|
||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
@@ -105,18 +106,15 @@ impl ActionController {
|
||||
pub(super) fn new(
|
||||
config: Config,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
) -> (Self, ActionSender) {
|
||||
let (sender, receiver) = mpsc::unbounded();
|
||||
(
|
||||
ActionController {
|
||||
config,
|
||||
pending_acks_data: HashMap::new(),
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions: receiver,
|
||||
retransmission_sender,
|
||||
},
|
||||
sender,
|
||||
)
|
||||
incoming_actions: AckActionReceiver,
|
||||
) -> Self {
|
||||
ActionController {
|
||||
config,
|
||||
pending_acks_data: HashMap::new(),
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions,
|
||||
retransmission_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_insert(&mut self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||
@@ -138,13 +136,18 @@ impl ActionController {
|
||||
trace!("{} is starting its timer", frag_id);
|
||||
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
|
||||
if queue_key.is_some() {
|
||||
// this branch should be IMPOSSIBLE under ANY condition. It would imply starting
|
||||
// timer TWICE for the SAME PendingAcknowledgement
|
||||
panic!("Tried to start an already started ack timer!")
|
||||
}
|
||||
let timeout = (pending_ack_data.delay.clone() * self.config.ack_wait_multiplier)
|
||||
.to_duration()
|
||||
// the fact that this branch is now POSSIBLE is a sign of a need to refactor this whole
|
||||
// retransmission procedure
|
||||
//
|
||||
// (it can happen as timer is started when ack expires to make sure it's not stuck in memory
|
||||
// and the second instance can be fired when we finally get reply surbs for data we failed to retransmit)
|
||||
|
||||
// if queue_key.is_some() {
|
||||
// // this branch should be IMPOSSIBLE under ANY condition. It would imply starting
|
||||
// // timer TWICE for the SAME PendingAcknowledgement
|
||||
// panic!("Tried to start an already started ack timer!")
|
||||
// }
|
||||
let timeout = (pending_ack_data.delay * self.config.ack_wait_multiplier).to_duration()
|
||||
+ self.config.ack_wait_addition;
|
||||
|
||||
let new_queue_key = self.pending_acks_timers.insert(frag_id, timeout);
|
||||
@@ -192,7 +195,8 @@ impl ActionController {
|
||||
trace!("{} is updating its delay", frag_id);
|
||||
// TODO: is it possible to solve this without either locking or temporarily removing the value?
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.remove(&frag_id) {
|
||||
// this Action is triggered by `RetransmissionRequestListener` which held the other potential
|
||||
// this Action is triggered by `RetransmissionRequestListener` (for 'normal' packets)
|
||||
// or `ReplyController` (for 'reply' packets) which held the other potential
|
||||
// reference to this Arc. HOWEVER, before the Action was pushed onto the queue, the reference
|
||||
// was dropped hence this unwrap is safe.
|
||||
let mut inner_data = Arc::try_unwrap(pending_ack_data).unwrap();
|
||||
@@ -245,7 +249,7 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
@@ -266,7 +270,7 @@ impl ActionController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ActionController: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -277,17 +281,4 @@ impl ActionController {
|
||||
.expect("Task stopped without shutdown called");
|
||||
log::debug!("ActionController: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-156
@@ -1,23 +1,14 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{Action, ActionSender};
|
||||
use super::PendingAcknowledgement;
|
||||
use crate::client::{
|
||||
inbound_messages::{InputMessage, InputMessageReceiver},
|
||||
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver};
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use client_connections::TransmissionLane;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::preparer::MessagePreparer;
|
||||
use nymsphinx::{acknowledgements::AckKey, addressing::clients::Recipient};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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.
|
||||
@@ -26,15 +17,9 @@ pub(super) struct InputMessageListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage: ReplyKeyStorage,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
@@ -45,156 +30,86 @@ where
|
||||
// some considerable refactoring
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
input_receiver,
|
||||
message_preparer,
|
||||
action_sender,
|
||||
real_message_sender,
|
||||
topology_access,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
message_handler,
|
||||
reply_controller_sender,
|
||||
}
|
||||
}
|
||||
|
||||
// we require topology for replies to generate surb_acks
|
||||
async fn handle_reply(&mut self, reply_surb: ReplySurb, data: Vec<u8>) -> Option<RealMessage> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match topology_permit.try_get_valid_topology_ref(&self.ack_recipient, None) {
|
||||
Some(topology_ref) => topology_ref,
|
||||
None => {
|
||||
warn!("Could not process the message - the network topology is invalid");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match self
|
||||
.message_preparer
|
||||
.prepare_reply_for_use(data, reply_surb, topology, &self.ack_key)
|
||||
.await
|
||||
{
|
||||
Ok((mix_packet, reply_id)) => {
|
||||
// TODO: later probably write pending ack here
|
||||
// and deal with them....
|
||||
// ... somehow
|
||||
Some(RealMessage::new(mix_packet, reply_id))
|
||||
}
|
||||
Err(err) => {
|
||||
// TODO: should we have some mechanism to indicate to the user that the `reply_surb`
|
||||
// could be reused since technically it wasn't used up here?
|
||||
warn!("failed to deal with received reply surb - {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
async fn handle_reply(
|
||||
&mut self,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
// offload reply handling to the dedicated task
|
||||
self.reply_controller_sender
|
||||
.send_reply(recipient_tag, data, lane)
|
||||
}
|
||||
|
||||
async fn handle_fresh_message(
|
||||
async fn handle_plain_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
content: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
) -> Option<Vec<RealMessage>> {
|
||||
log::trace!("handling msg size: {}", content.len());
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match topology_permit
|
||||
.try_get_valid_topology_ref(&self.ack_recipient, Some(&recipient))
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_plain_message(recipient, content, lane)
|
||||
.await
|
||||
{
|
||||
Some(topology_ref) => topology_ref,
|
||||
None => {
|
||||
warn!("Could not process the message - the network topology is invalid");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// split the message, attach optional reply surb
|
||||
let (split_message, reply_key) = self
|
||||
.message_preparer
|
||||
.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!")
|
||||
warn!("failed to send a plain message - {err}")
|
||||
}
|
||||
}
|
||||
|
||||
#[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());
|
||||
for message_chunk in split_message {
|
||||
// we need to clone it because we need to keep it in memory in case we had to retransmit
|
||||
// it. And then we'd need to recreate entire ACK again.
|
||||
let chunk_clone = message_chunk.clone();
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology, &self.ack_key, &recipient)
|
||||
.unwrap();
|
||||
|
||||
real_messages.push(RealMessage::new(
|
||||
prepared_fragment.mix_packet,
|
||||
message_chunk.fragment_identifier(),
|
||||
));
|
||||
|
||||
pending_acks.push(PendingAcknowledgement::new(
|
||||
message_chunk,
|
||||
prepared_fragment.total_delay,
|
||||
recipient,
|
||||
));
|
||||
async fn handle_repliable_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
content: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a repliable message - {err}")
|
||||
}
|
||||
|
||||
// tells the controller to put this into the hashmap
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_insert(pending_acks))
|
||||
.unwrap();
|
||||
|
||||
Some(real_messages)
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
let (real_messages, lane) = match msg {
|
||||
InputMessage::Fresh {
|
||||
match msg {
|
||||
InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
with_reply_surb,
|
||||
lane,
|
||||
} => (
|
||||
self.handle_fresh_message(recipient, data, with_reply_surb)
|
||||
.await,
|
||||
} => self.handle_plain_message(recipient, data, lane).await,
|
||||
InputMessage::Anonymous {
|
||||
recipient,
|
||||
data,
|
||||
reply_surbs,
|
||||
lane,
|
||||
),
|
||||
InputMessage::Reply { reply_surb, data } => (
|
||||
self.handle_reply(reply_surb, data)
|
||||
} => {
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane)
|
||||
.await
|
||||
.map(|message| vec![message]),
|
||||
TransmissionLane::Reply,
|
||||
),
|
||||
}
|
||||
InputMessage::Reply {
|
||||
recipient_tag,
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
self.handle_reply(recipient_tag, data, lane).await;
|
||||
}
|
||||
};
|
||||
|
||||
// there's no point in trying to send nothing
|
||||
if let Some(real_messages) = real_messages {
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
self.real_message_sender
|
||||
.send((real_messages, lane))
|
||||
.await
|
||||
.expect("BatchRealMessageReceiver has stopped receiving!");
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
@@ -208,7 +123,7 @@ where
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("InputMessageListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -216,13 +131,4 @@ where
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("InputMessageListener: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
pub(super) async fn run(&mut self) {
|
||||
debug!("Started InputMessageListener without graceful shutdown support");
|
||||
while let Some(input_msg) = self.input_receiver.recv().await {
|
||||
self.on_input_message(input_msg).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+76
-92
@@ -7,18 +7,20 @@ use self::{
|
||||
retransmission_request_listener::RetransmissionRequestListener,
|
||||
sent_notification_listener::SentNotificationListener,
|
||||
};
|
||||
use super::real_traffic_stream::BatchRealMessageSender;
|
||||
use crate::client::{inbound_messages::InputMessageReceiver, topology_control::TopologyAccessor};
|
||||
use crate::client::inbound_messages::InputMessageReceiver;
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::spawn_future;
|
||||
use action_controller::AckActionReceiver;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::AcknowledgementReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey,
|
||||
addressing::clients::Recipient,
|
||||
chunking::fragment::{Fragment, FragmentIdentifier},
|
||||
preparer::MessagePreparer,
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use rand::{CryptoRng, Rng};
|
||||
@@ -27,8 +29,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
pub(crate) use action_controller::{AckActionSender, Action};
|
||||
|
||||
mod acknowledgement_listener;
|
||||
mod action_controller;
|
||||
@@ -50,24 +51,64 @@ pub(super) type SentPacketNotificationSender = mpsc::UnboundedSender<FragmentIde
|
||||
/// that it is about to be sent to the mix network and its timeout timer should be started.
|
||||
type SentPacketNotificationReceiver = mpsc::UnboundedReceiver<FragmentIdentifier>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PacketDestination {
|
||||
Anonymous {
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
// special flag to indicate whether this was an ack for requesting additional surbs,
|
||||
// in that case we have to do everything we can to get it through, even if it means going
|
||||
// below our stored reply surb threshold
|
||||
extra_surb_request: bool,
|
||||
},
|
||||
KnownRecipient(Box<Recipient>),
|
||||
}
|
||||
|
||||
/// Structure representing a data `Fragment` that is on-route to the specified `Recipient`
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PendingAcknowledgement {
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
recipient: Recipient,
|
||||
destination: PacketDestination,
|
||||
}
|
||||
|
||||
impl PendingAcknowledgement {
|
||||
/// Creates new instance of `PendingAcknowledgement` using the provided data.
|
||||
fn new(message_chunk: Fragment, delay: SphinxDelay, recipient: Recipient) -> Self {
|
||||
pub(crate) fn new_known(
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
recipient: Recipient,
|
||||
) -> Self {
|
||||
PendingAcknowledgement {
|
||||
message_chunk,
|
||||
delay,
|
||||
recipient,
|
||||
destination: PacketDestination::KnownRecipient(recipient.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_anonymous(
|
||||
message_chunk: Fragment,
|
||||
delay: SphinxDelay,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
extra_surb_request: bool,
|
||||
) -> Self {
|
||||
PendingAcknowledgement {
|
||||
message_chunk,
|
||||
delay,
|
||||
destination: PacketDestination::Anonymous {
|
||||
recipient_tag,
|
||||
extra_surb_request,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn inner_fragment_identifier(&self) -> FragmentIdentifier {
|
||||
self.message_chunk.fragment_identifier()
|
||||
}
|
||||
|
||||
pub(crate) fn fragment_data(&self) -> Fragment {
|
||||
self.message_chunk.clone()
|
||||
}
|
||||
|
||||
fn update_delay(&mut self, new_delay: SphinxDelay) {
|
||||
self.delay = new_delay;
|
||||
}
|
||||
@@ -76,10 +117,6 @@ impl PendingAcknowledgement {
|
||||
/// AcknowledgementControllerConnectors represents set of channels for communication with
|
||||
/// other parts of the system in order to support acknowledgements and retransmission.
|
||||
pub(super) struct AcknowledgementControllerConnectors {
|
||||
/// Channel used for forwarding prepared sphinx messages into the poisson sender
|
||||
/// to be sent to the mix network.
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
|
||||
/// Channel used for receiving raw messages from a client. The messages need to be put
|
||||
/// into sphinx packets first.
|
||||
input_receiver: InputMessageReceiver,
|
||||
@@ -91,20 +128,28 @@ pub(super) struct AcknowledgementControllerConnectors {
|
||||
|
||||
/// Channel used for receiving acknowledgements from the mix network.
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
|
||||
/// Channel used for sending request to `ActionController` to deal with anything ack-related,
|
||||
ack_action_sender: AckActionSender,
|
||||
|
||||
/// Channel used for receiving request by `ActionController` to deal with anything ack-related,
|
||||
ack_action_receiver: AckActionReceiver,
|
||||
}
|
||||
|
||||
impl AcknowledgementControllerConnectors {
|
||||
pub(super) fn new(
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
input_receiver: InputMessageReceiver,
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
ack_action_sender: AckActionSender,
|
||||
ack_action_receiver: AckActionReceiver,
|
||||
) -> Self {
|
||||
AcknowledgementControllerConnectors {
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier,
|
||||
ack_receiver,
|
||||
ack_action_sender,
|
||||
ack_action_receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,28 +162,15 @@ pub(super) struct Config {
|
||||
/// Given ack timeout in the form a * BASE_DELAY + b, it specifies the multiplier `a`
|
||||
ack_wait_multiplier: f64,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delayed at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// 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 {
|
||||
pub(super) fn new(
|
||||
ack_wait_addition: Duration,
|
||||
ack_wait_multiplier: f64,
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
) -> Self {
|
||||
pub(super) fn new(ack_wait_addition: Duration, ack_wait_multiplier: f64) -> Self {
|
||||
Config {
|
||||
ack_wait_addition,
|
||||
ack_wait_multiplier,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
packet_size: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -162,68 +194,51 @@ where
|
||||
|
||||
impl<R> AcknowledgementController<R>
|
||||
where
|
||||
R: 'static + CryptoRng + Rng + Clone + Send,
|
||||
R: 'static + CryptoRng + Rng + Clone + Send + Sync,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn new(
|
||||
config: Config,
|
||||
rng: R,
|
||||
topology_access: TopologyAccessor,
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
connectors: AcknowledgementControllerConnectors,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> 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);
|
||||
|
||||
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);
|
||||
let action_controller = ActionController::new(
|
||||
action_config,
|
||||
retransmission_tx,
|
||||
connectors.ack_action_receiver,
|
||||
);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
let acknowledgement_listener = AcknowledgementListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
connectors.ack_receiver,
|
||||
action_sender.clone(),
|
||||
connectors.ack_action_sender.clone(),
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
let input_message_listener = InputMessageListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient,
|
||||
connectors.input_receiver,
|
||||
message_preparer.clone(),
|
||||
action_sender.clone(),
|
||||
connectors.real_message_sender.clone(),
|
||||
topology_access.clone(),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender.clone(),
|
||||
);
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
let retransmission_request_listener = RetransmissionRequestListener::new(
|
||||
Arc::clone(&ack_key),
|
||||
ack_recipient,
|
||||
message_preparer,
|
||||
action_sender.clone(),
|
||||
connectors.real_message_sender,
|
||||
connectors.ack_action_sender.clone(),
|
||||
message_handler,
|
||||
retransmission_rx,
|
||||
topology_access,
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
// 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);
|
||||
SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener,
|
||||
@@ -234,7 +249,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
pub(super) fn start_with_shutdown(self, shutdown: task::TaskClient) {
|
||||
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;
|
||||
@@ -278,35 +293,4 @@ where
|
||||
debug!("The controller has finished execution!");
|
||||
});
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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!");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+67
-65
@@ -1,82 +1,101 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
action_controller::{Action, ActionSender},
|
||||
action_controller::{AckActionSender, Action},
|
||||
PendingAcknowledgement, RetransmissionRequestReceiver,
|
||||
};
|
||||
use crate::client::{
|
||||
real_messages_control::real_traffic_stream::{BatchRealMessageSender, RealMessage},
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PacketDestination;
|
||||
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use client_connections::TransmissionLane;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nymsphinx::{
|
||||
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
|
||||
};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::chunking::fragment::Fragment;
|
||||
use nymsphinx::preparer::PreparedFragment;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
pub(super) struct RetransmissionRequestListener<R> {
|
||||
action_sender: AckActionSender,
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_recipient: Recipient,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
action_sender: ActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
action_sender: AckActionSender,
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
ack_key,
|
||||
ack_recipient,
|
||||
message_preparer,
|
||||
action_sender,
|
||||
real_message_sender,
|
||||
message_handler,
|
||||
request_receiver,
|
||||
topology_access,
|
||||
reply_controller_sender,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(&mut self, timed_out_ack: Weak<PendingAcknowledgement>) {
|
||||
let timed_out_ack = match timed_out_ack.upgrade() {
|
||||
async fn prepare_normal_retransmission_chunk(
|
||||
&mut self,
|
||||
packet_recipient: Recipient,
|
||||
chunk_data: Fragment,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("retransmitting normal packet...");
|
||||
|
||||
self.message_handler
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(
|
||||
&mut self,
|
||||
weak_timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
) {
|
||||
let timed_out_ack = match weak_timed_out_ack.upgrade() {
|
||||
Some(timed_out_ack) => timed_out_ack,
|
||||
None => {
|
||||
debug!("We received an ack JUST as we were about to retransmit [1]");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let packet_recipient = &timed_out_ack.recipient;
|
||||
let chunk_clone = timed_out_ack.message_chunk.clone();
|
||||
let frag_id = chunk_clone.fragment_identifier();
|
||||
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology_ref = match topology_permit
|
||||
.try_get_valid_topology_ref(&self.ack_recipient, Some(packet_recipient))
|
||||
{
|
||||
Some(topology_ref) => topology_ref,
|
||||
None => {
|
||||
warn!("Could not retransmit the packet - the network topology is invalid");
|
||||
let maybe_prepared_fragment = match &timed_out_ack.destination {
|
||||
PacketDestination::Anonymous {
|
||||
recipient_tag,
|
||||
extra_surb_request,
|
||||
} => {
|
||||
// if this is retransmission for reply, offload it to the dedicated task
|
||||
// that deals with all the surbs
|
||||
return self.reply_controller_sender.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*extra_surb_request,
|
||||
);
|
||||
}
|
||||
PacketDestination::KnownRecipient(recipient) => {
|
||||
self.prepare_normal_retransmission_chunk(
|
||||
**recipient,
|
||||
timed_out_ack.message_chunk.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
|
||||
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
|
||||
|
||||
let prepared_fragment = match maybe_prepared_fragment {
|
||||
Ok(prepared_fragment) => prepared_fragment,
|
||||
Err(err) => {
|
||||
warn!("Could not retransmit the packet - {err}");
|
||||
// we NEED to start timer here otherwise we will have this guy permanently stuck in memory
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
@@ -85,11 +104,6 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk_clone, topology_ref, &self.ack_key, packet_recipient)
|
||||
.unwrap();
|
||||
|
||||
// if we have the ONLY strong reference to the ack data, it means it was removed from the
|
||||
// pending acks
|
||||
if Arc::strong_count(&timed_out_ack) == 1 {
|
||||
@@ -101,7 +115,6 @@ where
|
||||
// we no longer need the reference - let's drop it so that if somehow `UpdateTimer` action
|
||||
// reached the controller before this function terminated, the controller would not panic.
|
||||
drop(timed_out_ack);
|
||||
|
||||
let new_delay = prepared_fragment.total_delay;
|
||||
|
||||
// We know this update will be reflected by the `StartTimer` Action performed when this
|
||||
@@ -116,16 +129,15 @@ where
|
||||
.unwrap();
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.real_message_sender
|
||||
.send((
|
||||
self.message_handler
|
||||
.forward_messages(
|
||||
vec![RealMessage::new(prepared_fragment.mix_packet, frag_id)],
|
||||
TransmissionLane::Retransmission,
|
||||
))
|
||||
)
|
||||
.await
|
||||
.expect("BatchRealMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
@@ -145,14 +157,4 @@ where
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RetransmissionRequestListener: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-20
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::action_controller::{Action, ActionSender};
|
||||
use super::action_controller::{AckActionSender, Action};
|
||||
use super::SentPacketNotificationReceiver;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
@@ -13,13 +13,13 @@ use nymsphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
/// accidentally fire retransmission way quicker than we should have.
|
||||
pub(super) struct SentNotificationListener {
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: ActionSender,
|
||||
action_sender: AckActionSender,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: ActionSender,
|
||||
action_sender: AckActionSender,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
sent_notifier,
|
||||
@@ -31,18 +31,13 @@ impl SentNotificationListener {
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||
return;
|
||||
} else if frag_id.is_reply() {
|
||||
debug!("sent off a reply message - no need to start retransmission timer!");
|
||||
// TODO: probably there will need to be some extra procedure here, like it would
|
||||
// be nice to know that our reply actually reached the recipient (i.e. we got the ack)
|
||||
return;
|
||||
}
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
@@ -56,7 +51,7 @@ impl SentNotificationListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("SentNotificationListener: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -64,14 +59,4 @@ impl SentNotificationListener {
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("SentNotificationListener: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::real_traffic_stream::{
|
||||
BatchRealMessageSender, RealMessage,
|
||||
};
|
||||
use crate::client::real_messages_control::{AckActionSender, Action};
|
||||
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
|
||||
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
|
||||
use client_connections::TransmissionLane;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nymsphinx::acknowledgements::AckKey;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
|
||||
use nymsphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
|
||||
use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use nymsphinx::message::NymMessage;
|
||||
use nymsphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
|
||||
use nymsphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nymsphinx::Delay;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use topology::{NymTopology, NymTopologyError};
|
||||
|
||||
// TODO: move that error elsewhere since it seems to be contaminating different files
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum PreparationError {
|
||||
#[error(transparent)]
|
||||
NymTopologyError(#[from] NymTopologyError),
|
||||
|
||||
#[error("The received message cannot be sent using a single reply surb. It ended up getting split into {fragments} fragments.")]
|
||||
MessageTooLongForSingleSurb { fragments: usize },
|
||||
|
||||
#[error("Not enough reply SURBs to send the message. We have {available} available and require at least {required}.")]
|
||||
NotEnoughSurbs { available: usize, required: usize },
|
||||
}
|
||||
|
||||
impl PreparationError {
|
||||
fn return_surbs(self, returned_surbs: Vec<ReplySurb>) -> SurbWrappedPreparationError {
|
||||
SurbWrappedPreparationError {
|
||||
source: self,
|
||||
returned_surbs: Some(returned_surbs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Failed to prepare packets - {source}. {} reply surbs will be returned", .returned_surbs.as_ref().map(|s| s.len()).unwrap_or_default())]
|
||||
pub struct SurbWrappedPreparationError {
|
||||
#[source]
|
||||
source: PreparationError,
|
||||
|
||||
returned_surbs: Option<Vec<ReplySurb>>,
|
||||
}
|
||||
|
||||
impl<T> From<T> for SurbWrappedPreparationError
|
||||
where
|
||||
T: Into<PreparationError>,
|
||||
{
|
||||
fn from(err: T) -> Self {
|
||||
SurbWrappedPreparationError {
|
||||
source: err.into(),
|
||||
returned_surbs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SurbWrappedPreparationError {
|
||||
pub(crate) fn return_unused_surbs(
|
||||
self,
|
||||
surb_storage: &ReceivedReplySurbsMap,
|
||||
target: &AnonymousSenderTag,
|
||||
) -> PreparationError {
|
||||
if let Some(reply_surbs) = self.returned_surbs {
|
||||
surb_storage.insert_surbs(target, reply_surbs)
|
||||
}
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Config {
|
||||
/// Key used to decrypt contents of received SURBAcks
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Address of this client which also represent an address to which all acknowledgements
|
||||
/// and surb-based are going to be sent.
|
||||
sender_address: Recipient,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
|
||||
/// Predefined packet size used for the encapsulated messages.
|
||||
packet_size: PacketSize,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
sender_address: Recipient,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
) -> Self {
|
||||
Config {
|
||||
ack_key,
|
||||
sender_address,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
packet_size: PacketSize::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
#[allow(dead_code)]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
|
||||
/// Allows setting non-default size of the sphinx packets sent out.
|
||||
pub fn with_custom_packet_size(mut self, packet_size: PacketSize) -> Self {
|
||||
self.packet_size = packet_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MessageHandler<R> {
|
||||
config: Config,
|
||||
rng: R,
|
||||
message_preparer: MessagePreparer<R>,
|
||||
action_sender: AckActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
}
|
||||
|
||||
impl<R> MessageHandler<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
rng: R,
|
||||
action_sender: AckActionSender,
|
||||
real_message_sender: BatchRealMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
) -> Self
|
||||
where
|
||||
R: Copy,
|
||||
{
|
||||
let message_preparer = MessagePreparer::new(
|
||||
rng,
|
||||
config.sender_address,
|
||||
config.average_packet_delay,
|
||||
config.average_ack_delay,
|
||||
)
|
||||
.with_custom_real_message_packet_size(config.packet_size)
|
||||
.with_mix_hops(config.num_mix_hops);
|
||||
|
||||
MessageHandler {
|
||||
config,
|
||||
rng,
|
||||
message_preparer,
|
||||
action_sender,
|
||||
real_message_sender,
|
||||
topology_access,
|
||||
reply_key_storage,
|
||||
tag_storage,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_or_create_sender_tag(&mut self, recipient: &Recipient) -> AnonymousSenderTag {
|
||||
if let Some(existing) = self.tag_storage.try_get_existing(recipient) {
|
||||
trace!("we already had sender tag for {recipient}");
|
||||
existing
|
||||
} else {
|
||||
info!("creating new sender tag for {recipient}");
|
||||
let new_tag = AnonymousSenderTag::new_random(&mut self.rng);
|
||||
self.tag_storage.insert_new(recipient, new_tag);
|
||||
info!("we'll be using {new_tag} for all anonymous messages sent to {recipient}");
|
||||
new_tag
|
||||
}
|
||||
}
|
||||
|
||||
fn get_topology<'a>(
|
||||
&self,
|
||||
permit: &'a TopologyReadPermit<'a>,
|
||||
) -> Result<&'a NymTopology, PreparationError> {
|
||||
match permit.try_get_valid_topology_ref(&self.config.sender_address, None) {
|
||||
Ok(topology_ref) => Ok(topology_ref),
|
||||
Err(err) => {
|
||||
warn!("Could not process the packet - the network topology is invalid - {err}");
|
||||
Err(err.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn generate_reply_surbs_with_keys(
|
||||
&mut self,
|
||||
amount: usize,
|
||||
) -> Result<(Vec<ReplySurb>, Vec<SurbEncryptionKey>), PreparationError> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let reply_surbs = self
|
||||
.message_preparer
|
||||
.generate_reply_surbs(amount, topology)?;
|
||||
|
||||
let reply_keys = reply_surbs
|
||||
.iter()
|
||||
.map(|s| *s.encryption_key())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok((reply_surbs, reply_keys))
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_single_surb_message(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
message: ReplyMessage,
|
||||
reply_surb: ReplySurb,
|
||||
is_extra_surb_request: bool,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let mut fragment = self
|
||||
.message_preparer
|
||||
.pad_and_split_message(NymMessage::new_reply(message));
|
||||
if fragment.len() > 1 {
|
||||
// well, it's not a single surb message
|
||||
return Err(SurbWrappedPreparationError {
|
||||
source: PreparationError::MessageTooLongForSingleSurb {
|
||||
fragments: fragment.len(),
|
||||
},
|
||||
returned_surbs: Some(vec![reply_surb]),
|
||||
});
|
||||
}
|
||||
|
||||
let chunk = fragment.pop().unwrap();
|
||||
let chunk_clone = chunk.clone();
|
||||
let prepared_fragment = self
|
||||
.try_prepare_single_reply_chunk_for_sending(reply_surb, chunk_clone)
|
||||
.await?;
|
||||
|
||||
let real_messages =
|
||||
RealMessage::new(prepared_fragment.mix_packet, chunk.fragment_identifier());
|
||||
let delay = prepared_fragment.total_delay;
|
||||
let pending_ack =
|
||||
PendingAcknowledgement::new_anonymous(chunk, delay, target, is_extra_surb_request);
|
||||
|
||||
let lane = if is_extra_surb_request {
|
||||
TransmissionLane::ReplySurbRequest
|
||||
} else {
|
||||
TransmissionLane::General
|
||||
};
|
||||
|
||||
self.forward_messages(vec![real_messages], lane).await;
|
||||
self.insert_pending_acks(vec![pending_ack]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_request_additional_reply_surbs(
|
||||
&mut self,
|
||||
from: AnonymousSenderTag,
|
||||
reply_surb: ReplySurb,
|
||||
amount: u32,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("requesting {amount} reply SURBs from {from}");
|
||||
|
||||
let surbs_request =
|
||||
ReplyMessage::new_surb_request_message(self.config.sender_address, amount);
|
||||
self.try_send_single_surb_message(from, surbs_request, reply_surb, true)
|
||||
.await
|
||||
}
|
||||
|
||||
// // TODO: this will require additional argument to make it use different variant of `ReplyMessage`
|
||||
pub(crate) fn split_reply_message(&mut self, message: Vec<u8>) -> Vec<Fragment> {
|
||||
self.message_preparer
|
||||
.pad_and_split_message(NymMessage::new_reply(ReplyMessage::new_data_message(
|
||||
message,
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn send_retransmission_reply_chunks(
|
||||
&mut self,
|
||||
prepared_fragments: Vec<PreparedFragment>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
let mut real_messages = Vec::with_capacity(prepared_fragments.len());
|
||||
|
||||
for prepared in prepared_fragments {
|
||||
self.update_ack_delay(prepared.fragment_identifier, prepared.total_delay);
|
||||
real_messages.push(prepared.into())
|
||||
}
|
||||
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_reply_chunks_on_lane(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
// TODO: technically this is performing an unnecessary cloning, but in the grand scheme of things
|
||||
// is it really that bad?
|
||||
self.try_send_reply_chunks(
|
||||
target,
|
||||
fragments.into_iter().map(|f| (lane, f)).collect(),
|
||||
reply_surbs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_reply_chunks(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let prepared_fragments = self
|
||||
.prepare_reply_chunks_for_sending(
|
||||
fragments.iter().map(|(_, f)| f.clone()).collect(),
|
||||
reply_surbs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut pending_acks = Vec::with_capacity(fragments.len());
|
||||
let mut to_forward: HashMap<_, Vec<_>> = HashMap::new();
|
||||
|
||||
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
|
||||
let lane = raw.0;
|
||||
let fragment = raw.1;
|
||||
|
||||
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
|
||||
let delay = prepared.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
|
||||
|
||||
let entry = to_forward.entry(lane).or_default();
|
||||
entry.push(real_message);
|
||||
pending_acks.push(pending_ack);
|
||||
}
|
||||
|
||||
for (lane, real_messages) in to_forward {
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
}
|
||||
|
||||
self.insert_pending_acks(pending_acks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_plain_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), PreparationError> {
|
||||
let message = NymMessage::new_plain(message);
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn try_split_and_send_non_reply_message(
|
||||
&mut self,
|
||||
message: NymMessage,
|
||||
recipient: Recipient,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), PreparationError> {
|
||||
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
|
||||
debug_assert!(!matches!(message, NymMessage::Reply(_)));
|
||||
|
||||
// TODO2: it's really annoying we have to get topology permit again here due to borrow-checker
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let fragments = self.message_preparer.pad_and_split_message(message);
|
||||
|
||||
let mut pending_acks = Vec::with_capacity(fragments.len());
|
||||
let mut real_messages = Vec::with_capacity(fragments.len());
|
||||
for fragment in fragments {
|
||||
// we need to clone it because we need to keep it in memory in case we had to retransmit
|
||||
// it. And then we'd need to recreate entire ACK again.
|
||||
let chunk_clone = fragment.clone();
|
||||
let prepared_fragment = self.message_preparer.prepare_chunk_for_sending(
|
||||
chunk_clone,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
)?;
|
||||
|
||||
let real_message =
|
||||
RealMessage::new(prepared_fragment.mix_packet, fragment.fragment_identifier());
|
||||
let delay = prepared_fragment.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
|
||||
|
||||
real_messages.push(real_message);
|
||||
pending_acks.push(pending_ack);
|
||||
}
|
||||
|
||||
self.insert_pending_acks(pending_acks);
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_additional_reply_surbs(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
) -> Result<(), PreparationError> {
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) =
|
||||
self.generate_reply_surbs_with_keys(amount as usize).await?;
|
||||
|
||||
let message = NymMessage::new_repliable(RepliableMessage::new_additional_surbs(
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
));
|
||||
|
||||
self.try_split_and_send_non_reply_message(
|
||||
message,
|
||||
recipient,
|
||||
TransmissionLane::AdditionalReplySurbs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::trace!("storing {} reply keys", reply_keys.len());
|
||||
self.reply_key_storage.insert_multiple(reply_keys);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_message_with_reply_surbs(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
num_reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
let (reply_surbs, reply_keys) = self
|
||||
.generate_reply_surbs_with_keys(num_reply_surbs as usize)
|
||||
.await?;
|
||||
|
||||
let message =
|
||||
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
|
||||
|
||||
self.try_split_and_send_non_reply_message(message, recipient, lane)
|
||||
.await?;
|
||||
|
||||
log::trace!("storing {} reply keys", reply_keys.len());
|
||||
self.reply_key_storage.insert_multiple(reply_keys);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_prepare_single_chunk_for_sending(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
chunk: Fragment,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = self.get_topology(&topology_permit)?;
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(chunk, topology, &self.config.ack_key, &recipient)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
}
|
||||
|
||||
pub(crate) async fn prepare_reply_chunks_for_sending(
|
||||
&mut self,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Result<Vec<PreparedFragment>, SurbWrappedPreparationError> {
|
||||
debug_assert_ne!(
|
||||
fragments.len(),
|
||||
reply_surbs.len(),
|
||||
"attempted to send {} fragments with {} reply surbs",
|
||||
fragments.len(),
|
||||
reply_surbs.len()
|
||||
);
|
||||
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match self.get_topology(&topology_permit) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => return Err(err.return_surbs(reply_surbs)),
|
||||
};
|
||||
|
||||
Ok(fragments
|
||||
.into_iter()
|
||||
.zip(reply_surbs.into_iter())
|
||||
.map(|(fragment, reply_surb)| {
|
||||
// unwrap here is fine as we know we have a valid topology
|
||||
self.message_preparer
|
||||
.prepare_reply_chunk_for_sending(
|
||||
fragment,
|
||||
topology,
|
||||
&self.config.ack_key,
|
||||
reply_surb,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_prepare_single_reply_chunk_for_sending(
|
||||
&mut self,
|
||||
reply_surb: ReplySurb,
|
||||
chunk: Fragment,
|
||||
) -> Result<PreparedFragment, SurbWrappedPreparationError> {
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
let topology = match self.get_topology(&topology_permit) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => return Err(err.return_surbs(vec![reply_surb])),
|
||||
};
|
||||
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_reply_chunk_for_sending(chunk, topology, &self.config.ack_key, reply_surb)
|
||||
.unwrap();
|
||||
|
||||
Ok(prepared_fragment)
|
||||
}
|
||||
|
||||
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
||||
self.action_sender
|
||||
.unbounded_send(Action::UpdateDelay(id, new_delay))
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
pub(crate) fn insert_pending_acks(&self, pending_acks: Vec<PendingAcknowledgement>) {
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_insert(pending_acks))
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
// tells real message sender (with the poisson timer) to send this to the mix network
|
||||
pub(crate) async fn forward_messages(
|
||||
&self,
|
||||
messages: Vec<RealMessage>,
|
||||
transmission_lane: TransmissionLane,
|
||||
) {
|
||||
self.real_message_sender
|
||||
.send((messages, transmission_lane))
|
||||
.await
|
||||
.expect("real message receiver task (OutQueueControl) has died");
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
use self::{
|
||||
acknowledgement_control::AcknowledgementController, real_traffic_stream::OutQueueControl,
|
||||
};
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::{
|
||||
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
|
||||
};
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use crate::{
|
||||
client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||
@@ -27,11 +32,13 @@ use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorage;
|
||||
use crate::client::replies::reply_controller;
|
||||
use crate::config;
|
||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||
|
||||
mod acknowledgement_control;
|
||||
mod real_traffic_stream;
|
||||
pub(crate) mod acknowledgement_control;
|
||||
pub(crate) mod message_handler;
|
||||
pub(crate) mod real_traffic_stream;
|
||||
|
||||
// TODO: ack_key and self_recipient shouldn't really be part of this config
|
||||
pub struct Config {
|
||||
@@ -62,31 +69,102 @@ pub struct Config {
|
||||
|
||||
/// Predefined packet size used for the encapsulated messages.
|
||||
packet_size: PacketSize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
minimum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
maximum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
maximum_reply_surb_waiting_period: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
maximum_reply_surb_age: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
maximum_reply_key_age: Duration,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for acknowledgement_control::Config {
|
||||
fn from(cfg: &'a Config) -> Self {
|
||||
acknowledgement_control::Config::new(cfg.ack_wait_addition, cfg.ack_wait_multiplier)
|
||||
.with_custom_packet_size(cfg.packet_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for real_traffic_stream::Config {
|
||||
fn from(cfg: &'a Config) -> Self {
|
||||
real_traffic_stream::Config::new(
|
||||
Arc::clone(&cfg.ack_key),
|
||||
cfg.self_recipient,
|
||||
cfg.average_ack_delay_duration,
|
||||
cfg.average_packet_delay_duration,
|
||||
cfg.average_message_sending_delay,
|
||||
cfg.disable_main_poisson_packet_distribution,
|
||||
)
|
||||
.with_custom_cover_packet_size(cfg.packet_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for reply_controller::Config {
|
||||
fn from(cfg: &'a Config) -> Self {
|
||||
reply_controller::Config::new(
|
||||
cfg.minimum_reply_surb_request_size,
|
||||
cfg.maximum_reply_surb_request_size,
|
||||
cfg.maximum_allowed_reply_surb_request_size,
|
||||
cfg.maximum_reply_surb_waiting_period,
|
||||
cfg.maximum_reply_surb_age,
|
||||
cfg.maximum_reply_key_age,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for message_handler::Config {
|
||||
fn from(cfg: &'a Config) -> Self {
|
||||
message_handler::Config::new(
|
||||
Arc::clone(&cfg.ack_key),
|
||||
cfg.self_recipient,
|
||||
cfg.average_packet_delay_duration,
|
||||
cfg.average_ack_delay_duration,
|
||||
)
|
||||
.with_custom_packet_size(cfg.packet_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// TODO: change the config into a builder
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
base_client_debug_config: &config::DebugConfig,
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_wait_multiplier: f64,
|
||||
ack_wait_addition: Duration,
|
||||
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 {
|
||||
ack_key,
|
||||
ack_wait_addition,
|
||||
ack_wait_multiplier,
|
||||
self_recipient,
|
||||
average_message_sending_delay,
|
||||
average_packet_delay_duration,
|
||||
average_ack_delay_duration,
|
||||
disable_main_poisson_packet_distribution,
|
||||
packet_size: Default::default(),
|
||||
ack_wait_addition: base_client_debug_config.ack_wait_addition,
|
||||
ack_wait_multiplier: base_client_debug_config.ack_wait_multiplier,
|
||||
average_message_sending_delay: base_client_debug_config.message_sending_average_delay,
|
||||
average_packet_delay_duration: base_client_debug_config.average_packet_delay,
|
||||
average_ack_delay_duration: base_client_debug_config.average_ack_delay,
|
||||
disable_main_poisson_packet_distribution: base_client_debug_config
|
||||
.disable_main_poisson_packet_distribution,
|
||||
minimum_reply_surb_request_size: base_client_debug_config
|
||||
.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: base_client_debug_config
|
||||
.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: base_client_debug_config
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_waiting_period: base_client_debug_config
|
||||
.maximum_reply_surb_waiting_period,
|
||||
maximum_reply_surb_age: base_client_debug_config.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: base_client_debug_config.maximum_reply_key_age,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,75 +173,84 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RealMessagesController<R>
|
||||
pub(crate) struct RealMessagesController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
out_queue_control: OutQueueControl<R>,
|
||||
ack_control: AcknowledgementController<R>,
|
||||
reply_control: ReplyController<R>,
|
||||
}
|
||||
|
||||
// obviously when we finally make shared rng that is on 'higher' level, this should become
|
||||
// generic `R`
|
||||
impl RealMessagesController<OsRng> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
input_receiver: InputMessageReceiver,
|
||||
mix_sender: BatchMixMessageSender,
|
||||
topology_access: TopologyAccessor,
|
||||
reply_storage: CombinedReplyStorage,
|
||||
// so much refactoring needed, but this is temporary just to test things out
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
// create channels for inter-task communication
|
||||
let (real_message_sender, real_message_receiver) = tokio::sync::mpsc::channel(1);
|
||||
let (sent_notifier_tx, sent_notifier_rx) = mpsc::unbounded();
|
||||
|
||||
let (ack_action_tx, ack_action_rx) = mpsc::unbounded();
|
||||
let ack_controller_connectors = AcknowledgementControllerConnectors::new(
|
||||
real_message_sender,
|
||||
input_receiver,
|
||||
sent_notifier_rx,
|
||||
ack_receiver,
|
||||
ack_action_tx.clone(),
|
||||
ack_action_rx,
|
||||
);
|
||||
|
||||
let ack_control_config = acknowledgement_control::Config::new(
|
||||
config.ack_wait_addition,
|
||||
config.ack_wait_multiplier,
|
||||
config.average_ack_delay_duration,
|
||||
config.average_packet_delay_duration,
|
||||
)
|
||||
.with_custom_packet_size(config.packet_size);
|
||||
// create all configs for the components
|
||||
let ack_control_config = (&config).into();
|
||||
let out_queue_config = (&config).into();
|
||||
let reply_controller_config = (&config).into();
|
||||
let message_handler_config = (&config).into();
|
||||
|
||||
// create the actual components
|
||||
let message_handler = MessageHandler::new(
|
||||
message_handler_config,
|
||||
rng,
|
||||
ack_action_tx,
|
||||
real_message_sender,
|
||||
topology_access.clone(),
|
||||
reply_storage.key_storage(),
|
||||
reply_storage.tags_storage(),
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
ack_control_config,
|
||||
rng,
|
||||
topology_access.clone(),
|
||||
Arc::clone(&config.ack_key),
|
||||
config.self_recipient,
|
||||
ack_controller_connectors,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
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 reply_control = ReplyController::new(
|
||||
reply_controller_config,
|
||||
message_handler,
|
||||
reply_storage,
|
||||
reply_controller_receiver,
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
out_queue_config,
|
||||
Arc::clone(&config.ack_key),
|
||||
rng,
|
||||
sent_notifier_tx,
|
||||
mix_sender,
|
||||
real_message_receiver,
|
||||
rng,
|
||||
config.self_recipient,
|
||||
topology_access,
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
@@ -172,30 +259,26 @@ impl RealMessagesController<OsRng> {
|
||||
RealMessagesController {
|
||||
out_queue_control,
|
||||
ack_control,
|
||||
reply_control,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
pub fn start_with_shutdown(self, shutdown: task::TaskClient) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
|
||||
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!");
|
||||
});
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
reply_control.run_with_shutdown(shutdown_handle).await;
|
||||
debug!("The reply controller has finished execution!");
|
||||
});
|
||||
|
||||
ack_control.start_with_shutdown(shutdown);
|
||||
}
|
||||
|
||||
#[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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::sending_delay_controller::SendingDelayController;
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
use client_connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
@@ -16,6 +18,7 @@ use nymsphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nymsphinx::cover::generate_loop_cover_packet;
|
||||
use nymsphinx::forwarding::packet::MixPacket;
|
||||
use nymsphinx::params::PacketSize;
|
||||
use nymsphinx::preparer::PreparedFragment;
|
||||
use nymsphinx::utils::sample_poisson_duration;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
@@ -28,25 +31,16 @@ use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
use self::{
|
||||
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
|
||||
};
|
||||
|
||||
mod sending_delay_controller;
|
||||
mod transmission_buffer;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_time_now() -> time::Instant {
|
||||
time::Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_time_now() -> wasm_timer::Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
pub(crate) struct Config {
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
|
||||
average_ack_delay: Duration,
|
||||
|
||||
@@ -66,12 +60,16 @@ pub(crate) struct Config {
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(
|
||||
ack_key: Arc<AckKey>,
|
||||
our_full_destination: Recipient,
|
||||
average_ack_delay: Duration,
|
||||
average_packet_delay: Duration,
|
||||
average_message_sending_delay: Duration,
|
||||
disable_poisson_packet_distribution: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
ack_key,
|
||||
our_full_destination,
|
||||
average_ack_delay,
|
||||
average_packet_delay,
|
||||
average_message_sending_delay,
|
||||
@@ -93,9 +91,6 @@ where
|
||||
/// Configurable parameters of the `ActionController`
|
||||
config: Config,
|
||||
|
||||
/// Key used to encrypt and decrypt content of an ACK packet.
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Channel used for notifying of a real packet being sent out. Used to start up retransmission timer.
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
|
||||
@@ -119,9 +114,6 @@ where
|
||||
/// before being sent out into the network.
|
||||
real_receiver: BatchRealMessageReceiver,
|
||||
|
||||
/// Represents full address of this client.
|
||||
our_full_destination: Recipient,
|
||||
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
@@ -130,7 +122,7 @@ where
|
||||
|
||||
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
|
||||
/// out to the mixnet.
|
||||
transmission_buffer: TransmissionBuffer,
|
||||
transmission_buffer: TransmissionBuffer<RealMessage>,
|
||||
|
||||
/// Incoming channel for being notified of closed connections, so that we can close lanes
|
||||
/// corresponding to connections. To avoid sending traffic unnecessary
|
||||
@@ -144,9 +136,23 @@ where
|
||||
pub(crate) struct RealMessage {
|
||||
mix_packet: MixPacket,
|
||||
fragment_id: FragmentIdentifier,
|
||||
// TODO: add info about it being constructed with reply-surb
|
||||
}
|
||||
|
||||
impl From<PreparedFragment> for RealMessage {
|
||||
fn from(fragment: PreparedFragment) -> Self {
|
||||
RealMessage {
|
||||
mix_packet: fragment.mix_packet,
|
||||
fragment_id: fragment.fragment_identifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RealMessage {
|
||||
pub(crate) fn packet_size(&self) -> usize {
|
||||
self.mix_packet.sphinx_packet().len()
|
||||
}
|
||||
|
||||
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
|
||||
RealMessage {
|
||||
mix_packet,
|
||||
@@ -175,28 +181,24 @@ where
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
ack_key: Arc<AckKey>,
|
||||
rng: R,
|
||||
sent_notifier: SentPacketNotificationSender,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
real_receiver: BatchRealMessageReceiver,
|
||||
rng: R,
|
||||
our_full_destination: Recipient,
|
||||
topology_access: TopologyAccessor,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
ack_key,
|
||||
sent_notifier,
|
||||
next_delay: None,
|
||||
sending_delay_controller: Default::default(),
|
||||
mix_tx,
|
||||
real_receiver,
|
||||
our_full_destination,
|
||||
rng,
|
||||
topology_access,
|
||||
transmission_buffer: Default::default(),
|
||||
transmission_buffer: TransmissionBuffer::new(),
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
}
|
||||
@@ -220,24 +222,23 @@ where
|
||||
// poisson delay, but is it really a problem?
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
// the ack is sent back to ourselves (and then ignored)
|
||||
let topology_ref_option = topology_permit.try_get_valid_topology_ref(
|
||||
&self.our_full_destination,
|
||||
Some(&self.our_full_destination),
|
||||
);
|
||||
if topology_ref_option.is_none() {
|
||||
warn!(
|
||||
"No valid topology detected - won't send any loop cover message this time"
|
||||
);
|
||||
return;
|
||||
}
|
||||
let topology_ref = topology_ref_option.unwrap();
|
||||
let topology_ref = match topology_permit.try_get_valid_topology_ref(
|
||||
&self.config.our_full_destination,
|
||||
Some(&self.config.our_full_destination),
|
||||
) {
|
||||
Ok(topology) => topology,
|
||||
Err(err) => {
|
||||
warn!("We're not going to send any loop cover message this time, as the current topology seem to be invalid - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
topology_ref,
|
||||
&self.ack_key,
|
||||
&self.our_full_destination,
|
||||
&self.config.ack_key,
|
||||
&self.config.our_full_destination,
|
||||
self.config.average_ack_delay,
|
||||
self.config.average_packet_delay,
|
||||
self.config.cover_packet_size,
|
||||
@@ -254,7 +255,7 @@ where
|
||||
};
|
||||
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
log::error!("Failed to send: {}", err);
|
||||
log::error!("Failed to send: {err}");
|
||||
}
|
||||
|
||||
// notify ack controller about sending our message only after we actually managed to push it
|
||||
@@ -318,7 +319,9 @@ where
|
||||
|
||||
fn pop_next_message(&mut self) -> Option<RealMessage> {
|
||||
// Pop the next message from the transmission buffer
|
||||
let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?;
|
||||
let (lane, real_next) = self
|
||||
.transmission_buffer
|
||||
.pop_next_message_at_random(&mut self.rng)?;
|
||||
|
||||
// Update the published queue length
|
||||
let lane_length = self.transmission_buffer.lane_length(&lane);
|
||||
@@ -459,7 +462,9 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status(&self) {
|
||||
fn log_status(&self, shutdown: &mut task::TaskClient) {
|
||||
use crate::error::ClientCoreStatusMessage;
|
||||
|
||||
let packets = self.transmission_buffer.total_size();
|
||||
let backlog = self.transmission_buffer.total_size_in_bytes() as f64 / 1024.0;
|
||||
let lanes = self.transmission_buffer.num_lanes();
|
||||
@@ -483,19 +488,26 @@ where
|
||||
} else {
|
||||
log::debug!("{status_str}");
|
||||
}
|
||||
|
||||
// Send status message to whoever is listening (possibly UI)
|
||||
if mult == self.sending_delay_controller.max_multiplier() {
|
||||
shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsVerySlow));
|
||||
} else if mult > self.sending_delay_controller.min_multiplier() {
|
||||
shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsSlow));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status_infrequent(&self) {
|
||||
if self.sending_delay_controller.current_multiplier() > 1 {
|
||||
log::warn!(
|
||||
"Unable to send packets fast enough - sending delay multiplier set to: {}",
|
||||
"Unable to send packets at the default rate - rate reduced by setting the delay multiplier set to: {}",
|
||||
self.sending_delay_controller.current_multiplier()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub(super) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started OutQueueControl with graceful shutdown support");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -506,11 +518,11 @@ where
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("OutQueueControl: Received shutdown");
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
self.log_status();
|
||||
self.log_status(&mut shutdown);
|
||||
}
|
||||
_ = infrequent_status_timer.tick() => {
|
||||
self.log_status_infrequent();
|
||||
@@ -547,16 +559,6 @@ where
|
||||
}
|
||||
log::debug!("OutQueueControl: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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>
|
||||
|
||||
+13
-16
@@ -1,14 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::get_time_now;
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
// the available buffer space we want to take somewhat swift action, but we still need to give a
|
||||
// short time to give the channel a chance reduce pressure.
|
||||
@@ -39,19 +34,11 @@ pub(crate) struct SendingDelayController {
|
||||
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,
|
||||
time_when_changed: 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,
|
||||
time_when_backpressure_detected: Instant,
|
||||
}
|
||||
|
||||
impl Default for SendingDelayController {
|
||||
@@ -77,6 +64,16 @@ impl SendingDelayController {
|
||||
self.current_multiplier
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn min_multiplier(&self) -> u32 {
|
||||
self.lower_bound
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn max_multiplier(&self) -> u32 {
|
||||
self.upper_bound
|
||||
}
|
||||
|
||||
pub(crate) fn increase_delay_multiplier(&mut self) {
|
||||
if self.current_multiplier < self.upper_bound {
|
||||
self.current_multiplier =
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::client::replies::reply_storage::SentReplyKeys;
|
||||
use crate::spawn_future;
|
||||
use crypto::asymmetric::encryption;
|
||||
use crypto::Digest;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
use gateway_client::MixnetMessageReceiver;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::requests::{
|
||||
RepliableMessage, RepliableMessageContent, ReplyMessage, ReplyMessageContent,
|
||||
};
|
||||
use nymsphinx::anonymous_replies::{encryption_key::EncryptionKeyDigest, SurbEncryptionKey};
|
||||
use nymsphinx::message::{NymMessage, PlainMessage};
|
||||
use nymsphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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"
|
||||
pub type ReceivedBufferRequestSender = mpsc::UnboundedSender<ReceivedBufferMessage>;
|
||||
@@ -46,26 +46,15 @@ struct ReceivedMessagesBufferInner {
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferInner {
|
||||
fn process_received_fragment(&mut self, raw_fragment: Vec<u8>) -> Option<ReconstructedMessage> {
|
||||
let fragment_data = match self
|
||||
.message_receiver
|
||||
.recover_plaintext(self.local_encryption_keypair.private_key(), raw_fragment)
|
||||
{
|
||||
Err(e) => {
|
||||
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) {
|
||||
fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option<NymMessage> {
|
||||
if nymsphinx::cover::is_cover(fragment_data) {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
return None;
|
||||
}
|
||||
|
||||
let fragment = match self.message_receiver.recover_fragment(&fragment_data) {
|
||||
Err(e) => {
|
||||
warn!("failed to recover fragment from raw data: {:?}. The whole underlying message might be corrupted and unrecoverable!", e);
|
||||
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
return None;
|
||||
}
|
||||
Ok(frag) => frag,
|
||||
@@ -79,9 +68,10 @@ impl ReceivedMessagesBufferInner {
|
||||
// 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) => {
|
||||
MessageRecoveryError::MalformedReconstructedMessage { source, used_sets } => {
|
||||
error!("message reconstruction failed - {source}. Attempting to re-use the message sets...");
|
||||
// TODO: should we really insert reconstructed sets? could this be abused for some attack?
|
||||
for set_id in message_sets {
|
||||
for set_id in used_sets {
|
||||
if !self.recently_reconstructed.insert(set_id) {
|
||||
// or perhaps we should even panic at this point?
|
||||
error!("Reconstructed another message containing already used set id!")
|
||||
@@ -107,6 +97,34 @@ impl ReceivedMessagesBufferInner {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn process_received_reply(
|
||||
&mut self,
|
||||
reply_ciphertext: &mut [u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
) -> Option<NymMessage> {
|
||||
// note: this performs decryption IN PLACE without extra allocation
|
||||
self.message_receiver
|
||||
.recover_plaintext_from_reply(reply_ciphertext, reply_key);
|
||||
let fragment_data = reply_ciphertext;
|
||||
|
||||
self.recover_from_fragment(fragment_data)
|
||||
}
|
||||
|
||||
fn process_received_regular_packet(&mut self, mut raw_fragment: Vec<u8>) -> Option<NymMessage> {
|
||||
let fragment_data = match self.message_receiver.recover_plaintext_from_regular_packet(
|
||||
self.local_encryption_keypair.private_key(),
|
||||
&mut raw_fragment,
|
||||
) {
|
||||
Err(err) => {
|
||||
warn!("failed to recover fragment data: {err}. The whole underlying message might be corrupted and unrecoverable!");
|
||||
return None;
|
||||
}
|
||||
Ok(frag_data) => frag_data,
|
||||
};
|
||||
|
||||
self.recover_from_fragment(fragment_data)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -114,17 +132,15 @@ impl ReceivedMessagesBufferInner {
|
||||
// You should always use .clone() to create additional instances
|
||||
struct ReceivedMessagesBuffer {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner>>,
|
||||
|
||||
/// 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,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBuffer {
|
||||
fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -134,8 +150,8 @@ impl ReceivedMessagesBuffer {
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
})),
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,34 +193,139 @@ impl ReceivedMessagesBuffer {
|
||||
guard.message_sender = Some(sender);
|
||||
}
|
||||
|
||||
async fn add_reconstructed_messages(&mut self, msgs: Vec<ReconstructedMessage>) {
|
||||
debug!("Adding {:?} new messages to the buffer!", msgs.len());
|
||||
trace!("Adding new messages to the buffer! {:?}", msgs);
|
||||
self.inner.lock().await.messages.extend(msgs)
|
||||
fn handle_reconstructed_plain_messages(
|
||||
&mut self,
|
||||
msgs: Vec<PlainMessage>,
|
||||
) -> Vec<ReconstructedMessage> {
|
||||
msgs.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
fn process_received_reply(
|
||||
reply_ciphertext: &[u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
) -> Option<ReconstructedMessage> {
|
||||
let zero_iv = stream_cipher::zero_iv::<ReplySurbEncryptionAlgorithm>();
|
||||
fn handle_reconstructed_repliable_messages(
|
||||
&mut self,
|
||||
msgs: Vec<RepliableMessage>,
|
||||
) -> Vec<ReconstructedMessage> {
|
||||
let mut reconstructed = Vec::new();
|
||||
for msg in msgs {
|
||||
let (reply_surbs, from_surb_request) = match msg.content {
|
||||
RepliableMessageContent::Data {
|
||||
message,
|
||||
reply_surbs,
|
||||
} => {
|
||||
trace!(
|
||||
"received message that also contained additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
msg.sender_tag
|
||||
);
|
||||
|
||||
let mut reply_msg = stream_cipher::decrypt::<ReplySurbEncryptionAlgorithm>(
|
||||
reply_key.inner(),
|
||||
&zero_iv,
|
||||
reply_ciphertext,
|
||||
);
|
||||
if let Err(err) = MessageReceiver::remove_padding(&mut reply_msg) {
|
||||
warn!("Received reply had malformed padding! - {:?}", err);
|
||||
None
|
||||
} else {
|
||||
// TODO: perhaps having to say it doesn't have a surb an indication the type should be changed?
|
||||
Some(ReconstructedMessage {
|
||||
message: reply_msg,
|
||||
reply_surb: None,
|
||||
})
|
||||
reconstructed.push(ReconstructedMessage::new(message, msg.sender_tag));
|
||||
|
||||
(reply_surbs, false)
|
||||
}
|
||||
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
|
||||
trace!(
|
||||
"received additional {} reply surbs from {:?}!",
|
||||
reply_surbs.len(),
|
||||
msg.sender_tag
|
||||
);
|
||||
(reply_surbs, true)
|
||||
}
|
||||
RepliableMessageContent::Heartbeat {
|
||||
additional_reply_surbs,
|
||||
} => {
|
||||
error!("received a repliable heartbeat message - we don't know how to handle it yet (and we won't know until future PRs)");
|
||||
(additional_reply_surbs, false)
|
||||
}
|
||||
};
|
||||
|
||||
self.reply_controller_sender.send_additional_surbs(
|
||||
msg.sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
)
|
||||
}
|
||||
reconstructed
|
||||
}
|
||||
|
||||
fn handle_reconstructed_reply_messages(
|
||||
&mut self,
|
||||
msgs: Vec<ReplyMessage>,
|
||||
) -> Vec<ReconstructedMessage> {
|
||||
let mut reconstructed = Vec::new();
|
||||
for msg in msgs {
|
||||
match msg.content {
|
||||
ReplyMessageContent::Data { message } => reconstructed.push(message.into()),
|
||||
ReplyMessageContent::SurbRequest { recipient, amount } => {
|
||||
debug!("received request for {amount} additional reply SURBs from {recipient}");
|
||||
self.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
reconstructed
|
||||
}
|
||||
|
||||
async fn handle_reconstructed_messages(&mut self, msgs: Vec<NymMessage>) {
|
||||
if msgs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut plain_messages = Vec::new();
|
||||
let mut repliable_messages = Vec::new();
|
||||
let mut reply_messages = Vec::new();
|
||||
|
||||
for msg in msgs {
|
||||
match msg {
|
||||
NymMessage::Plain(plain) => plain_messages.push(plain),
|
||||
NymMessage::Repliable(repliable) => repliable_messages.push(repliable),
|
||||
NymMessage::Reply(reply) => reply_messages.push(reply),
|
||||
}
|
||||
}
|
||||
|
||||
let mut reconstructed_messages = self.handle_reconstructed_plain_messages(plain_messages);
|
||||
reconstructed_messages
|
||||
.append(&mut self.handle_reconstructed_repliable_messages(repliable_messages));
|
||||
reconstructed_messages
|
||||
.append(&mut self.handle_reconstructed_reply_messages(reply_messages));
|
||||
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
debug!(
|
||||
"Adding {:?} new messages to the buffer!",
|
||||
reconstructed_messages.len()
|
||||
);
|
||||
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(reconstructed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {err})");
|
||||
inner_guard.message_sender = None;
|
||||
inner_guard.messages.extend(err.into_inner());
|
||||
}
|
||||
} else {
|
||||
trace!("No sender available - buffering reconstructed messages");
|
||||
inner_guard.messages.extend(reconstructed_messages)
|
||||
}
|
||||
}
|
||||
|
||||
// this function doesn't really belong here...
|
||||
fn get_reply_key<'a>(
|
||||
&self,
|
||||
raw_message: &'a mut [u8],
|
||||
) -> Option<(SurbEncryptionKey, &'a mut [u8])> {
|
||||
let reply_surb_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
|
||||
if raw_message.len() < reply_surb_digest_size {
|
||||
return None;
|
||||
}
|
||||
|
||||
let possible_key_digest =
|
||||
EncryptionKeyDigest::clone_from_slice(&raw_message[..reply_surb_digest_size]);
|
||||
self.reply_key_storage
|
||||
.try_pop(possible_key_digest)
|
||||
.map(|reply_encryption_key| {
|
||||
(
|
||||
*reply_encryption_key,
|
||||
&mut raw_message[reply_surb_digest_size..],
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
@@ -217,69 +338,27 @@ impl ReceivedMessagesBuffer {
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
|
||||
// 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 {
|
||||
// TODO:
|
||||
// 1. make it nicer
|
||||
// 2. make it not feature-locked
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
{
|
||||
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)
|
||||
}
|
||||
// note: there's a possible information leakage associated with this check https://github.com/nymtech/nym/issues/296
|
||||
for mut msg in msgs {
|
||||
// check first `HasherOutputSize` bytes if they correspond to known encryption key
|
||||
// if yes - this is a reply message
|
||||
let completed_message =
|
||||
if let Some((reply_key, reply_message)) = self.get_reply_key(&mut msg) {
|
||||
inner_guard.process_received_reply(reply_message, reply_key)
|
||||
} else {
|
||||
// otherwise - it's a 'normal' message
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
inner_guard.process_received_regular_packet(msg)
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "reply-surb"))]
|
||||
if let Some(completed_message) = inner_guard.process_received_fragment(msg) {
|
||||
completed_messages.push(completed_message)
|
||||
if let Some(completed) = completed_message {
|
||||
info!("received {completed}");
|
||||
completed_messages.push(completed)
|
||||
}
|
||||
}
|
||||
|
||||
drop(inner_guard);
|
||||
|
||||
if !completed_messages.is_empty() {
|
||||
if let Some(sender) = &inner_guard.message_sender {
|
||||
trace!("Sending reconstructed messages to announced sender");
|
||||
if let Err(err) = sender.unbounded_send(completed_messages) {
|
||||
warn!("The reconstructed message receiver went offline without explicit notification (relevant error: - {:?})", err);
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
inner_guard.message_sender = None;
|
||||
drop(inner_guard);
|
||||
self.add_reconstructed_messages(err.into_inner()).await;
|
||||
}
|
||||
} else {
|
||||
// make sure to drop the lock to not deadlock
|
||||
// (it is required by `add_reconstructed_messages`)
|
||||
drop(inner_guard);
|
||||
trace!("No sender available - buffering reconstructed messages");
|
||||
self.add_reconstructed_messages(completed_messages).await;
|
||||
}
|
||||
self.handle_reconstructed_messages(completed_messages).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,21 +399,20 @@ impl RequestReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
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;
|
||||
},
|
||||
if let Some(message) = request {
|
||||
self.handle_message(message).await
|
||||
} else {
|
||||
log::trace!("RequestReceiver: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -342,16 +420,6 @@ impl RequestReceiver {
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("RequestReceiver: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
@@ -370,20 +438,19 @@ impl FragmentedMessageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) {
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
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) => {
|
||||
new_messages = self.mixnet_packet_receiver.next() => {
|
||||
if let Some(new_messages) = new_messages {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
}
|
||||
None => {
|
||||
} else {
|
||||
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
}
|
||||
}
|
||||
@@ -391,34 +458,25 @@ impl FragmentedMessageReceiver {
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
}
|
||||
|
||||
// todo: think whether this is still required
|
||||
#[allow(dead_code)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReceivedMessagesBufferController {
|
||||
pub(crate) struct ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver,
|
||||
request_receiver: RequestReceiver,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferController {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
#[cfg(feature = "reply-surb")] reply_key_storage: ReplyKeyStorage,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
#[cfg(feature = "reply-surb")]
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
@@ -430,7 +488,7 @@ impl ReceivedMessagesBufferController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(self, shutdown: task::ShutdownListener) {
|
||||
pub fn start_with_shutdown(self, shutdown: task::TaskClient) {
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
@@ -444,16 +502,4 @@ impl ReceivedMessagesBufferController {
|
||||
request_receiver.run_with_shutdown(shutdown).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start(self) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod reply_controller;
|
||||
pub mod reply_storage;
|
||||
@@ -0,0 +1,865 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use client_connections::{ConnectionId, TransmissionLane};
|
||||
use futures::channel::oneshot;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::client::helpers::new_interval_stream;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
pub(crate) use requests::{ReplyControllerMessage, ReplyControllerReceiver, ReplyControllerSender};
|
||||
|
||||
pub mod requests;
|
||||
|
||||
pub struct Config {
|
||||
min_surb_request_size: u32,
|
||||
max_surb_request_size: u32,
|
||||
maximum_allowed_reply_surb_request_size: u32,
|
||||
max_surb_waiting_period: Duration,
|
||||
max_reply_surb_age: Duration,
|
||||
max_reply_key_age: Duration,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(
|
||||
min_surb_request_size: u32,
|
||||
max_surb_request_size: u32,
|
||||
maximum_allowed_reply_surb_request_size: u32,
|
||||
max_surb_waiting_period: Duration,
|
||||
max_reply_surb_age: Duration,
|
||||
max_reply_key_age: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
min_surb_request_size,
|
||||
max_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size,
|
||||
max_surb_waiting_period,
|
||||
max_reply_surb_age,
|
||||
max_reply_key_age,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the purpose of this task:
|
||||
// - buffers split messages from input message listener if there were insufficient surbs to send them
|
||||
// - upon getting extra surbs, resends them
|
||||
// - so I guess it will handle all 'RepliableMessage' and requests from 'ReplyMessage'
|
||||
// - replies to "give additional surbs" requests
|
||||
// - will reply to future heartbeats
|
||||
|
||||
// TODO: this should be split into ingress and egress controllers
|
||||
// because currently its trying to perform two distinct jobs
|
||||
pub struct ReplyController<R> {
|
||||
config: Config,
|
||||
|
||||
// TODO: incorporate that field at some point
|
||||
// and use binomial distribution to determine the expected required number
|
||||
// of surbs required to send the message through
|
||||
// expected_reliability: f32,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
pending_replies: HashMap<AnonymousSenderTag, TransmissionBuffer<Fragment>>,
|
||||
|
||||
/// Retransmission packets that have already timed out and are waiting for additional reply SURBs
|
||||
/// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP.
|
||||
// TODO: when purging stale entries, we must take extra care to also purge all pending ACK data!!
|
||||
pending_retransmissions:
|
||||
HashMap<AnonymousSenderTag, BTreeMap<FragmentIdentifier, Weak<PendingAcknowledgement>>>,
|
||||
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
}
|
||||
|
||||
impl<R> ReplyController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
config: Config,
|
||||
message_handler: MessageHandler<R>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
) -> Self {
|
||||
ReplyController {
|
||||
config,
|
||||
request_receiver,
|
||||
pending_replies: HashMap::new(),
|
||||
pending_retransmissions: HashMap::new(),
|
||||
message_handler,
|
||||
full_reply_storage,
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_pending_replies<I: IntoIterator<Item = Fragment>>(
|
||||
&mut self,
|
||||
recipient: &AnonymousSenderTag,
|
||||
fragments: I,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
.store(&lane, fragments)
|
||||
}
|
||||
|
||||
fn re_insert_pending_replies(
|
||||
&mut self,
|
||||
recipient: &AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||
) {
|
||||
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
.store_multiple(fragments)
|
||||
}
|
||||
|
||||
fn re_insert_pending_retransmission(
|
||||
&mut self,
|
||||
recipient: &AnonymousSenderTag,
|
||||
data: Vec<Arc<PendingAcknowledgement>>,
|
||||
) {
|
||||
// the underlying entry MUST exist as we've just got data from there
|
||||
let map_entry = self
|
||||
.pending_retransmissions
|
||||
.get_mut(recipient)
|
||||
.expect("our pending retransmission entry is somehow gone!");
|
||||
|
||||
for pending in data {
|
||||
// if it's 0, we don't need to do anything - we just got that ack!
|
||||
if Arc::strong_count(&pending) > 1 {
|
||||
let id = pending.inner_fragment_identifier();
|
||||
let downgraded = Arc::downgrade(&pending);
|
||||
map_entry.insert(id, downgraded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_request_more_surbs(&self, target: &AnonymousSenderTag) -> bool {
|
||||
trace!("checking if we should request more surbs from {:?}", target);
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
.get(target)
|
||||
.map(|pending_queue| pending_queue.total_size())
|
||||
.unwrap_or_default();
|
||||
|
||||
let retransmission_queue = self
|
||||
.pending_retransmissions
|
||||
.get(target)
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.unwrap_or_default();
|
||||
|
||||
let total_queue = pending_queue_size + retransmission_queue;
|
||||
|
||||
// simple as that - there's absolutely nothing to retransmit
|
||||
if total_queue == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.available_surbs(target);
|
||||
let pending_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.pending_reception(target) as usize;
|
||||
let min_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.min_surb_threshold();
|
||||
let max_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.max_surb_threshold();
|
||||
|
||||
debug!("total queue size: {total_queue} = pending data {pending_queue_size} + pending retransmission {retransmission_queue}, available surbs: {available_surbs} pending surbs: {pending_surbs} threshold range: {min_surbs_threshold}..{max_surbs_threshold}");
|
||||
|
||||
(pending_surbs + available_surbs) < max_surbs_threshold
|
||||
&& (pending_surbs + available_surbs) < (total_queue + min_surbs_threshold)
|
||||
}
|
||||
|
||||
async fn handle_send_reply(
|
||||
&mut self,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
if !self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.contains_surbs_for(&recipient_tag)
|
||||
{
|
||||
warn!("received reply request for {:?} but we don't have any surbs stored for that recipient!", recipient_tag);
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("handling reply to {:?}", recipient_tag);
|
||||
let mut fragments = self.message_handler.split_reply_message(data);
|
||||
let total_size = fragments.len();
|
||||
trace!("This reply requires {:?} SURBs", total_size);
|
||||
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.available_surbs(&recipient_tag);
|
||||
let min_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.min_surb_threshold();
|
||||
|
||||
let max_to_send = if available_surbs > min_surbs_threshold {
|
||||
min(fragments.len(), available_surbs - min_surbs_threshold)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
if max_to_send > 0 {
|
||||
let (surbs, _surbs_left) = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&recipient_tag, max_to_send);
|
||||
|
||||
if let Some(reply_surbs) = surbs {
|
||||
let to_send = fragments.drain(..max_to_send).collect::<Vec<_>>();
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_reply_chunks_on_lane(
|
||||
recipient_tag,
|
||||
to_send.clone(),
|
||||
reply_surbs,
|
||||
lane,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let err = err.return_unused_surbs(
|
||||
self.full_reply_storage.surbs_storage_ref(),
|
||||
&recipient_tag,
|
||||
);
|
||||
warn!("failed to send reply to {recipient_tag}: {err}");
|
||||
self.insert_pending_replies(&recipient_tag, to_send, lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||
if !fragments.is_empty() {
|
||||
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
||||
}
|
||||
|
||||
if self.should_request_more_surbs(&recipient_tag) {
|
||||
self.request_reply_surbs_for_queue_clearing(recipient_tag)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_additional_reply_surbs(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
amount: u32,
|
||||
) -> Result<(), PreparationError> {
|
||||
let reply_surb = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surb_ignoring_threshold(&target)
|
||||
.and_then(|(reply_surb, _)| reply_surb)
|
||||
.ok_or(PreparationError::NotEnoughSurbs {
|
||||
available: 0,
|
||||
required: 1,
|
||||
})?;
|
||||
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_request_additional_reply_surbs(target, reply_surb, amount)
|
||||
.await
|
||||
{
|
||||
let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
warn!(
|
||||
"failed to request additional surbs from {:?} - {err}",
|
||||
target
|
||||
);
|
||||
return Err(err);
|
||||
} else {
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.increment_pending_reception(&target, amount);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_clear_pending_retransmission(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("trying to clear pending retransmission queue");
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.available_surbs(&target);
|
||||
let min_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.min_surb_threshold();
|
||||
|
||||
let max_to_clear = if available_surbs > min_surbs_threshold {
|
||||
available_surbs - min_surbs_threshold
|
||||
} else {
|
||||
trace!("we don't have enough surbs for retransmission queue clearing...");
|
||||
return;
|
||||
};
|
||||
trace!("we can clear up to {max_to_clear} entries");
|
||||
|
||||
let Some(pending) = self.pending_retransmissions.get_mut(&target) else {
|
||||
trace!("there are no pending retransmissions for {target}!");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut to_take = Vec::new();
|
||||
|
||||
while to_take.len() < max_to_clear {
|
||||
if let Some((_, data)) = pending.pop_first() {
|
||||
// no need to do anything if we failed to upgrade the reference,
|
||||
// it means we got the ack while the data was waiting in the queue
|
||||
if let Some(upgraded) = data.upgrade() {
|
||||
to_take.push(upgraded)
|
||||
}
|
||||
} else {
|
||||
// our map is empty!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if to_take.is_empty() {
|
||||
// no need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
let (surbs_for_reply, _) = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&target, to_take.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
return;
|
||||
};
|
||||
|
||||
let to_send_vec = to_take.iter().map(|ack| ack.fragment_data()).collect();
|
||||
|
||||
let prepared_fragments = match self
|
||||
.message_handler
|
||||
.prepare_reply_chunks_for_sending(to_send_vec, surbs_for_reply)
|
||||
.await
|
||||
{
|
||||
Ok(prepared) => prepared,
|
||||
Err(err) => {
|
||||
let err =
|
||||
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
|
||||
warn!(
|
||||
"failed to clear pending retransmission queue for {:?} - {err}",
|
||||
target
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// we can't fail at this point, so drop all references to acks so that timer updates wouldn't blow up
|
||||
drop(to_take);
|
||||
|
||||
self.message_handler
|
||||
.send_retransmission_reply_chunks(prepared_fragments, TransmissionLane::Retransmission)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn pop_at_most_pending_replies(
|
||||
&mut self,
|
||||
from: &AnonymousSenderTag,
|
||||
amount: usize,
|
||||
) -> Option<Vec<(TransmissionLane, Fragment)>> {
|
||||
// if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb
|
||||
let total = self.pending_replies.get(from)?.total_size();
|
||||
trace!("pending queue has {total} elements");
|
||||
if total == 0 {
|
||||
return None;
|
||||
}
|
||||
self.pending_replies
|
||||
.get_mut(from)?
|
||||
.pop_at_most_n_next_messages_at_random(amount)
|
||||
}
|
||||
|
||||
async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("trying to clear pending queue");
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.available_surbs(&target);
|
||||
let min_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.min_surb_threshold();
|
||||
|
||||
let max_to_clear = if available_surbs > min_surbs_threshold {
|
||||
available_surbs - min_surbs_threshold
|
||||
} else {
|
||||
trace!("we don't have enough surbs for queue clearing...");
|
||||
return;
|
||||
};
|
||||
trace!("we can clear up to {max_to_clear} entries");
|
||||
|
||||
// we're guaranteed to not get more entries than we have reply surbs for
|
||||
if let Some(to_send) = self.pop_at_most_pending_replies(&target, max_to_clear) {
|
||||
let to_send_clone = to_send.clone();
|
||||
|
||||
if to_send_clone.is_empty() {
|
||||
panic!(
|
||||
"please let the devs know if you ever see this message (reply_controller.rs)"
|
||||
);
|
||||
}
|
||||
|
||||
let (surbs_for_reply, _) = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&target, to_send_clone.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_reply_chunks(target, to_send_clone, surbs_for_reply)
|
||||
.await
|
||||
{
|
||||
let err =
|
||||
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
warn!("failed to clear pending queue for {:?} - {err}", target);
|
||||
}
|
||||
} else {
|
||||
trace!("the pending queue is empty");
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_received_surbs(
|
||||
&mut self,
|
||||
from: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) {
|
||||
trace!("handling received surbs");
|
||||
|
||||
// clear the requesting flag since we should have been asking for surbs
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.reset_surbs_last_received_at(&from);
|
||||
if from_surb_request {
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.decrement_pending_reception(&from, reply_surbs.len() as u32);
|
||||
}
|
||||
|
||||
// store received surbs
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.insert_surbs(&from, reply_surbs);
|
||||
|
||||
// use as many as we can for clearing pending retransmission queue
|
||||
self.try_clear_pending_retransmission(from).await;
|
||||
|
||||
// use as many as we can for clearing pending 'normal' queue
|
||||
self.try_clear_pending_queue(from).await;
|
||||
|
||||
// if we have to, request more
|
||||
if self.should_request_more_surbs(&from) {
|
||||
self.request_reply_surbs_for_queue_clearing(from).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_surb_request(&mut self, recipient: Recipient, mut amount: u32) {
|
||||
// 1. check whether we sent any surbs in the past to this recipient, otherwise
|
||||
// they have no business in asking for more
|
||||
if !self
|
||||
.full_reply_storage
|
||||
.tags_storage_ref()
|
||||
.exists(&recipient)
|
||||
{
|
||||
warn!("{recipient} asked us for reply SURBs even though we never sent them any anonymous messages before!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. check whether the requested amount is within sane range
|
||||
if amount > self.config.maximum_allowed_reply_surb_request_size {
|
||||
warn!("The requested reply surb amount is larger than our maximum allowed ({amount} > {}). Lowering it to a more sane value...", self.config.maximum_allowed_reply_surb_request_size);
|
||||
amount = self.config.maximum_allowed_reply_surb_request_size;
|
||||
}
|
||||
|
||||
// 3. construct and send the surbs away
|
||||
// (send them in smaller batches to make the experience a bit smoother
|
||||
let mut remaining = amount;
|
||||
while remaining > 0 {
|
||||
let to_send = min(remaining, 100);
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_additional_reply_surbs(recipient, to_send)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send additional surbs to {recipient} - {err}");
|
||||
} else {
|
||||
trace!("sent {to_send} reply SURBs to {recipient}");
|
||||
}
|
||||
|
||||
remaining -= to_send;
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_pending_ack(
|
||||
&mut self,
|
||||
recipient: AnonymousSenderTag,
|
||||
ack_ref: Arc<PendingAcknowledgement>,
|
||||
weak_ack_ref: Weak<PendingAcknowledgement>,
|
||||
) {
|
||||
let frag_id = ack_ref.inner_fragment_identifier();
|
||||
if let Some(existing) = self.pending_retransmissions.get_mut(&recipient) {
|
||||
if let Entry::Vacant(e) = existing.entry(frag_id) {
|
||||
e.insert(weak_ack_ref);
|
||||
} else {
|
||||
warn!("we're already trying to retransmit {frag_id}. We must be really behind in surbs!");
|
||||
}
|
||||
} else {
|
||||
let mut inner = BTreeMap::new();
|
||||
inner.insert(frag_id, weak_ack_ref);
|
||||
self.pending_retransmissions.insert(recipient, inner);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_reply_retransmission(
|
||||
&mut self,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surbs_request: bool,
|
||||
) {
|
||||
// seems we got the ack in the end
|
||||
let ack_ref = match timed_out_ack.upgrade() {
|
||||
Some(ack) => ack,
|
||||
None => {
|
||||
debug!("we received the ack for one of the reply packets as we were putting it in the retransmission queue");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// if this is retransmission for obtaining additional reply surbs,
|
||||
// we can dip below the storage threshold
|
||||
let (maybe_reply_surb, _) = if extra_surbs_request {
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surb_ignoring_threshold(&recipient_tag)
|
||||
} else {
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surb(&recipient_tag)
|
||||
}
|
||||
.expect("attempted to retransmit a packet to an unknown recipient - we shouldn't have sent the original packet in the first place!");
|
||||
|
||||
if let Some(reply_surb) = maybe_reply_surb {
|
||||
match self
|
||||
.message_handler
|
||||
.try_prepare_single_reply_chunk_for_sending(reply_surb, ack_ref.fragment_data())
|
||||
.await
|
||||
{
|
||||
Ok(prepared) => {
|
||||
// drop the ack ref so that controller would not panic on `UpdateTimer` if that task
|
||||
// got to handle the action before this function terminated (which is very much
|
||||
// possible if `forward_messages` takes a while)
|
||||
drop(ack_ref);
|
||||
|
||||
self.message_handler
|
||||
.update_ack_delay(prepared.fragment_identifier, prepared.total_delay);
|
||||
self.message_handler
|
||||
.forward_messages(vec![prepared.into()], TransmissionLane::Retransmission)
|
||||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
let err = err.return_unused_surbs(
|
||||
self.full_reply_storage.surbs_storage_ref(),
|
||||
&recipient_tag,
|
||||
);
|
||||
warn!("failed to prepare message for retransmission - {err}");
|
||||
// we buffer that packet and to try another day
|
||||
self.buffer_pending_ack(recipient_tag, ack_ref, timed_out_ack);
|
||||
|
||||
if self.should_request_more_surbs(&recipient_tag) {
|
||||
self.request_reply_surbs_for_queue_clearing(recipient_tag)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
self.buffer_pending_ack(recipient_tag, ack_ref, timed_out_ack);
|
||||
|
||||
if self.should_request_more_surbs(&recipient_tag) {
|
||||
self.request_reply_surbs_for_queue_clearing(recipient_tag)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// to be honest this doesn't make a lot of sense in the context of `connection_id`,
|
||||
// it should really be asked per tag
|
||||
fn handle_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
response_channel: oneshot::Sender<usize>,
|
||||
) {
|
||||
// TODO: if we ever have duplicate ids for different senders, it means our rng is super weak
|
||||
// thus I don't think we have to worry about it?
|
||||
let lane = TransmissionLane::ConnectionId(connection_id);
|
||||
for buf in self.pending_replies.values() {
|
||||
if let Some(length) = buf.lane_length(&lane) {
|
||||
if response_channel.send(length).is_err() {
|
||||
error!("the requester for lane queue length has dropped the response channel!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// make sure that if we didn't find that lane, we reply with 0
|
||||
if response_channel.send(0).is_err() {
|
||||
error!("the requester for lane queue length has dropped the response channel!")
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, request: ReplyControllerMessage) {
|
||||
match request {
|
||||
ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
} => {
|
||||
self.handle_reply_retransmission(recipient, timed_out_ack, extra_surb_request)
|
||||
.await
|
||||
}
|
||||
ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
} => self.handle_send_reply(recipient, message, lane).await,
|
||||
ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
} => {
|
||||
self.handle_received_surbs(sender_tag, reply_surbs, from_surb_request)
|
||||
.await
|
||||
}
|
||||
ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel,
|
||||
} => self.handle_lane_queue_length(connection_id, response_channel),
|
||||
ReplyControllerMessage::AdditionalSurbsRequest { recipient, amount } => {
|
||||
self.handle_surb_request(*recipient, amount).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: modify this method to more accurately determine the amount of surbs it needs to request
|
||||
// it should take into consideration the average latency, sending rate and queue size.
|
||||
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
|
||||
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("requesting surbs for queues clearing");
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
.get(&target)
|
||||
.map(|pending_queue| pending_queue.total_size())
|
||||
.unwrap_or_default();
|
||||
|
||||
let retransmission_queue = self
|
||||
.pending_retransmissions
|
||||
.get(&target)
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.unwrap_or_default();
|
||||
|
||||
let total_queue = (pending_queue_size + retransmission_queue) as u32;
|
||||
|
||||
if total_queue == 0 {
|
||||
trace!("the pending queues for {:?} are already empty", target);
|
||||
return;
|
||||
}
|
||||
|
||||
let request_size = min(
|
||||
self.config.max_surb_request_size,
|
||||
max(total_queue, self.config.min_surb_request_size),
|
||||
);
|
||||
|
||||
if let Err(err) = self
|
||||
.request_additional_reply_surbs(target, request_size)
|
||||
.await
|
||||
{
|
||||
warn!("failed to request additional surbs... - {err}")
|
||||
}
|
||||
}
|
||||
|
||||
async fn inspect_stale_entries(&mut self) {
|
||||
let mut to_request = Vec::new();
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
for (pending_reply_target, vals) in &self.pending_replies {
|
||||
if vals.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(last_received) = self.full_reply_storage.surbs_storage_ref().surbs_last_received_at(pending_reply_target) else {
|
||||
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.total_size());
|
||||
to_remove.push(*pending_reply_target);
|
||||
continue;
|
||||
};
|
||||
|
||||
// this should never ever happen (famous last words, eh?), but in case it DOES happen eventually
|
||||
// purge that malformed data
|
||||
let Ok(last_received_time) = OffsetDateTime::from_unix_timestamp(last_received) else {
|
||||
error!("somehow our stored timestamp ({last_received}) for surbs from {pending_reply_target} is corrupted!. Going to remove all the associated entries");
|
||||
to_remove.push(*pending_reply_target);
|
||||
continue;
|
||||
};
|
||||
|
||||
let diff = now - last_received_time;
|
||||
|
||||
if diff > self.config.max_surb_waiting_period {
|
||||
warn!("We haven't received any surbs in {:?} from {pending_reply_target}. Going to explicitly ask for more", diff);
|
||||
to_request.push(*pending_reply_target);
|
||||
}
|
||||
}
|
||||
|
||||
for pending_reply_target in to_request {
|
||||
self.request_reply_surbs_for_queue_clearing(pending_reply_target)
|
||||
.await;
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.reset_pending_reception(&pending_reply_target)
|
||||
}
|
||||
for to_remove in to_remove {
|
||||
self.pending_replies.remove(&to_remove);
|
||||
}
|
||||
}
|
||||
|
||||
async fn invalidate_old_data(&self) {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let mut to_remove_surbs = Vec::new();
|
||||
let mut to_remove_keys = Vec::new();
|
||||
for map_ref in self.full_reply_storage.surbs_storage_ref().as_raw_iter() {
|
||||
let (sender, received) = map_ref.pair();
|
||||
// TODO: handle the following edge case:
|
||||
// there's a malicious client sending us exactly one reply surb just before we should have invalidated
|
||||
// the data thus making us keep everything in memory
|
||||
// possible solution: keep timestamp PER reply surb (but that seems like an overkill)
|
||||
// but I doubt this is ever going to be a problem...
|
||||
// ...
|
||||
// However, if you're reading this message, it probably became a legit problem,
|
||||
// so I guess add timestamp per surb then? chop-chop.
|
||||
|
||||
let last_received = received.surbs_last_received_at();
|
||||
// this should never ever happen (famous last words, eh?), but in case it DOES happen eventually
|
||||
// purge that malformed data
|
||||
let Ok(last_received_time) = OffsetDateTime::from_unix_timestamp(last_received) else {
|
||||
error!("somehow our stored timestamp ({last_received}) for surbs from {sender} is corrupted!. Going to remove all the associated entries");
|
||||
to_remove_surbs.push(*sender);
|
||||
continue;
|
||||
};
|
||||
let diff = now - last_received_time;
|
||||
|
||||
if diff > self.config.max_reply_surb_age {
|
||||
info!("it's been {diff:?} since we last received any reply surb from {sender}. Going to remove all stored entries...");
|
||||
|
||||
to_remove_surbs.push(*sender);
|
||||
}
|
||||
}
|
||||
|
||||
for map_ref in self.full_reply_storage.key_storage_ref().as_raw_iter() {
|
||||
let (digest, reply_key) = map_ref.pair();
|
||||
|
||||
// this should never ever happen (famous last words, eh?), but in case it DOES happen eventually
|
||||
// purge that malformed data
|
||||
let Ok(sent_at) = OffsetDateTime::from_unix_timestamp(reply_key.sent_at_timestamp) else {
|
||||
error!("somehow our stored timestamp ({}) for one of our reply key is corrupted!. Going to remove all the entry", reply_key.sent_at_timestamp);
|
||||
to_remove_keys.push(*digest);
|
||||
continue;
|
||||
};
|
||||
|
||||
let diff = now - sent_at;
|
||||
|
||||
if diff > self.config.max_reply_key_age {
|
||||
debug!("it's been {diff:?} since we created this reply key. it's probably never going to get used, so we're going to purge it...");
|
||||
to_remove_keys.push(*digest);
|
||||
}
|
||||
}
|
||||
|
||||
for to_remove in to_remove_surbs {
|
||||
self.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.remove(&to_remove);
|
||||
}
|
||||
|
||||
for to_remove in to_remove_keys {
|
||||
self.full_reply_storage.key_storage().remove(to_remove)
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// async fn log_status(&self) {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started ReplyController with graceful shutdown support");
|
||||
|
||||
let polling_rate = Duration::from_secs(5);
|
||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
// this is in the order of hours/days so we don't have to poll it that often
|
||||
let polling_rate = Duration::from_secs(self.config.max_reply_surb_age.as_secs() / 10);
|
||||
let mut invalidation_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv_with_delay() => {
|
||||
log::trace!("ReplyController: Received shutdown");
|
||||
},
|
||||
req = self.request_receiver.next() => match req {
|
||||
Some(req) => self.handle_request(req).await,
|
||||
None => {
|
||||
log::trace!("ReplyController: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = stale_inspection.next() => {
|
||||
self.inspect_stale_entries().await
|
||||
},
|
||||
_ = invalidation_inspection.next() => {
|
||||
self.invalidate_old_data().await
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
log::debug!("ReplyController: Exiting");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use client_connections::{ConnectionId, TransmissionLane};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use log::error;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use std::sync::Weak;
|
||||
|
||||
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
|
||||
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
|
||||
ReplyControllerSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplyControllerSender {
|
||||
pub(crate) fn send_retransmission_data(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_reply(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs(
|
||||
&self,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel: response_tx,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!");
|
||||
|
||||
match response_rx.await {
|
||||
Ok(length) => length,
|
||||
Err(_) => {
|
||||
error!("The reply controller has dropped our response channel!");
|
||||
// TODO: should we panic here instead? this message implies something weird and unrecoverable has happened
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReplyControllerMessage {
|
||||
RetransmitReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
},
|
||||
|
||||
SendReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
AdditionalSurbs {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
},
|
||||
|
||||
// this one doesn't belong here either...
|
||||
LaneQueueLength {
|
||||
connection_id: ConnectionId,
|
||||
response_channel: oneshot::Sender<usize>,
|
||||
},
|
||||
|
||||
// Should this also be handled in here? it's technically a completely different side of the pipe
|
||||
// let's see how it works when combined, might split it before creating PR
|
||||
AdditionalSurbsRequest {
|
||||
recipient: Box<Recipient>,
|
||||
amount: u32,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::backend::Empty;
|
||||
use crate::client::replies::reply_storage::{CombinedReplyStorage, ReplyStorageBackend};
|
||||
use async_trait::async_trait;
|
||||
|
||||
// well, right now we don't have the browser storage : (
|
||||
// so we keep everything in memory
|
||||
pub struct Backend {
|
||||
empty: Empty,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new(min_surb_threshold: usize, max_surb_threshold: usize) -> Self {
|
||||
Backend {
|
||||
empty: Empty {
|
||||
min_surb_threshold,
|
||||
max_surb_threshold,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = <Empty as ReplyStorageBackend>::StorageError;
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
storage: &CombinedReplyStorage,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
self.empty.flush_surb_storage(storage).await
|
||||
}
|
||||
|
||||
async fn init_fresh(&mut self, fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> {
|
||||
self.empty.init_fresh(fresh).await
|
||||
}
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
self.empty.load_surb_storage().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StorageError {
|
||||
#[error("the provided database path doesn't have a filename defined")]
|
||||
DatabasePathWithoutFilename { provided_path: PathBuf },
|
||||
|
||||
#[error("failed to rename our databse file - {source}")]
|
||||
DatabaseRenameError {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to rename our old databse file - {source}")]
|
||||
DatabaseOldFileRemoveError {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error("failed to perform sqlx migration: {source}")]
|
||||
MigrationError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::migrate::MigrateError,
|
||||
},
|
||||
|
||||
#[error("failed to connect to the underlying connection pool: {source}")]
|
||||
DatabaseConnectionError {
|
||||
#[source]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
#[error("failed to run the SQL query: {source}")]
|
||||
QueryError {
|
||||
#[source]
|
||||
#[from]
|
||||
source: sqlx::error::Error,
|
||||
},
|
||||
|
||||
#[error("The loaded data is inconsistent - it seems that on the last shutdown the client hasn't finished the data flush. You may have to remove the entire storage manually")]
|
||||
IncompleteDataFlush,
|
||||
|
||||
#[error("data retrieved from the underlying storage is corrupted: {details}")]
|
||||
CorruptedData {
|
||||
details: String,
|
||||
// err: Option<Box<dyn std::error::Error>>
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::error::StorageError;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
};
|
||||
use log::{error, info};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct StorageManager {
|
||||
pub(crate) connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
// all SQL goes here
|
||||
impl StorageManager {
|
||||
pub(crate) async fn init<P: AsRef<Path>>(
|
||||
database_path: P,
|
||||
fresh: bool,
|
||||
) -> Result<Self, StorageError> {
|
||||
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(database_path)
|
||||
.create_if_missing(fresh);
|
||||
|
||||
opts.disable_statement_logging();
|
||||
|
||||
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
||||
Ok(pool) => pool,
|
||||
Err(err) => {
|
||||
error!("Failed to connect to SQLx database: {err}");
|
||||
return Err(StorageError::DatabaseConnectionError { source: err });
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
{
|
||||
error!("Failed to initialize SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
info!("Database migration finished!");
|
||||
Ok(StorageManager { connection_pool })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn status_table_exists(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT name FROM sqlite_master WHERE type='table' AND name='status'")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.is_some())
|
||||
}
|
||||
|
||||
pub(crate) async fn create_status_table(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("INSERT INTO status(flush_in_progress, previous_flush_timestamp, client_in_use) VALUES (0, 0, 1)")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_flush_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT flush_in_progress FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.flush_in_progress > 0)
|
||||
}
|
||||
|
||||
pub(crate) async fn set_previous_flush_timestamp(
|
||||
&self,
|
||||
timestamp: i64,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("UPDATE status SET previous_flush_timestamp = ?", timestamp)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_previous_flush_timestamp(&self) -> Result<i64, sqlx::Error> {
|
||||
sqlx::query!("SELECT previous_flush_timestamp FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.previous_flush_timestamp)
|
||||
}
|
||||
|
||||
pub(crate) async fn set_flush_status(&self, in_progress: bool) -> Result<(), sqlx::Error> {
|
||||
let in_progress_int = i64::from(in_progress);
|
||||
sqlx::query!("UPDATE status SET flush_in_progress = ?", in_progress_int)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_client_in_use_status(&self) -> Result<bool, sqlx::Error> {
|
||||
sqlx::query!("SELECT client_in_use FROM status;")
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
.map(|r| r.client_in_use > 0)
|
||||
}
|
||||
|
||||
pub(crate) async fn set_client_in_use_status(&self, in_use: bool) -> Result<(), sqlx::Error> {
|
||||
let in_use_int = i64::from(in_use);
|
||||
sqlx::query!("UPDATE status SET client_in_use = ?", in_use_int)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_all_tags(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM sender_tag;")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_tags(&self) -> Result<Vec<StoredSenderTag>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSenderTag, "SELECT * FROM sender_tag;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_tag(&self, stored_tag: StoredSenderTag) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO sender_tag(recipient, tag) VALUES (?, ?);
|
||||
"#,
|
||||
stored_tag.recipient,
|
||||
stored_tag.tag
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_all_reply_keys(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_key;")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_reply_keys(&self) -> Result<Vec<StoredReplyKey>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredReplyKey, "SELECT * FROM reply_key;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_reply_key(
|
||||
&self,
|
||||
stored_reply_key: StoredReplyKey,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO reply_key(key_digest, reply_key, sent_at_timestamp) VALUES (?, ?, ?);
|
||||
"#,
|
||||
stored_reply_key.key_digest,
|
||||
stored_reply_key.reply_key,
|
||||
stored_reply_key.sent_at_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_surb_senders(&self) -> Result<Vec<StoredSurbSender>, sqlx::Error> {
|
||||
sqlx::query_as!(StoredSurbSender, "SELECT * FROM reply_surb_sender;",)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_surb_sender(
|
||||
&self,
|
||||
stored_surb_sender: StoredSurbSender,
|
||||
) -> Result<i64, sqlx::Error> {
|
||||
let id = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO reply_surb_sender(tag, last_sent_timestamp) VALUES (?, ?);
|
||||
"#,
|
||||
stored_surb_sender.tag,
|
||||
stored_surb_sender.last_sent_timestamp
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?
|
||||
.last_insert_rowid();
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_reply_surbs(
|
||||
&self,
|
||||
sender_id: i64,
|
||||
) -> Result<Vec<StoredReplySurb>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
StoredReplySurb,
|
||||
"SELECT * FROM reply_surb WHERE reply_surb_sender_id = ?",
|
||||
sender_id
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_all_reply_surb_data(&self) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM reply_surb;")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!("DELETE FROM reply_surb_sender;")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_reply_surb(
|
||||
&self,
|
||||
stored_reply_surb: StoredReplySurb,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO reply_surb(reply_surb_sender_id, reply_surb) VALUES (?, ?);
|
||||
"#,
|
||||
stored_reply_surb.reply_surb_sender_id,
|
||||
stored_reply_surb.reply_surb
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_reply_surb_storage_metadata(
|
||||
&self,
|
||||
) -> Result<ReplySurbStorageMetadata, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
ReplySurbStorageMetadata,
|
||||
r#"
|
||||
SELECT min_reply_surb_threshold as "min_reply_surb_threshold: u32", max_reply_surb_threshold as "max_reply_surb_threshold: u32" FROM reply_surb_storage_metadata;
|
||||
"#,
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_reply_surb_storage_metadata(
|
||||
&self,
|
||||
metadata: ReplySurbStorageMetadata,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(r#"
|
||||
INSERT INTO reply_surb_storage_metadata(min_reply_surb_threshold, max_reply_surb_threshold)
|
||||
VALUES (?, ?);
|
||||
"#,
|
||||
metadata.min_reply_surb_threshold,
|
||||
metadata.max_reply_surb_threshold,
|
||||
).execute(&self.connection_pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::manager::StorageManager;
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::models::{
|
||||
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
|
||||
};
|
||||
use crate::client::replies::reply_storage::surb_storage::ReceivedReplySurbs;
|
||||
use crate::client::replies::reply_storage::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub use self::error::StorageError;
|
||||
|
||||
mod error;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Backend {
|
||||
temporary_old_path: Option<PathBuf>,
|
||||
database_path: PathBuf,
|
||||
manager: StorageManager,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
const OLD_EXTENSION: &'static str = "old";
|
||||
|
||||
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
provided_path: owned_path,
|
||||
});
|
||||
}
|
||||
|
||||
let backend = Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager: StorageManager::init(database_path, true).await?,
|
||||
};
|
||||
|
||||
backend.manager.create_status_table().await?;
|
||||
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
provided_path: owned_path,
|
||||
});
|
||||
}
|
||||
|
||||
let manager = StorageManager::init(database_path, false).await?;
|
||||
|
||||
// the database flush wasn't fully finished and thus the data is in inconsistent state
|
||||
// (we don't really know what's properly saved or what's not)
|
||||
if manager.get_flush_status().await? {
|
||||
return Err(StorageError::IncompleteDataFlush);
|
||||
}
|
||||
|
||||
let last_flush_timestamp = manager.get_previous_flush_timestamp().await?;
|
||||
if last_flush_timestamp == 0 {
|
||||
// either this client has been running since 1970 or the flush failed
|
||||
return Err(StorageError::IncompleteDataFlush);
|
||||
}
|
||||
|
||||
// the process has gone down without full graceful shutdown,
|
||||
// meaning the database doesn't contain valid data anymore
|
||||
// so we have to purge it
|
||||
if manager.get_client_in_use_status().await? {
|
||||
error!("the client hasn't undergone through graceful shutdown the last time it's gone down - we can't trust its reply surbs or stored encryption keys. They shall get purged");
|
||||
manager.delete_all_reply_surb_data().await?;
|
||||
manager.delete_all_reply_keys().await?;
|
||||
}
|
||||
|
||||
if let Err(err) = manager.get_reply_surb_storage_metadata().await {
|
||||
// we can't recover here, we HAVE TO initialise fresh (because we don't know correct starting metadata)
|
||||
error!("it seems the client has been shutdown gracefully - we're missing valid surb data dump. the existing database cannot be used");
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
let last_flush = match OffsetDateTime::from_unix_timestamp(last_flush_timestamp) {
|
||||
Ok(last_flush) => last_flush,
|
||||
Err(err) => {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!("failed to parse stored timestamp - {err}"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// in theory clients can use our reply surbs whenever they want, even a year in the future
|
||||
// (assuming no key rotation has happened)
|
||||
// but the way it's currently coded, everyone will purge old data
|
||||
let since_last_flush = OffsetDateTime::now_utc() - last_flush;
|
||||
if since_last_flush.whole_days() > 0 {
|
||||
info!("it's been over {} days and {} hours since we last used our data store. our reply surbs are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
|
||||
manager.delete_all_reply_surb_data().await?;
|
||||
}
|
||||
|
||||
if since_last_flush.whole_days() > 1 {
|
||||
info!("it's been over {} days and {} hours since we last used our data store. our reply keys are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
|
||||
manager.delete_all_reply_keys().await?;
|
||||
}
|
||||
|
||||
if since_last_flush.whole_days() > 2 {
|
||||
info!("it's been over {} days and {} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.", since_last_flush.whole_days(), since_last_flush.whole_hours());
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
temporary_old_path: None,
|
||||
database_path: owned_path,
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_pool(&mut self) {
|
||||
self.manager.connection_pool.close().await;
|
||||
}
|
||||
|
||||
async fn rotate(&mut self) -> Result<(), StorageError> {
|
||||
self.close_pool().await;
|
||||
|
||||
let new_extension = if let Some(existing_extension) =
|
||||
self.database_path.extension().and_then(|ext| ext.to_str())
|
||||
{
|
||||
format!("{existing_extension}.{}", Self::OLD_EXTENSION)
|
||||
} else {
|
||||
Self::OLD_EXTENSION.to_string()
|
||||
};
|
||||
|
||||
let mut temp_old = self.database_path.clone();
|
||||
temp_old.set_extension(new_extension);
|
||||
|
||||
fs::rename(&self.database_path, &temp_old)
|
||||
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
|
||||
self.manager = StorageManager::init(&self.database_path, true).await?;
|
||||
self.manager.create_status_table().await?;
|
||||
|
||||
self.temporary_old_path = Some(temp_old);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_old(&mut self) -> Result<(), StorageError> {
|
||||
if let Some(old_path) = self.temporary_old_path.take() {
|
||||
fs::remove_file(old_path)
|
||||
.map_err(|err| StorageError::DatabaseOldFileRemoveError { source: err })
|
||||
} else {
|
||||
warn!("the old database file doesn't seem to exist!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_storage_flush(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.set_flush_status(true).await?)
|
||||
}
|
||||
|
||||
async fn end_storage_flush(&self) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.set_previous_flush_timestamp(OffsetDateTime::now_utc().unix_timestamp())
|
||||
.await?;
|
||||
Ok(self.manager.set_flush_status(false).await?)
|
||||
}
|
||||
|
||||
async fn start_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.set_client_in_use_status(true).await?)
|
||||
}
|
||||
|
||||
async fn stop_client_use(&self) -> Result<(), StorageError> {
|
||||
Ok(self.manager.set_client_in_use_status(false).await?)
|
||||
}
|
||||
|
||||
async fn get_stored_tags(&self) -> Result<UsedSenderTags, StorageError> {
|
||||
let stored = self.manager.get_tags().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
let raw = stored
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(UsedSenderTags::from_raw(raw))
|
||||
}
|
||||
|
||||
async fn dump_sender_tags(&self, tags: &UsedSenderTags) -> Result<(), StorageError> {
|
||||
for map_ref in tags.as_raw_iter() {
|
||||
let (recipient, tag) = map_ref.pair();
|
||||
self.manager
|
||||
.insert_tag(StoredSenderTag::new(*recipient, *tag))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_stored_reply_keys(&self) -> Result<SentReplyKeys, StorageError> {
|
||||
let stored = self.manager.get_reply_keys().await?;
|
||||
|
||||
// stop at the first instance of corruption. if even a single entry is malformed,
|
||||
// something weird has happened and we can't trust the rest of the data
|
||||
let raw = stored
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(SentReplyKeys::from_raw(raw))
|
||||
}
|
||||
|
||||
async fn dump_sender_reply_keys(&self, reply_keys: &SentReplyKeys) -> Result<(), StorageError> {
|
||||
for map_ref in reply_keys.as_raw_iter() {
|
||||
let (digest, key) = map_ref.pair();
|
||||
self.manager
|
||||
.insert_reply_key(StoredReplyKey::new(*digest, *key))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_stored_reply_surbs(&self) -> Result<ReceivedReplySurbsMap, StorageError> {
|
||||
let surb_senders = self.manager.get_surb_senders().await?;
|
||||
|
||||
let metadata = self.get_reply_surb_storage_metadata().await?;
|
||||
let mut received_surbs = Vec::with_capacity(surb_senders.len());
|
||||
for sender in surb_senders {
|
||||
let sender_id = sender.id;
|
||||
let (sender_tag, surbs_last_received_at_timestamp): (AnonymousSenderTag, i64) =
|
||||
sender.try_into()?;
|
||||
let stored_surbs = self
|
||||
.manager
|
||||
.get_reply_surbs(sender_id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|raw| raw.try_into())
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
received_surbs.push((
|
||||
sender_tag,
|
||||
ReceivedReplySurbs::new_retrieved(stored_surbs, surbs_last_received_at_timestamp),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(ReceivedReplySurbsMap::from_raw(
|
||||
metadata.min_reply_surb_threshold as usize,
|
||||
metadata.max_reply_surb_threshold as usize,
|
||||
received_surbs,
|
||||
))
|
||||
}
|
||||
|
||||
async fn dump_reply_surbs(
|
||||
&self,
|
||||
reply_surbs: &ReceivedReplySurbsMap,
|
||||
) -> Result<(), StorageError> {
|
||||
for map_ref in reply_surbs.as_raw_iter() {
|
||||
let (tag, received_surbs) = map_ref.pair();
|
||||
let sender_id = self
|
||||
.manager
|
||||
.insert_surb_sender(StoredSurbSender::new(
|
||||
*tag,
|
||||
received_surbs.surbs_last_received_at(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
for reply_surb in received_surbs.surbs_ref() {
|
||||
self.manager
|
||||
.insert_reply_surb(StoredReplySurb::new(sender_id, reply_surb))
|
||||
.await?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_reply_surb_storage_metadata(
|
||||
&self,
|
||||
) -> Result<ReplySurbStorageMetadata, StorageError> {
|
||||
self.manager
|
||||
.get_reply_surb_storage_metadata()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn dump_reply_surb_storage_metadata(
|
||||
&self,
|
||||
reply_surbs: &ReceivedReplySurbsMap,
|
||||
) -> Result<(), StorageError> {
|
||||
self.manager
|
||||
.insert_reply_surb_storage_metadata(ReplySurbStorageMetadata::new(
|
||||
reply_surbs.min_surb_threshold(),
|
||||
reply_surbs.max_surb_threshold(),
|
||||
))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReplyStorageBackend for Backend {
|
||||
type StorageError = error::StorageError;
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
self.start_client_use().await
|
||||
}
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
storage: &CombinedReplyStorage,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
// close all connections (there should be none! and rename the file to contain .old extension)
|
||||
self.rotate().await?;
|
||||
self.start_storage_flush().await?;
|
||||
|
||||
self.dump_sender_tags(storage.tags_storage_ref()).await?;
|
||||
self.dump_sender_reply_keys(storage.key_storage_ref())
|
||||
.await?;
|
||||
let surbs_ref = storage.surbs_storage_ref();
|
||||
self.dump_reply_surb_storage_metadata(surbs_ref).await?;
|
||||
self.dump_reply_surbs(surbs_ref).await?;
|
||||
|
||||
self.remove_old()?;
|
||||
self.end_storage_flush().await
|
||||
}
|
||||
|
||||
async fn init_fresh(&mut self, fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError> {
|
||||
// for now nothing more to do apart from dumping the metadata
|
||||
self.dump_reply_surb_storage_metadata(fresh.surbs_storage_ref())
|
||||
.await
|
||||
}
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
let reply_keys = self.get_stored_reply_keys().await?;
|
||||
let tags = self.get_stored_tags().await?;
|
||||
let reply_surbs = self.get_stored_reply_surbs().await?;
|
||||
|
||||
Ok(CombinedReplyStorage::load(reply_keys, reply_surbs, tags))
|
||||
}
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
self.stop_client_use().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::backend::fs_backend::error::StorageError;
|
||||
use crate::client::replies::reply_storage::key_storage::UsedReplyKey;
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use crypto::Digest;
|
||||
use nymsphinx::addressing::clients::{Recipient, RecipientBytes};
|
||||
use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest;
|
||||
use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
|
||||
use nymsphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey, SurbEncryptionKeySize};
|
||||
use nymsphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct StoredSenderTag {
|
||||
pub(crate) recipient: Vec<u8>,
|
||||
pub(crate) tag: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StoredSenderTag {
|
||||
pub(crate) fn new(recipient: RecipientBytes, tag: AnonymousSenderTag) -> StoredSenderTag {
|
||||
StoredSenderTag {
|
||||
recipient: recipient.to_vec(),
|
||||
tag: tag.to_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StoredSenderTag> for (RecipientBytes, AnonymousSenderTag) {
|
||||
type Error = StorageError;
|
||||
|
||||
fn try_from(value: StoredSenderTag) -> Result<Self, Self::Error> {
|
||||
let recipient_len = value.recipient.len();
|
||||
let Ok(recipient_bytes) = value.recipient.try_into() else {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!(
|
||||
"the retrieved recipient has length of {recipient_len} while {} was expected",
|
||||
Recipient::LEN
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
let tag_len = value.tag.len();
|
||||
let Ok(sender_tag_bytes) = value.tag.try_into() else {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!(
|
||||
"the retrieved sender tag has length of {tag_len} while {} was expected",
|
||||
SENDER_TAG_SIZE
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
Ok((
|
||||
recipient_bytes,
|
||||
AnonymousSenderTag::from_bytes(sender_tag_bytes),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct StoredReplyKey {
|
||||
pub(crate) key_digest: Vec<u8>,
|
||||
pub(crate) reply_key: Vec<u8>,
|
||||
pub(crate) sent_at_timestamp: i64,
|
||||
}
|
||||
|
||||
impl StoredReplyKey {
|
||||
pub(crate) fn new(key_digest: EncryptionKeyDigest, reply_key: UsedReplyKey) -> StoredReplyKey {
|
||||
StoredReplyKey {
|
||||
key_digest: key_digest.to_vec(),
|
||||
reply_key: (*reply_key).to_bytes(),
|
||||
sent_at_timestamp: reply_key.sent_at_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StoredReplyKey> for (EncryptionKeyDigest, UsedReplyKey) {
|
||||
type Error = StorageError;
|
||||
|
||||
fn try_from(value: StoredReplyKey) -> Result<Self, Self::Error> {
|
||||
let expected_reply_key_digest_size = ReplySurbKeyDigestAlgorithm::output_size();
|
||||
let reply_key_digest_size = value.key_digest.len();
|
||||
|
||||
let Some(digest) = EncryptionKeyDigest::from_exact_iter(value.key_digest) else {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!(
|
||||
"the reply surb digest has length of {reply_key_digest_size} while {expected_reply_key_digest_size} was expected",
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
let reply_key_len = value.reply_key.len();
|
||||
let Ok(reply_key) = SurbEncryptionKey::try_from_bytes(&value.reply_key) else {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!(
|
||||
"the reply key has length of {reply_key_len} while {} was expected",
|
||||
SurbEncryptionKeySize::USIZE
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
Ok((
|
||||
digest,
|
||||
UsedReplyKey::new(reply_key, value.sent_at_timestamp),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StoredSurbSender {
|
||||
pub(crate) id: i64,
|
||||
pub(crate) tag: Vec<u8>,
|
||||
pub(crate) last_sent_timestamp: i64,
|
||||
}
|
||||
|
||||
impl StoredSurbSender {
|
||||
pub(crate) fn new(tag: AnonymousSenderTag, last_sent_timestamp: i64) -> Self {
|
||||
StoredSurbSender {
|
||||
// for the purposes of STORING data,
|
||||
// we ignore that field anyway
|
||||
id: 0,
|
||||
tag: tag.to_bytes().to_vec(),
|
||||
last_sent_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StoredSurbSender> for (AnonymousSenderTag, i64) {
|
||||
type Error = StorageError;
|
||||
|
||||
fn try_from(value: StoredSurbSender) -> Result<Self, Self::Error> {
|
||||
let tag_len = value.tag.len();
|
||||
let Ok(sender_tag_bytes) = value.tag.try_into() else {
|
||||
return Err(StorageError::CorruptedData {
|
||||
details: format!(
|
||||
"the retrieved sender tag has length of {tag_len} while {} was expected",
|
||||
SENDER_TAG_SIZE
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
Ok((
|
||||
AnonymousSenderTag::from_bytes(sender_tag_bytes),
|
||||
value.last_sent_timestamp,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StoredReplySurb {
|
||||
pub(crate) reply_surb_sender_id: i64,
|
||||
pub(crate) reply_surb: Vec<u8>,
|
||||
}
|
||||
|
||||
impl StoredReplySurb {
|
||||
pub(crate) fn new(reply_surb_sender_id: i64, reply_surb: &ReplySurb) -> Self {
|
||||
StoredReplySurb {
|
||||
reply_surb_sender_id,
|
||||
reply_surb: reply_surb.to_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<StoredReplySurb> for ReplySurb {
|
||||
type Error = StorageError;
|
||||
|
||||
fn try_from(value: StoredReplySurb) -> Result<Self, Self::Error> {
|
||||
ReplySurb::from_bytes(&value.reply_surb).map_err(|err| StorageError::CorruptedData {
|
||||
details: format!("failed to recover the reply surb: {err}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct ReplySurbStorageMetadata {
|
||||
pub(crate) min_reply_surb_threshold: u32,
|
||||
pub(crate) max_reply_surb_threshold: u32,
|
||||
}
|
||||
|
||||
impl ReplySurbStorageMetadata {
|
||||
pub(crate) fn new(min_reply_surb_threshold: usize, max_reply_surb_threshold: usize) -> Self {
|
||||
Self {
|
||||
min_reply_surb_threshold: min_reply_surb_threshold as u32,
|
||||
max_reply_surb_threshold: max_reply_surb_threshold as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod browser_backend;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub mod fs_backend;
|
||||
|
||||
// #[cfg(all(test, feature = "std"))]
|
||||
// third case: node with actual filesystem
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("no information provided")]
|
||||
pub struct UndefinedError;
|
||||
|
||||
pub struct Empty {
|
||||
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
|
||||
min_surb_threshold: usize,
|
||||
max_surb_threshold: usize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ReplyStorageBackend for Empty {
|
||||
type StorageError = UndefinedError;
|
||||
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
_storage: &CombinedReplyStorage,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn init_fresh(
|
||||
&mut self,
|
||||
_fresh: &CombinedReplyStorage,
|
||||
) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError> {
|
||||
Ok(CombinedReplyStorage::new(
|
||||
self.min_surb_threshold,
|
||||
self.max_surb_threshold,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ReplyStorageBackend: Sized {
|
||||
type StorageError: Error + 'static;
|
||||
|
||||
async fn start_storage_session(&self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// reply keys and surbs would need additional field set when data is loaded
|
||||
// so if there's some failure, we'd trash it all
|
||||
async fn flush_surb_storage(
|
||||
&mut self,
|
||||
storage: &CombinedReplyStorage,
|
||||
) -> Result<(), Self::StorageError>;
|
||||
|
||||
/// The purpose of this call is to save any metadata that might be present.
|
||||
/// (such as surb thresholds)
|
||||
async fn init_fresh(&mut self, fresh: &CombinedReplyStorage) -> Result<(), Self::StorageError>;
|
||||
|
||||
async fn load_surb_storage(&self) -> Result<CombinedReplyStorage, Self::StorageError>;
|
||||
|
||||
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CombinedReplyStorage {
|
||||
sent_reply_keys: SentReplyKeys,
|
||||
received_reply_surbs: ReceivedReplySurbsMap,
|
||||
used_tags: UsedSenderTags,
|
||||
}
|
||||
|
||||
impl CombinedReplyStorage {
|
||||
pub fn new(min_surb_threshold: usize, max_surb_threshold: usize) -> CombinedReplyStorage {
|
||||
CombinedReplyStorage {
|
||||
sent_reply_keys: SentReplyKeys::new(),
|
||||
received_reply_surbs: ReceivedReplySurbsMap::new(
|
||||
min_surb_threshold,
|
||||
max_surb_threshold,
|
||||
),
|
||||
used_tags: UsedSenderTags::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
sent_reply_keys: SentReplyKeys,
|
||||
received_reply_surbs: ReceivedReplySurbsMap,
|
||||
used_tags: UsedSenderTags,
|
||||
) -> Self {
|
||||
CombinedReplyStorage {
|
||||
sent_reply_keys,
|
||||
received_reply_surbs,
|
||||
used_tags,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_storage(&self) -> SentReplyKeys {
|
||||
self.sent_reply_keys.clone()
|
||||
}
|
||||
|
||||
pub fn surbs_storage(&self) -> ReceivedReplySurbsMap {
|
||||
self.received_reply_surbs.clone()
|
||||
}
|
||||
|
||||
pub fn tags_storage(&self) -> UsedSenderTags {
|
||||
self.used_tags.clone()
|
||||
}
|
||||
|
||||
pub fn key_storage_ref(&self) -> &SentReplyKeys {
|
||||
&self.sent_reply_keys
|
||||
}
|
||||
|
||||
pub fn surbs_storage_ref(&self) -> &ReceivedReplySurbsMap {
|
||||
&self.received_reply_surbs
|
||||
}
|
||||
|
||||
pub fn tags_storage_ref(&self) -> &UsedSenderTags {
|
||||
&self.used_tags
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use dashmap::iter::Iter;
|
||||
use dashmap::DashMap;
|
||||
use nymsphinx::anonymous_replies::encryption_key::EncryptionKeyDigest;
|
||||
use nymsphinx::anonymous_replies::SurbEncryptionKey;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SentReplyKeys {
|
||||
inner: Arc<SentReplyKeysInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SentReplyKeysInner {
|
||||
data: DashMap<EncryptionKeyDigest, UsedReplyKey>,
|
||||
}
|
||||
|
||||
impl SentReplyKeys {
|
||||
pub(crate) fn new() -> SentReplyKeys {
|
||||
SentReplyKeys {
|
||||
inner: Arc::new(SentReplyKeysInner {
|
||||
data: DashMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn from_raw(raw: Vec<(EncryptionKeyDigest, UsedReplyKey)>) -> SentReplyKeys {
|
||||
SentReplyKeys {
|
||||
inner: Arc::new(SentReplyKeysInner {
|
||||
data: raw.into_iter().collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_raw_iter(&self) -> Iter<'_, EncryptionKeyDigest, UsedReplyKey> {
|
||||
self.inner.data.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn insert_multiple(&self, keys: Vec<SurbEncryptionKey>) {
|
||||
let now = OffsetDateTime::now_utc().unix_timestamp();
|
||||
for key in keys {
|
||||
self.insert(UsedReplyKey::new(key, now))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&self, key: UsedReplyKey) {
|
||||
self.inner.data.insert(key.compute_digest(), key);
|
||||
}
|
||||
|
||||
pub(crate) fn try_pop(&self, digest: EncryptionKeyDigest) -> Option<UsedReplyKey> {
|
||||
self.inner.data.remove(&digest).map(|(_k, v)| v)
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, digest: EncryptionKeyDigest) {
|
||||
self.inner.data.remove(&digest);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct UsedReplyKey {
|
||||
key: SurbEncryptionKey,
|
||||
// the purpose of this field is to perform invalidation at relatively very long intervals
|
||||
pub(crate) sent_at_timestamp: i64,
|
||||
}
|
||||
|
||||
impl UsedReplyKey {
|
||||
pub(crate) fn new(key: SurbEncryptionKey, sent_at_timestamp: i64) -> Self {
|
||||
UsedReplyKey {
|
||||
key,
|
||||
sent_at_timestamp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UsedReplyKey {
|
||||
type Target = SurbEncryptionKey;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.key
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use crate::client::replies::reply_storage::combined::CombinedReplyStorage;
|
||||
pub use crate::client::replies::reply_storage::key_storage::SentReplyKeys;
|
||||
pub use crate::client::replies::reply_storage::surb_storage::ReceivedReplySurbsMap;
|
||||
pub use crate::client::replies::reply_storage::tag_storage::UsedSenderTags;
|
||||
pub use backend::*;
|
||||
|
||||
mod backend;
|
||||
mod combined;
|
||||
mod key_storage;
|
||||
mod surb_storage;
|
||||
mod tag_storage;
|
||||
|
||||
// only really exists to get information about shutdown and save data to the backing storage
|
||||
pub struct PersistentReplyStorage<T = backend::Empty>
|
||||
where
|
||||
T: ReplyStorageBackend,
|
||||
{
|
||||
backend: T,
|
||||
}
|
||||
|
||||
impl<T> PersistentReplyStorage<T>
|
||||
where
|
||||
T: ReplyStorageBackend + Send + Sync,
|
||||
{
|
||||
pub fn new(backend: T) -> Self {
|
||||
PersistentReplyStorage { backend }
|
||||
}
|
||||
|
||||
pub async fn load_state_from_backend(&self) -> Result<CombinedReplyStorage, T::StorageError> {
|
||||
self.backend.load_surb_storage().await
|
||||
}
|
||||
|
||||
// this will have to get enabled after merging develop
|
||||
pub async fn flush_on_shutdown(
|
||||
mut self,
|
||||
mem_state: CombinedReplyStorage,
|
||||
mut shutdown: task::TaskClient,
|
||||
) {
|
||||
use log::{debug, error, info, warn};
|
||||
|
||||
debug!("Started PersistentReplyStorage");
|
||||
if let Err(err) = self.backend.start_storage_session().await {
|
||||
error!("failed to start the storage session - {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
shutdown.recv().await;
|
||||
|
||||
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
||||
warn!("you MUST NOT forcefully shutdown now or you risk data corruption!");
|
||||
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
||||
error!("failed to flush our reply-related data to the persistent storage: {err}")
|
||||
} else {
|
||||
info!("Data flush is complete")
|
||||
}
|
||||
|
||||
if let Err(err) = self.backend.stop_storage_session().await {
|
||||
error!("failed to properly stop the storage session - {err}. We might not be able to smoothly restore it")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use dashmap::iter::Iter;
|
||||
use dashmap::DashMap;
|
||||
use log::trace;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReceivedReplySurbsMap {
|
||||
inner: Arc<ReceivedReplySurbsMapInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ReceivedReplySurbsMapInner {
|
||||
data: DashMap<AnonymousSenderTag, ReceivedReplySurbs>,
|
||||
|
||||
// the minimum amount of surbs that have to be kept in storage for requests for more surbs
|
||||
min_surb_threshold: AtomicUsize,
|
||||
|
||||
// the maximum amount of surbs that we want to keep in storage so that we don't over-request them
|
||||
max_surb_threshold: AtomicUsize,
|
||||
}
|
||||
|
||||
impl ReceivedReplySurbsMap {
|
||||
pub(crate) fn new(
|
||||
min_surb_threshold: usize,
|
||||
max_surb_threshold: usize,
|
||||
) -> ReceivedReplySurbsMap {
|
||||
ReceivedReplySurbsMap {
|
||||
inner: Arc::new(ReceivedReplySurbsMapInner {
|
||||
data: DashMap::new(),
|
||||
min_surb_threshold: AtomicUsize::new(min_surb_threshold),
|
||||
max_surb_threshold: AtomicUsize::new(max_surb_threshold),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn from_raw(
|
||||
min_surb_threshold: usize,
|
||||
max_surb_threshold: usize,
|
||||
raw: Vec<(AnonymousSenderTag, ReceivedReplySurbs)>,
|
||||
) -> ReceivedReplySurbsMap {
|
||||
ReceivedReplySurbsMap {
|
||||
inner: Arc::new(ReceivedReplySurbsMapInner {
|
||||
data: raw.into_iter().collect(),
|
||||
min_surb_threshold: AtomicUsize::new(min_surb_threshold),
|
||||
max_surb_threshold: AtomicUsize::new(max_surb_threshold),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_raw_iter(&self) -> Iter<'_, AnonymousSenderTag, ReceivedReplySurbs> {
|
||||
self.inner.data.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&self, target: &AnonymousSenderTag) {
|
||||
self.inner.data.remove(target);
|
||||
}
|
||||
|
||||
pub(crate) fn reset_surbs_last_received_at(&self, target: &AnonymousSenderTag) {
|
||||
if let Some(mut entry) = self.inner.data.get_mut(target) {
|
||||
entry.surbs_last_received_at_timestamp = OffsetDateTime::now_utc().unix_timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn surbs_last_received_at(&self, target: &AnonymousSenderTag) -> Option<i64> {
|
||||
self.inner
|
||||
.data
|
||||
.get(target)
|
||||
.map(|e| e.surbs_last_received_at())
|
||||
}
|
||||
|
||||
pub(crate) fn pending_reception(&self, target: &AnonymousSenderTag) -> u32 {
|
||||
self.inner
|
||||
.data
|
||||
.get(target)
|
||||
.map(|e| e.pending_reception())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn increment_pending_reception(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
amount: u32,
|
||||
) -> Option<u32> {
|
||||
self.inner
|
||||
.data
|
||||
.get_mut(target)
|
||||
.map(|mut e| e.increment_pending_reception(amount))
|
||||
}
|
||||
|
||||
pub(crate) fn decrement_pending_reception(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
amount: u32,
|
||||
) -> Option<u32> {
|
||||
self.inner
|
||||
.data
|
||||
.get_mut(target)
|
||||
.map(|mut e| e.decrement_pending_reception(amount))
|
||||
}
|
||||
|
||||
pub(crate) fn reset_pending_reception(&self, target: &AnonymousSenderTag) {
|
||||
if let Some(mut e) = self.inner.data.get_mut(target) {
|
||||
e.reset_pending_reception()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn min_surb_threshold(&self) -> usize {
|
||||
self.inner.min_surb_threshold.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn max_surb_threshold(&self) -> usize {
|
||||
self.inner.max_surb_threshold.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub(crate) fn available_surbs(&self, target: &AnonymousSenderTag) -> usize {
|
||||
self.inner
|
||||
.data
|
||||
.get(target)
|
||||
.map(|entry| entry.items_left())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn contains_surbs_for(&self, target: &AnonymousSenderTag) -> bool {
|
||||
self.inner.data.contains_key(target)
|
||||
}
|
||||
|
||||
pub(crate) fn get_reply_surbs(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
amount: usize,
|
||||
) -> (Option<Vec<ReplySurb>>, usize) {
|
||||
if let Some(mut entry) = self.inner.data.get_mut(target) {
|
||||
let surbs_left = entry.items_left();
|
||||
if surbs_left < self.min_surb_threshold() + amount {
|
||||
(None, surbs_left)
|
||||
} else {
|
||||
entry.get_reply_surbs(amount)
|
||||
}
|
||||
} else {
|
||||
(None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_reply_surb_ignoring_threshold(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
) -> Option<(Option<ReplySurb>, usize)> {
|
||||
self.inner
|
||||
.data
|
||||
.get_mut(target)
|
||||
.map(|mut s| s.get_reply_surb())
|
||||
}
|
||||
|
||||
pub(crate) fn get_reply_surb(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
) -> Option<(Option<ReplySurb>, usize)> {
|
||||
self.inner.data.get_mut(target).map(|mut entry| {
|
||||
let surbs_left = entry.items_left();
|
||||
if surbs_left < self.min_surb_threshold() {
|
||||
(None, surbs_left)
|
||||
} else {
|
||||
entry.get_reply_surb()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn insert_surbs<I: IntoIterator<Item = ReplySurb>>(
|
||||
&self,
|
||||
target: &AnonymousSenderTag,
|
||||
surbs: I,
|
||||
) {
|
||||
if let Some(mut existing_data) = self.inner.data.get_mut(target) {
|
||||
existing_data.insert_reply_surbs(surbs)
|
||||
} else {
|
||||
let new_entry = ReceivedReplySurbs::new(surbs.into_iter().collect());
|
||||
self.inner.data.insert(*target, new_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReceivedReplySurbs {
|
||||
// in the future we'd probably want to put extra data here to indicate when the SURBs got received
|
||||
// so we could invalidate entries from the previous key rotations
|
||||
data: VecDeque<ReplySurb>,
|
||||
|
||||
pending_reception: u32,
|
||||
surbs_last_received_at_timestamp: i64,
|
||||
}
|
||||
|
||||
impl ReceivedReplySurbs {
|
||||
fn new(initial_surbs: VecDeque<ReplySurb>) -> Self {
|
||||
ReceivedReplySurbs {
|
||||
data: initial_surbs,
|
||||
pending_reception: 0,
|
||||
surbs_last_received_at_timestamp: OffsetDateTime::now_utc().unix_timestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn new_retrieved(
|
||||
surbs: Vec<ReplySurb>,
|
||||
surbs_last_received_at_timestamp: i64,
|
||||
) -> ReceivedReplySurbs {
|
||||
ReceivedReplySurbs {
|
||||
data: surbs.into(),
|
||||
pending_reception: 0,
|
||||
surbs_last_received_at_timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn surbs_ref(&self) -> &VecDeque<ReplySurb> {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub(crate) fn surbs_last_received_at(&self) -> i64 {
|
||||
self.surbs_last_received_at_timestamp
|
||||
}
|
||||
|
||||
pub(crate) fn pending_reception(&self) -> u32 {
|
||||
self.pending_reception
|
||||
}
|
||||
|
||||
pub(crate) fn increment_pending_reception(&mut self, amount: u32) -> u32 {
|
||||
self.pending_reception += amount;
|
||||
self.pending_reception
|
||||
}
|
||||
|
||||
pub(crate) fn decrement_pending_reception(&mut self, amount: u32) -> u32 {
|
||||
self.pending_reception = self.pending_reception.saturating_sub(amount);
|
||||
self.pending_reception
|
||||
}
|
||||
|
||||
pub(crate) fn reset_pending_reception(&mut self) {
|
||||
self.pending_reception = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn get_reply_surbs(&mut self, amount: usize) -> (Option<Vec<ReplySurb>>, usize) {
|
||||
if self.items_left() < amount {
|
||||
(None, self.items_left())
|
||||
} else {
|
||||
let surbs = self.data.drain(..amount).collect();
|
||||
(Some(surbs), self.items_left())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_reply_surb(&mut self) -> (Option<ReplySurb>, usize) {
|
||||
(self.pop_surb(), self.items_left())
|
||||
}
|
||||
|
||||
fn pop_surb(&mut self) -> Option<ReplySurb> {
|
||||
self.data.pop_front()
|
||||
}
|
||||
|
||||
fn items_left(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
// realistically we're always going to be getting multiple surbs at once
|
||||
pub(crate) fn insert_reply_surbs<I: IntoIterator<Item = ReplySurb>>(&mut self, surbs: I) {
|
||||
let mut v = surbs.into_iter().collect::<VecDeque<_>>();
|
||||
trace!("storing {} surbs in the storage", v.len());
|
||||
self.data.append(&mut v);
|
||||
self.surbs_last_received_at_timestamp = OffsetDateTime::now_utc().unix_timestamp();
|
||||
trace!("we now have {} surbs!", self.data.len());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use dashmap::DashMap;
|
||||
use nymsphinx::addressing::clients::{Recipient, RecipientBytes};
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
use dashmap::iter::Iter;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UsedSenderTags {
|
||||
inner: Arc<UsedSenderTagsInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct UsedSenderTagsInner {
|
||||
data: DashMap<RecipientBytes, AnonymousSenderTag>,
|
||||
}
|
||||
|
||||
impl UsedSenderTags {
|
||||
pub(crate) fn new() -> UsedSenderTags {
|
||||
UsedSenderTags {
|
||||
inner: Arc::new(UsedSenderTagsInner {
|
||||
data: DashMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn from_raw(raw: Vec<(RecipientBytes, AnonymousSenderTag)>) -> UsedSenderTags {
|
||||
UsedSenderTags {
|
||||
inner: Arc::new(UsedSenderTagsInner {
|
||||
data: raw.into_iter().collect(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
pub(crate) fn as_raw_iter(&self) -> Iter<'_, RecipientBytes, AnonymousSenderTag> {
|
||||
self.inner.data.iter()
|
||||
}
|
||||
|
||||
pub(crate) fn insert_new(&self, recipient: &Recipient, tag: AnonymousSenderTag) {
|
||||
self.inner.data.insert(recipient.to_bytes(), tag);
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_existing(&self, recipient: &Recipient) -> Option<AnonymousSenderTag> {
|
||||
self.inner
|
||||
.data
|
||||
.get(&recipient.to_bytes())
|
||||
.map(|r| *r.value())
|
||||
}
|
||||
|
||||
pub(crate) fn exists(&self, recipient: &Recipient) -> bool {
|
||||
self.inner.data.contains_key(&recipient.to_bytes())
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crypto::generic_array::typenum::Unsigned;
|
||||
use log::*;
|
||||
use nymsphinx::anonymous_replies::{
|
||||
encryption_key::EncryptionKeyDigest, SurbEncryptionKey, SurbEncryptionKeySize,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ReplyKeyStorageError {
|
||||
#[error("DB Read Error: {0}")]
|
||||
DbReadError(sled::Error),
|
||||
#[error("DB Write Error: {0}")]
|
||||
DbWriteError(sled::Error),
|
||||
#[error("DB Open Error: {0}")]
|
||||
DbOpenError(sled::Error),
|
||||
}
|
||||
|
||||
/// Permanent storage for keys in all sent [`ReplySURB`]
|
||||
///
|
||||
/// Each sent out [`ReplySURB`] has a new key associated with it that is going to be used for
|
||||
/// payload encryption. In order to -decrypt whatever reply we receive, we need to know which
|
||||
/// key to use for that purpose. We do it based on received `H(t)` which has to be included
|
||||
/// with each reply.
|
||||
/// Moreover, there is no restriction when the [`ReplySURB`] might get used so we need to
|
||||
/// have a permanent storage for all the keys that we might ever see in the future.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyKeyStorage {
|
||||
db: sled::Db,
|
||||
}
|
||||
|
||||
impl ReplyKeyStorage {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, ReplyKeyStorageError> {
|
||||
let db = match sled::open(path) {
|
||||
Err(e) => return Err(ReplyKeyStorageError::DbOpenError(e)),
|
||||
Ok(db) => db,
|
||||
};
|
||||
|
||||
Ok(ReplyKeyStorage { db })
|
||||
}
|
||||
|
||||
fn read_encryption_key(&self, raw_key: sled::IVec) -> SurbEncryptionKey {
|
||||
let key_bytes_ref = raw_key.as_ref();
|
||||
// if this fails it means we have some database corruption and we
|
||||
// absolutely can't continue
|
||||
|
||||
if key_bytes_ref.len() != SurbEncryptionKeySize::USIZE {
|
||||
error!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
panic!("REPLY KEY STORAGE DATA CORRUPTION - ENCRYPTION KEY HAS INVALID LENGTH");
|
||||
}
|
||||
|
||||
// this can only fail if the bytes have invalid length but we already asserted it
|
||||
SurbEncryptionKey::try_from_bytes(key_bytes_ref).unwrap()
|
||||
}
|
||||
|
||||
// TOOD: perhaps we could also store some part of original message here too?
|
||||
pub fn insert_encryption_key(
|
||||
&mut self,
|
||||
encryption_key: SurbEncryptionKey,
|
||||
) -> Result<(), ReplyKeyStorageError> {
|
||||
let digest = encryption_key.compute_digest();
|
||||
|
||||
let insertion_result = match self.db.insert(digest, encryption_key.to_bytes()) {
|
||||
Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)),
|
||||
Ok(existing_key) => {
|
||||
if existing_key.is_some() {
|
||||
panic!("HASH COLLISION DETECTED")
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: perhaps we could implement some batching mechanism to avoid frequent flushes?
|
||||
self.db.flush().unwrap();
|
||||
insertion_result
|
||||
}
|
||||
|
||||
// Once we use key once, we do not expect to use it again
|
||||
pub fn get_and_remove_encryption_key(
|
||||
&self,
|
||||
key_digest: EncryptionKeyDigest,
|
||||
) -> Result<Option<SurbEncryptionKey>, ReplyKeyStorageError> {
|
||||
let removal_result = match self.db.remove(key_digest) {
|
||||
Err(e) => Err(ReplyKeyStorageError::DbReadError(e)),
|
||||
Ok(existing_key) => {
|
||||
Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key)))
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: not sure how to feel about flushing it every single time here...
|
||||
// same with insertion
|
||||
self.db.flush().unwrap();
|
||||
removal_result
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// 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::spawn_future;
|
||||
@@ -10,10 +10,9 @@ use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use topology::{nym_topology_from_detailed, NymTopology};
|
||||
use topology::{nym_topology_from_detailed, NymTopology, NymTopologyError};
|
||||
use url::Url;
|
||||
|
||||
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
|
||||
@@ -55,18 +54,36 @@ impl<'a> TopologyReadPermit<'a> {
|
||||
&'a self,
|
||||
ack_recipient: &Recipient,
|
||||
packet_recipient: Option<&Recipient>,
|
||||
) -> Option<&'a NymTopology> {
|
||||
// Note: implicit deref with Deref for TopologyReadPermit is happening here
|
||||
let topology_ref_option = self.permit.as_ref();
|
||||
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 {
|
||||
false
|
||||
})
|
||||
})
|
||||
) -> Result<&'a NymTopology, NymTopologyError> {
|
||||
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
|
||||
let topology = self
|
||||
.permit
|
||||
.as_ref()
|
||||
.as_ref()
|
||||
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
|
||||
|
||||
// 2. does it have any mixnode at all?
|
||||
// 3. does it have any gateways at all?
|
||||
// 4. does it have a mixnode on each layer?
|
||||
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
|
||||
|
||||
// 5. does it contain OUR gateway (so that we could create an ack packet)?
|
||||
if !topology.gateway_exists(ack_recipient.gateway()) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: ack_recipient.gateway().to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
|
||||
if let Some(recipient) = packet_recipient {
|
||||
if !topology.gateway_exists(recipient.gateway()) {
|
||||
return Err(NymTopologyError::NonExistentGatewayError {
|
||||
identity_key: recipient.gateway().to_base58_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(topology)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,10 +121,10 @@ impl TopologyAccessor {
|
||||
|
||||
// only used by the client at startup to get a slightly more reasonable error message
|
||||
// (currently displays as unused because health checker is disabled due to required changes)
|
||||
pub async fn is_routable(&self) -> bool {
|
||||
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
|
||||
match &self.inner.read().await.0 {
|
||||
None => false,
|
||||
Some(ref topology) => topology.can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
|
||||
None => Err(NymTopologyError::EmptyNetworkTopology),
|
||||
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,19 +136,15 @@ impl Default for TopologyAccessor {
|
||||
}
|
||||
|
||||
pub struct TopologyRefresherConfig {
|
||||
validator_api_urls: Vec<Url>,
|
||||
refresh_rate: time::Duration,
|
||||
nym_api_urls: Vec<Url>,
|
||||
refresh_rate: Duration,
|
||||
client_version: String,
|
||||
}
|
||||
|
||||
impl TopologyRefresherConfig {
|
||||
pub fn new(
|
||||
validator_api_urls: Vec<Url>,
|
||||
refresh_rate: time::Duration,
|
||||
client_version: String,
|
||||
) -> Self {
|
||||
pub fn new(nym_api_urls: Vec<Url>, refresh_rate: Duration, client_version: String) -> Self {
|
||||
TopologyRefresherConfig {
|
||||
validator_api_urls,
|
||||
nym_api_urls,
|
||||
refresh_rate,
|
||||
client_version,
|
||||
}
|
||||
@@ -139,10 +152,10 @@ impl TopologyRefresherConfig {
|
||||
}
|
||||
|
||||
pub struct TopologyRefresher {
|
||||
validator_client: validator_client::client::ApiClient,
|
||||
validator_client: validator_client::client::NymApiClient,
|
||||
client_version: String,
|
||||
|
||||
validator_api_urls: Vec<Url>,
|
||||
nym_api_urls: Vec<Url>,
|
||||
topology_accessor: TopologyAccessor,
|
||||
refresh_rate: Duration,
|
||||
|
||||
@@ -152,14 +165,14 @@ pub struct TopologyRefresher {
|
||||
|
||||
impl TopologyRefresher {
|
||||
pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self {
|
||||
cfg.validator_api_urls.shuffle(&mut thread_rng());
|
||||
cfg.nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
TopologyRefresher {
|
||||
validator_client: validator_client::client::ApiClient::new(
|
||||
cfg.validator_api_urls[0].clone(),
|
||||
validator_client: validator_client::client::NymApiClient::new(
|
||||
cfg.nym_api_urls[0].clone(),
|
||||
),
|
||||
client_version: cfg.client_version,
|
||||
validator_api_urls: cfg.validator_api_urls,
|
||||
nym_api_urls: cfg.nym_api_urls,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
currently_used_api: 0,
|
||||
@@ -167,15 +180,15 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
fn use_next_validator_api(&mut self) {
|
||||
if self.validator_api_urls.len() == 1 {
|
||||
warn!("There's only a single validator API available - it won't be possible to use a different one");
|
||||
fn use_next_nym_api(&mut self) {
|
||||
if self.nym_api_urls.len() == 1 {
|
||||
warn!("There's only a single nym API available - it won't be possible to use a different one");
|
||||
return;
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.validator_api_urls.len();
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
self.validator_client
|
||||
.change_validator_api(self.validator_api_urls[self.currently_used_api].clone())
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
/// Verifies whether nodes a reasonably distributed among all mix layers.
|
||||
@@ -241,7 +254,7 @@ impl TopologyRefresher {
|
||||
|
||||
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
|
||||
Err(err) => {
|
||||
error!("failed to get network mixnodes - {}", err);
|
||||
error!("failed to get network mixnodes - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(mixes) => mixes,
|
||||
@@ -249,7 +262,7 @@ impl TopologyRefresher {
|
||||
|
||||
let gateways = match self.validator_client.get_cached_gateways().await {
|
||||
Err(err) => {
|
||||
error!("failed to get network gateways - {}", err);
|
||||
error!("failed to get network gateways - {err}");
|
||||
return None;
|
||||
}
|
||||
Ok(gateways) => gateways,
|
||||
@@ -271,7 +284,7 @@ impl TopologyRefresher {
|
||||
let new_topology = self.get_current_compatible_topology().await;
|
||||
|
||||
if new_topology.is_none() {
|
||||
self.use_next_validator_api();
|
||||
self.use_next_nym_api();
|
||||
}
|
||||
|
||||
if new_topology.is_none() && self.was_latest_valid {
|
||||
@@ -289,11 +302,11 @@ impl TopologyRefresher {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn is_topology_routable(&self) -> bool {
|
||||
self.topology_accessor.is_routable().await
|
||||
pub async fn ensure_topology_is_routable(&self) -> Result<(), NymTopologyError> {
|
||||
self.topology_accessor.ensure_is_routable().await
|
||||
}
|
||||
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::ShutdownListener) {
|
||||
pub fn start_with_shutdown(mut self, mut shutdown: task::TaskClient) {
|
||||
spawn_future(async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
@@ -320,21 +333,4 @@ impl TopologyRefresher {
|
||||
log::debug!("TopologyRefresher: Exiting");
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
spawn_future(async move {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
|
||||
self.refresh_rate,
|
||||
));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
|
||||
while (interval.next().await).is_some() {
|
||||
self.refresh().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+127
-51
@@ -1,38 +1,57 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use client_connections::TransmissionLane;
|
||||
use rand::seq::SliceRandom;
|
||||
use nymsphinx::chunking::fragment::Fragment;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
use super::{get_time_now, RealMessage};
|
||||
|
||||
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
|
||||
const OLDEST_LANE_SET_SIZE: usize = 5;
|
||||
const OLDEST_LANE_SET_SIZE: usize = 4;
|
||||
// As a way of prune connections we also check for timeouts.
|
||||
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TransmissionBuffer {
|
||||
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
|
||||
pub(crate) trait SizedData {
|
||||
fn data_size(&self) -> usize;
|
||||
}
|
||||
|
||||
impl TransmissionBuffer {
|
||||
impl SizedData for RealMessage {
|
||||
fn data_size(&self) -> usize {
|
||||
self.packet_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedData for Fragment {
|
||||
fn data_size(&self) -> usize {
|
||||
// note that raw `Fragment` is smaller than sphinx packet payload
|
||||
// as it doesn't include surb-ack or the [shared] key materials
|
||||
self.payload_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TransmissionBuffer<T> {
|
||||
buffer: HashMap<TransmissionLane, LaneBufferEntry<T>>,
|
||||
}
|
||||
|
||||
impl<T> TransmissionBuffer<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
TransmissionBuffer {
|
||||
buffer: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
|
||||
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry<T>> {
|
||||
self.buffer.remove(lane)
|
||||
}
|
||||
|
||||
@@ -57,20 +76,22 @@ impl TransmissionBuffer {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size(&self) -> usize {
|
||||
self.buffer.values().map(LaneBufferEntry::len).sum()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize {
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||
where
|
||||
T: SizedData,
|
||||
{
|
||||
self.buffer
|
||||
.values()
|
||||
.map(|lane_buffer_entry| {
|
||||
lane_buffer_entry
|
||||
.real_messages
|
||||
.items
|
||||
.iter()
|
||||
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
|
||||
.map(|item| item.data_size())
|
||||
.sum::<usize>()
|
||||
})
|
||||
.sum()
|
||||
@@ -92,36 +113,51 @@ impl TransmissionBuffer {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
|
||||
pub(crate) fn store<I: IntoIterator<Item = T>>(&mut self, lane: &TransmissionLane, items: I) {
|
||||
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
|
||||
lane_buffer_entry.append(real_messages);
|
||||
lane_buffer_entry.extend(items);
|
||||
} else {
|
||||
self.buffer
|
||||
.insert(*lane, LaneBufferEntry::new(real_messages));
|
||||
.insert(*lane, LaneBufferEntry::new(items.into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
pub(crate) fn store_multiple(&mut self, items: Vec<(TransmissionLane, T)>) {
|
||||
for (lane, item) in items {
|
||||
self.buffer
|
||||
.entry(lane)
|
||||
.or_insert_with(LaneBufferEntry::new_empty)
|
||||
.push_item(item)
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
|
||||
fn pick_random_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
|
||||
lanes.choose(rng).copied()
|
||||
}
|
||||
|
||||
fn pick_random_small_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.filter(|(_, v)| v.is_small())
|
||||
.map(|(k, _)| k)
|
||||
.collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
lanes.choose(rng).copied()
|
||||
}
|
||||
|
||||
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
// 2/3 chance to pick from the old lanes
|
||||
fn pick_random_old_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<TransmissionLane> {
|
||||
let rand = &mut rand::thread_rng();
|
||||
if rand.gen_ratio(2, 3) {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(rand).copied()
|
||||
} else {
|
||||
self.pick_random_lane(rng).copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<T> {
|
||||
let real_msgs_queued = self.buffer.get_mut(lane)?;
|
||||
let real_next = real_msgs_queued.pop_front()?;
|
||||
real_msgs_queued.messages_transmitted += 1;
|
||||
@@ -131,19 +167,48 @@ impl TransmissionBuffer {
|
||||
Some(real_next)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> {
|
||||
pub(crate) fn pop_at_most_n_next_messages_at_random(
|
||||
&mut self,
|
||||
n: usize,
|
||||
) -> Option<Vec<(TransmissionLane, T)>> {
|
||||
// let start = Instant::now();
|
||||
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rng = &mut rand::thread_rng();
|
||||
let mut items = Vec::with_capacity(n);
|
||||
|
||||
while items.len() < n {
|
||||
let Some(next) = self.pop_next_message_at_random(rng) else {
|
||||
break
|
||||
};
|
||||
items.push(next)
|
||||
}
|
||||
|
||||
// todo!("time time taken");
|
||||
|
||||
Some(items)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_next_message_at_random<R: Rng + ?Sized>(
|
||||
&mut self,
|
||||
// turns out the caller always have access to some rng, so no point in instantiating new one
|
||||
rng: &mut R,
|
||||
) -> Option<(TransmissionLane, T)> {
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
|
||||
// to try to finish lanes when possible, then the rest.
|
||||
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
|
||||
let lane = if let Some(small_lane) = self.pick_random_small_lane(rng) {
|
||||
*small_lane
|
||||
} else if let Some(old_lane) = self.pick_random_old_lane() {
|
||||
} else if let Some(old_lane) = self.pick_random_old_lane(rng) {
|
||||
old_lane
|
||||
} else {
|
||||
*self.pick_random_lane()?
|
||||
*self.pick_random_lane(rng)?
|
||||
};
|
||||
|
||||
let msg = self.pop_front_from_lane(&lane)?;
|
||||
@@ -165,35 +230,46 @@ impl TransmissionBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LaneBufferEntry {
|
||||
pub real_messages: VecDeque<RealMessage>,
|
||||
pub(crate) struct LaneBufferEntry<T> {
|
||||
pub items: VecDeque<T>,
|
||||
pub messages_transmitted: usize,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub time_for_last_activity: time::Instant,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub time_for_last_activity: wasm_timer::Instant,
|
||||
pub time_for_last_activity: Instant,
|
||||
}
|
||||
|
||||
impl LaneBufferEntry {
|
||||
fn new(real_messages: Vec<RealMessage>) -> Self {
|
||||
impl<T> LaneBufferEntry<T> {
|
||||
fn new_empty() -> Self {
|
||||
LaneBufferEntry {
|
||||
real_messages: real_messages.into(),
|
||||
items: VecDeque::new(),
|
||||
messages_transmitted: 0,
|
||||
time_for_last_activity: get_time_now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, real_messages: Vec<RealMessage>) {
|
||||
self.real_messages.append(&mut real_messages.into());
|
||||
fn new(items: VecDeque<T>) -> Self {
|
||||
LaneBufferEntry {
|
||||
items,
|
||||
messages_transmitted: 0,
|
||||
time_for_last_activity: get_time_now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_item(&mut self, item: T) {
|
||||
self.items.push_back(item);
|
||||
// I'm not updating time here on purpose. This method is called just after `new_empty`,
|
||||
// where the time is already set. Furthermore, this method is called there multiple times at once
|
||||
}
|
||||
|
||||
fn extend<I: IntoIterator<Item = T>>(&mut self, items: I) {
|
||||
self.items.extend(items);
|
||||
self.time_for_last_activity = get_time_now();
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<RealMessage> {
|
||||
self.real_messages.pop_front()
|
||||
fn pop_front(&mut self) -> Option<T> {
|
||||
self.items.pop_front()
|
||||
}
|
||||
|
||||
fn is_small(&self) -> bool {
|
||||
self.real_messages.len() < 100
|
||||
self.items.len() < 100
|
||||
}
|
||||
|
||||
fn is_stale(&self) -> bool {
|
||||
@@ -202,10 +278,10 @@ impl LaneBufferEntry {
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.real_messages.len()
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.real_messages.is_empty()
|
||||
self.items.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use config::{NymConfig, DB_FILE_NAME};
|
||||
use config::defaults::NymNetworkDetails;
|
||||
use config::{NymConfig, OptionalSet, DB_FILE_NAME};
|
||||
use nymsphinx::params::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
@@ -30,10 +31,36 @@ const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_00
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
|
||||
// reply-surbs related:
|
||||
|
||||
// define when to request
|
||||
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
|
||||
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 100;
|
||||
|
||||
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
|
||||
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD: Duration = Duration::from_secs(10);
|
||||
|
||||
// 12 hours
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
|
||||
|
||||
// 24 hours
|
||||
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
pub fn missing_string_value() -> String {
|
||||
MISSING_VALUE.to_string()
|
||||
}
|
||||
|
||||
pub trait ClientCoreConfigTrait {
|
||||
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config<T> {
|
||||
@@ -45,30 +72,48 @@ pub struct Config<T> {
|
||||
debug: DebugConfig,
|
||||
}
|
||||
|
||||
impl<T> ClientCoreConfigTrait for Config<T> {
|
||||
fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
|
||||
&self.client.gateway_endpoint
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OptionalSet for Config<T> where T: NymConfig {}
|
||||
|
||||
impl<T> Config<T> {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let mut cfg = Config::default();
|
||||
cfg.with_id(id);
|
||||
cfg
|
||||
Config::default().with_id(id)
|
||||
}
|
||||
|
||||
pub fn with_id<S: Into<String>>(&mut self, id: S)
|
||||
pub fn with_id<S: Into<String>>(mut self, id: S) -> Self
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let id = id.into();
|
||||
self.client.id = id.into();
|
||||
self.set_empty_fields_to_defaults();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_empty_fields_to_defaults(&mut self) -> bool
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let id = &self.client.id;
|
||||
let mut changes_made = false;
|
||||
|
||||
// identity key setting
|
||||
if self.client.private_identity_key_file.as_os_str().is_empty() {
|
||||
changes_made = true;
|
||||
self.client.private_identity_key_file =
|
||||
self::Client::<T>::default_private_identity_key_file(&id);
|
||||
self::Client::<T>::default_private_identity_key_file(id);
|
||||
}
|
||||
if self.client.public_identity_key_file.as_os_str().is_empty() {
|
||||
changes_made = true;
|
||||
self.client.public_identity_key_file =
|
||||
self::Client::<T>::default_public_identity_key_file(&id);
|
||||
self::Client::<T>::default_public_identity_key_file(id);
|
||||
}
|
||||
|
||||
// encryption key setting
|
||||
@@ -78,8 +123,9 @@ impl<T> Config<T> {
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
changes_made = true;
|
||||
self.client.private_encryption_key_file =
|
||||
self::Client::<T>::default_private_encryption_key_file(&id);
|
||||
self::Client::<T>::default_private_encryption_key_file(id);
|
||||
}
|
||||
if self
|
||||
.client
|
||||
@@ -87,40 +133,40 @@ impl<T> Config<T> {
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
changes_made = true;
|
||||
self.client.public_encryption_key_file =
|
||||
self::Client::<T>::default_public_encryption_key_file(&id);
|
||||
self::Client::<T>::default_public_encryption_key_file(id);
|
||||
}
|
||||
|
||||
// shared gateway key setting
|
||||
if self.client.gateway_shared_key_file.as_os_str().is_empty() {
|
||||
changes_made = true;
|
||||
self.client.gateway_shared_key_file =
|
||||
self::Client::<T>::default_gateway_shared_key_file(&id);
|
||||
self::Client::<T>::default_gateway_shared_key_file(id);
|
||||
}
|
||||
|
||||
// ack key setting
|
||||
if self.client.ack_key_file.as_os_str().is_empty() {
|
||||
self.client.ack_key_file = self::Client::<T>::default_ack_key_file(&id);
|
||||
changes_made = true;
|
||||
self.client.ack_key_file = self::Client::<T>::default_ack_key_file(id);
|
||||
}
|
||||
|
||||
if self
|
||||
.client
|
||||
.reply_encryption_key_store_path
|
||||
.as_os_str()
|
||||
.is_empty()
|
||||
{
|
||||
self.client.reply_encryption_key_store_path =
|
||||
self::Client::<T>::default_reply_encryption_key_store_path(&id);
|
||||
if self.client.reply_surb_database_path.as_os_str().is_empty() {
|
||||
changes_made = true;
|
||||
self.client.reply_surb_database_path =
|
||||
self::Client::<T>::default_reply_surb_database_path(id);
|
||||
}
|
||||
|
||||
if self.client.database_path.as_os_str().is_empty() {
|
||||
self.client.database_path = self::Client::<T>::default_database_path(&id);
|
||||
changes_made = true;
|
||||
self.client.database_path = self::Client::<T>::default_database_path(id);
|
||||
}
|
||||
|
||||
self.client.id = id;
|
||||
changes_made
|
||||
}
|
||||
|
||||
pub fn with_disabled_credentials(&mut self, disabled_credentials_mode: bool) {
|
||||
pub fn with_disabled_credentials(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.client.disabled_credentials_mode = disabled_credentials_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpointConfig) {
|
||||
@@ -131,12 +177,29 @@ impl<T> Config<T> {
|
||||
self.client.gateway_endpoint.gateway_id = id.into();
|
||||
}
|
||||
|
||||
pub fn set_custom_validators(&mut self, validator_urls: Vec<Url>) {
|
||||
self.client.validator_urls = validator_urls;
|
||||
pub fn with_custom_nyxd(mut self, urls: Vec<Url>) -> Self {
|
||||
self.client.nyxd_urls = urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
|
||||
self.client.validator_api_urls = validator_api_urls;
|
||||
pub fn set_custom_nyxd(&mut self, nyxd_urls: Vec<Url>) {
|
||||
self.client.nyxd_urls = nyxd_urls;
|
||||
}
|
||||
|
||||
pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec<Url>) -> Self {
|
||||
self.client.nym_api_urls = nym_api_urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec<Url>) {
|
||||
self.client.nym_api_urls = nym_api_urls;
|
||||
}
|
||||
|
||||
pub fn with_high_default_traffic_volume(mut self, enabled: bool) -> Self {
|
||||
if enabled {
|
||||
self.set_high_default_traffic_volume();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_high_default_traffic_volume(&mut self) {
|
||||
@@ -147,6 +210,13 @@ impl<T> Config<T> {
|
||||
self.debug.message_sending_average_delay = Duration::from_millis(4);
|
||||
}
|
||||
|
||||
pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self {
|
||||
if disabled {
|
||||
self.set_no_cover_traffic()
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_no_cover_traffic(&mut self) {
|
||||
self.debug.disable_loop_cover_traffic_stream = true;
|
||||
self.debug.disable_main_poisson_packet_distribution = true;
|
||||
@@ -188,20 +258,16 @@ impl<T> Config<T> {
|
||||
self.client.gateway_shared_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_reply_encryption_key_store_path(&self) -> PathBuf {
|
||||
self.client.reply_encryption_key_store_path.clone()
|
||||
}
|
||||
|
||||
pub fn get_ack_key_file(&self) -> PathBuf {
|
||||
self.client.ack_key_file.clone()
|
||||
}
|
||||
|
||||
pub fn get_validator_endpoints(&self) -> Vec<Url> {
|
||||
self.client.validator_urls.clone()
|
||||
self.client.nyxd_urls.clone()
|
||||
}
|
||||
|
||||
pub fn get_validator_api_endpoints(&self) -> Vec<Url> {
|
||||
self.client.validator_api_urls.clone()
|
||||
pub fn get_nym_api_endpoints(&self) -> Vec<Url> {
|
||||
self.client.nym_api_urls.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway_id(&self) -> String {
|
||||
@@ -220,14 +286,18 @@ impl<T> Config<T> {
|
||||
&self.client.gateway_endpoint
|
||||
}
|
||||
|
||||
pub fn get_gateway_endpoint(&self) -> &GatewayEndpointConfig {
|
||||
&self.client.gateway_endpoint
|
||||
}
|
||||
|
||||
pub fn get_database_path(&self) -> PathBuf {
|
||||
self.client.database_path.clone()
|
||||
}
|
||||
|
||||
pub fn get_reply_surb_database_path(&self) -> PathBuf {
|
||||
self.client.reply_surb_database_path.clone()
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> &str {
|
||||
&self.client.version
|
||||
}
|
||||
|
||||
// Debug getters
|
||||
pub fn get_debug_config(&self) -> &DebugConfig {
|
||||
&self.debug
|
||||
@@ -281,8 +351,36 @@ impl<T> Config<T> {
|
||||
self.debug.use_extended_packet_size
|
||||
}
|
||||
|
||||
pub fn get_version(&self) -> &str {
|
||||
&self.client.version
|
||||
pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize {
|
||||
self.debug.minimum_reply_surb_storage_threshold
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize {
|
||||
self.debug.maximum_reply_surb_storage_threshold
|
||||
}
|
||||
|
||||
pub fn get_minimum_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.minimum_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.maximum_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.maximum_allowed_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_waiting_period(&self) -> Duration {
|
||||
self.debug.maximum_reply_surb_waiting_period
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_age(&self) -> Duration {
|
||||
self.debug.maximum_reply_surb_age
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_key_age(&self) -> Duration {
|
||||
self.debug.maximum_reply_key_age
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +408,22 @@ pub struct GatewayEndpointConfig {
|
||||
pub gateway_listener: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
|
||||
impl GatewayEndpointConfig {
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(constructor))]
|
||||
pub fn new(
|
||||
gateway_id: String,
|
||||
gateway_owner: String,
|
||||
gateway_listener: String,
|
||||
) -> GatewayEndpointConfig {
|
||||
GatewayEndpointConfig {
|
||||
gateway_id,
|
||||
gateway_owner,
|
||||
gateway_listener,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<topology::gateway::Node> for GatewayEndpointConfig {
|
||||
fn from(node: topology::gateway::Node) -> GatewayEndpointConfig {
|
||||
let gateway_listener = node.clients_address();
|
||||
@@ -335,12 +449,13 @@ pub struct Client<T> {
|
||||
#[serde(default)]
|
||||
disabled_credentials_mode: bool,
|
||||
|
||||
/// Addresses to nymd validators via which the client can communicate with the chain.
|
||||
#[serde(default)]
|
||||
validator_urls: Vec<Url>,
|
||||
/// Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
#[serde(alias = "validator_urls")]
|
||||
nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls: Vec<Url>,
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Path to file containing private identity key.
|
||||
private_identity_key_file: PathBuf,
|
||||
@@ -362,16 +477,19 @@ pub struct Client<T> {
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file: PathBuf,
|
||||
|
||||
/// Full path to file containing reply encryption keys of all reply-SURBs we have ever
|
||||
/// sent but not received back.
|
||||
reply_encryption_key_store_path: PathBuf,
|
||||
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
gateway_endpoint: GatewayEndpointConfig,
|
||||
|
||||
/// Path to the database containing bandwidth credentials of this client.
|
||||
database_path: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
// this was set to use #[serde(default)] for the purposes of compatibility for multi-surbs introduced in 1.1.4.
|
||||
// if you're reading this message and we have already introduced some breaking changes, feel free
|
||||
// to remove that attribute since at this point the client configs should have gotten regenerated
|
||||
#[serde(default)]
|
||||
reply_surb_database_path: PathBuf,
|
||||
|
||||
/// nym_home_directory specifies absolute path to the home nym Clients directory.
|
||||
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||
nym_root_directory: PathBuf,
|
||||
@@ -382,22 +500,38 @@ pub struct Client<T> {
|
||||
|
||||
impl<T: NymConfig> Default for Client<T> {
|
||||
fn default() -> Self {
|
||||
let network = NymNetworkDetails::new_mainnet();
|
||||
let nyxd_urls = network
|
||||
.endpoints
|
||||
.iter()
|
||||
.map(|validator| validator.nyxd_url())
|
||||
.collect();
|
||||
let nym_api_urls = network
|
||||
.endpoints
|
||||
.iter()
|
||||
.filter_map(|validator| validator.api_url())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
panic!("we do not have any default nym-api urls available!")
|
||||
}
|
||||
|
||||
// there must be explicit checks for whether id is not empty later
|
||||
Client {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
disabled_credentials_mode: true,
|
||||
validator_urls: vec![],
|
||||
validator_api_urls: vec![],
|
||||
nyxd_urls,
|
||||
nym_api_urls,
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
private_encryption_key_file: Default::default(),
|
||||
public_encryption_key_file: Default::default(),
|
||||
gateway_shared_key_file: Default::default(),
|
||||
ack_key_file: Default::default(),
|
||||
reply_encryption_key_store_path: Default::default(),
|
||||
gateway_endpoint: Default::default(),
|
||||
database_path: Default::default(),
|
||||
reply_surb_database_path: Default::default(),
|
||||
nym_root_directory: T::default_root_directory(),
|
||||
super_struct: Default::default(),
|
||||
}
|
||||
@@ -429,9 +563,10 @@ impl<T: NymConfig> Client<T> {
|
||||
T::default_data_directory(Some(id)).join("ack_key.pem")
|
||||
}
|
||||
|
||||
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("reply_key_store")
|
||||
fn default_reply_surb_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join("persistent_reply_store.sqlite")
|
||||
}
|
||||
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(Some(id)).join(DB_FILE_NAME)
|
||||
}
|
||||
@@ -507,6 +642,37 @@ pub struct DebugConfig {
|
||||
|
||||
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
/// Defines the minimum number of reply surbs the client would request.
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs the client would request.
|
||||
pub maximum_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
pub maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
/// for more even though in theory they wouldn't need to.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_waiting_period: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply surb is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_age: Duration,
|
||||
|
||||
/// Defines maximum amount of time given reply key is going to be valid for.
|
||||
/// This is going to be superseded by key rotation once implemented.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_key_age: Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -532,6 +698,14 @@ impl Default for DebugConfig {
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: None,
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_WAITING_PERIOD,
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,69 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
use crate::client::reply_key_storage::ReplyKeyStorageError;
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use gateway_client::error::GatewayClientError;
|
||||
use topology::NymTopologyError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreError {
|
||||
#[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),
|
||||
|
||||
#[cfg(feature = "reply-surb")]
|
||||
#[error("Reply key storage error: {0}")]
|
||||
ReplyKeyStorageError(#[from] ReplyKeyStorageError),
|
||||
|
||||
#[error("No gateway with id: {0}")]
|
||||
NoGatewayWithId(String),
|
||||
|
||||
#[error("No gateways on network")]
|
||||
NoGatewaysOnNetwork,
|
||||
#[error("List of validator apis is empty")]
|
||||
ListOfValidatorApisIsEmpty,
|
||||
|
||||
#[error("Failed to setup gateway")]
|
||||
FailedToSetupGateway,
|
||||
|
||||
#[error("List of nym apis is empty")]
|
||||
ListOfNymApisIsEmpty,
|
||||
|
||||
#[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,
|
||||
InsufficientNetworkTopology(#[from] NymTopologyError),
|
||||
|
||||
#[error("experienced a failure with our reply surb persistent storage: {source}")]
|
||||
SurbStorageError {
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error("The gateway id is invalid - {0}")]
|
||||
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
|
||||
|
||||
#[error("The identity of the gateway is unknwown - did you run init?")]
|
||||
GatewayIdUnknown,
|
||||
|
||||
#[error("The owner of the gateway is unknown - did you run init?")]
|
||||
GatewayOwnerUnknown,
|
||||
|
||||
#[error("The address of the gateway is unknown - did you run init?")]
|
||||
GatwayAddressUnknown,
|
||||
|
||||
#[error("Unexpected exit")]
|
||||
UnexpectedExit,
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientCoreStatusMessage {
|
||||
#[error("The connected gateway is slow, or the connection to it is slow")]
|
||||
GatewayIsSlow,
|
||||
#[error("The connected gateway is very slow, or the connection to it is very slow")]
|
||||
GatewayIsVerySlow,
|
||||
}
|
||||
|
||||
@@ -1,39 +1,31 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Collection of initialization steps used by client implementations
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::addressing::nodes::NodeIdentity;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tap::TapFallible;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
client::key_manager::KeyManager,
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use rand::{rngs::OsRng, seq::SliceRandom, thread_rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
use topology::{filter::VersionFilterable, gateway};
|
||||
use url::Url;
|
||||
|
||||
pub async fn query_gateway_details(
|
||||
pub(super) async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<&str>,
|
||||
chosen_gateway_id: Option<String>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let validator_api = validator_servers
|
||||
let nym_api = validator_servers
|
||||
.choose(&mut thread_rng())
|
||||
.ok_or(ClientCoreError::ListOfValidatorApisIsEmpty)?;
|
||||
let validator_client = validator_client::client::ApiClient::new(validator_api.clone());
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
|
||||
log::trace!("Fetching list of gateways from: {}", validator_api);
|
||||
log::trace!("Fetching list of gateways from: {}", nym_api);
|
||||
let gateways = validator_client.get_cached_gateways().await?;
|
||||
let valid_gateways = gateways
|
||||
.into_iter()
|
||||
@@ -59,26 +51,6 @@ pub async fn query_gateway_details(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn register_with_gateway_and_store_keys<T>(
|
||||
gateway_details: gateway::Node,
|
||||
config: &Config<T>,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
let mut rng = OsRng;
|
||||
let mut key_manager = KeyManager::new(&mut rng);
|
||||
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
Ok(key_manager
|
||||
.store_keys(&pathfinder)
|
||||
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||
}
|
||||
|
||||
async fn register_with_gateway(
|
||||
gateway: &gateway::Node,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
@@ -102,46 +74,22 @@ async fn register_with_gateway(
|
||||
Ok(shared_keys)
|
||||
}
|
||||
|
||||
pub fn show_address<T>(config: &Config<T>) -> Result<(), ClientCoreError>
|
||||
pub(super) async fn register_with_gateway_and_store_keys<T>(
|
||||
gateway_details: gateway::Node,
|
||||
config: &Config<T>,
|
||||
) -> Result<(), ClientCoreError>
|
||||
where
|
||||
T: config::NymConfig,
|
||||
T: NymConfig,
|
||||
{
|
||||
fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
let mut rng = OsRng;
|
||||
let mut key_manager = KeyManager::new(&mut rng);
|
||||
|
||||
fn load_sphinx_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
let shared_keys =
|
||||
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await?;
|
||||
key_manager.insert_gateway_shared_key(shared_keys);
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||
|
||||
let client_recipient = Recipient::new(
|
||||
*identity_keypair.public_key(),
|
||||
*sphinx_keypair.public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||
);
|
||||
|
||||
println!("\nThe address of this client is: {}", client_recipient);
|
||||
Ok(())
|
||||
Ok(key_manager
|
||||
.store_keys(&pathfinder)
|
||||
.tap_err(|err| log::error!("Failed to generate keys: {err}"))?)
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Collection of initialization steps used by client implementations
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use nymsphinx::addressing::{clients::Recipient, nodes::NodeIdentity};
|
||||
use serde::Serialize;
|
||||
use tap::TapFallible;
|
||||
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
|
||||
use crate::{
|
||||
config::{
|
||||
persistence::key_pathfinder::ClientKeyPathfinder, ClientCoreConfigTrait, Config,
|
||||
GatewayEndpointConfig,
|
||||
},
|
||||
error::ClientCoreError,
|
||||
init::helpers::{query_gateway_details, register_with_gateway_and_store_keys},
|
||||
};
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
version: String,
|
||||
id: String,
|
||||
identity_key: String,
|
||||
encryption_key: String,
|
||||
gateway_id: String,
|
||||
gateway_listener: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
pub fn new<T>(config: &Config<T>, address: &Recipient) -> Self
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
Self {
|
||||
version: config.get_version().to_string(),
|
||||
id: config.get_id(),
|
||||
identity_key: address.identity().to_base58_string(),
|
||||
encryption_key: address.encryption_key().to_base58_string(),
|
||||
gateway_id: config.get_gateway_id(),
|
||||
gateway_listener: config.get_gateway_listener(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "Version: {}", self.version)?;
|
||||
writeln!(f, "ID: {}", self.id)?;
|
||||
writeln!(f, "Identity key: {}", self.identity_key)?;
|
||||
writeln!(f, "Encryption: {}", self.encryption_key)?;
|
||||
writeln!(f, "Gateway ID: {}", self.gateway_id)?;
|
||||
write!(f, "Gateway: {}", self.gateway_listener)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience function for setting up the gateway for a client. Depending on the arguments given
|
||||
/// it will do the sensible thing.
|
||||
pub async fn setup_gateway<C, T>(
|
||||
register_gateway: bool,
|
||||
// TODO: this should get refactored to instead take Option<identity::PublicKey>
|
||||
user_chosen_gateway_id: Option<String>,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
C: NymConfig + ClientCoreConfigTrait,
|
||||
T: NymConfig,
|
||||
{
|
||||
let id = config.get_id();
|
||||
if register_gateway {
|
||||
register_with_gateway(user_chosen_gateway_id, config).await
|
||||
} else if let Some(user_chosen_gateway_id) = user_chosen_gateway_id {
|
||||
config_gateway_with_existing_keys(user_chosen_gateway_id, config).await
|
||||
} else {
|
||||
reuse_existing_gateway_config::<C>(&id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
/// the chosen one if it's among the available ones.
|
||||
/// Saves keys to disk, specified by the paths in `config`.
|
||||
pub async fn register_with_gateway<T>(
|
||||
user_chosen_gateway_id: Option<String>,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
println!("Configuring gateway");
|
||||
let gateway =
|
||||
query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id).await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
register_with_gateway_and_store_keys(gateway.clone(), config).await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
}
|
||||
|
||||
/// Set the gateway using the usual procedue of querying the validator-api, but don't register or
|
||||
/// create any keys.
|
||||
/// This assumes that the user knows what they are doing, and that the existing keys are valid for
|
||||
/// the gateway being used
|
||||
pub async fn config_gateway_with_existing_keys<T>(
|
||||
user_chosen_gateway_id: String,
|
||||
config: &Config<T>,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
T: NymConfig,
|
||||
{
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway =
|
||||
query_gateway_details(config.get_nym_api_endpoints(), Some(user_chosen_gateway_id)).await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
}
|
||||
|
||||
/// Read and reuse the existing gateway configuration from a file that was generate earlier.
|
||||
pub fn reuse_existing_gateway_config<T>(id: &str) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
T: NymConfig + ClientCoreConfigTrait,
|
||||
{
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
T::load_from_file(Some(id))
|
||||
.map(|existing_config| existing_config.get_gateway_endpoint().clone())
|
||||
.map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the client address by loading the keys from stored files.
|
||||
pub fn get_client_address_from_stored_keys<T>(
|
||||
config: &Config<T>,
|
||||
) -> Result<Recipient, ClientCoreError>
|
||||
where
|
||||
T: config::NymConfig,
|
||||
{
|
||||
fn load_identity_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<identity::KeyPair, ClientCoreError> {
|
||||
let identity_keypair: identity::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_identity_key().to_owned(),
|
||||
pathfinder.public_identity_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored identity key files"))?;
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
fn load_sphinx_keys(
|
||||
pathfinder: &ClientKeyPathfinder,
|
||||
) -> Result<encryption::KeyPair, ClientCoreError> {
|
||||
let sphinx_keypair: encryption::KeyPair =
|
||||
pemstore::load_keypair(&pemstore::KeyPairPath::new(
|
||||
pathfinder.private_encryption_key().to_owned(),
|
||||
pathfinder.public_encryption_key().to_owned(),
|
||||
))
|
||||
.tap_err(|_| log::error!("Failed to read stored sphinx key files"))?;
|
||||
Ok(sphinx_keypair)
|
||||
}
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config);
|
||||
let identity_keypair = load_identity_keys(&pathfinder)?;
|
||||
let sphinx_keypair = load_sphinx_keys(&pathfinder)?;
|
||||
|
||||
let client_recipient = Recipient::new(
|
||||
*identity_keypair.public_key(),
|
||||
*sphinx_keypair.public_key(),
|
||||
// TODO: below only works under assumption that gateway address == gateway id
|
||||
// (which currently is true)
|
||||
NodeIdentity::from_base58_string(config.get_gateway_id())?,
|
||||
);
|
||||
|
||||
Ok(client_recipient)
|
||||
}
|
||||
|
||||
pub fn output_to_json<T: Serialize>(init_results: &T, output_file: &str) {
|
||||
match std::fs::File::create(output_file) {
|
||||
Ok(file) => match serde_json::to_writer_pretty(file, init_results) {
|
||||
Ok(_) => println!("Saved: {}", output_file),
|
||||
Err(err) => eprintln!("Could not save {}: {err}", output_file),
|
||||
},
|
||||
Err(err) => eprintln!("Could not save {}: {err}", output_file),
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bip39 = "1.0.1"
|
||||
cfg-if = "0.1"
|
||||
clap = { version = "3.2", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
rand = "0.7.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
@@ -23,7 +23,7 @@ credential-storage = { path = "../../common/credential-storage" }
|
||||
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
|
||||
network-defaults = { path = "../../common/network-defaults" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
|
||||
|
||||
[features]
|
||||
coconut = ["credentials/coconut"]
|
||||
|
||||
@@ -6,27 +6,27 @@ use bip39::Mnemonic;
|
||||
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
use validator_client::nymd;
|
||||
use validator_client::nymd::traits::CoconutBandwidthSigningClient;
|
||||
use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
|
||||
use validator_client::nyxd;
|
||||
use validator_client::nyxd::traits::CoconutBandwidthSigningClient;
|
||||
use validator_client::nyxd::{Coin, Fee, NyxdClient, SigningNyxdClient};
|
||||
|
||||
pub(crate) struct Client {
|
||||
nymd_client: NymdClient<SigningNymdClient>,
|
||||
nyxd_client: NyxdClient<SigningNyxdClient>,
|
||||
mix_denom_base: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(nymd_url: &str, mnemonic: &str) -> Self {
|
||||
let nymd_url = Url::from_str(nymd_url).unwrap();
|
||||
pub fn new(nyxd_url: &str, mnemonic: &str) -> Self {
|
||||
let nyxd_url = Url::from_str(nyxd_url).unwrap();
|
||||
let mnemonic = Mnemonic::from_str(mnemonic).unwrap();
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config = nymd::Config::try_from_nym_network_details(&network_details)
|
||||
let config = nyxd::Config::try_from_nym_network_details(&network_details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
let nymd_client =
|
||||
NymdClient::connect_with_mnemonic(config, nymd_url.as_ref(), mnemonic, None).unwrap();
|
||||
let nyxd_client =
|
||||
NyxdClient::connect_with_mnemonic(config, nyxd_url.as_ref(), mnemonic, None).unwrap();
|
||||
|
||||
Client {
|
||||
nymd_client,
|
||||
nyxd_client,
|
||||
mix_denom_base: network_details.chain_details.mix_denom.base,
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ impl Client {
|
||||
) -> Result<String> {
|
||||
let amount = Coin::new(amount as u128, self.mix_denom_base.clone());
|
||||
Ok(self
|
||||
.nymd_client
|
||||
.nyxd_client
|
||||
.deposit(
|
||||
amount,
|
||||
String::from(VOUCHER_INFO),
|
||||
|
||||
@@ -13,7 +13,7 @@ use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
|
||||
use credentials::coconut::utils::obtain_aggregate_signature;
|
||||
use crypto::asymmetric::{encryption, identity};
|
||||
use network_defaults::{NymNetworkDetails, VOUCHER_INFO};
|
||||
use validator_client::nymd::tx::Hash;
|
||||
use validator_client::nyxd::tx::Hash;
|
||||
use validator_client::{CoconutApiClient, Config};
|
||||
|
||||
use crate::client::Client;
|
||||
@@ -38,9 +38,9 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
pub(crate) client_home_directory: std::path::PathBuf,
|
||||
|
||||
/// The nymd URL that should be used
|
||||
/// The nyxd URL that should be used
|
||||
#[clap(long)]
|
||||
pub(crate) nymd_url: String,
|
||||
pub(crate) nyxd_url: String,
|
||||
|
||||
/// A mnemonic for the account that buys the credential
|
||||
#[clap(long)]
|
||||
@@ -51,12 +51,12 @@ pub(crate) struct Run {
|
||||
pub(crate) amount: u64,
|
||||
}
|
||||
|
||||
pub(crate) async fn deposit(nymd_url: &str, mnemonic: &str, amount: u64) -> Result<State> {
|
||||
pub(crate) async fn deposit(nyxd_url: &str, mnemonic: &str, amount: u64) -> Result<State> {
|
||||
let mut rng = OsRng;
|
||||
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
|
||||
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
|
||||
|
||||
let client = Client::new(nymd_url, mnemonic);
|
||||
let client = Client::new(nyxd_url, mnemonic);
|
||||
let tx_hash = client
|
||||
.deposit(
|
||||
amount,
|
||||
@@ -98,6 +98,7 @@ pub(crate) async fn get_credential(state: &State, shared_storage: PersistentStor
|
||||
&coconut_api_clients,
|
||||
)
|
||||
.await?;
|
||||
println!("Signature: {:?}", signature.to_bs58());
|
||||
shared_storage
|
||||
.insert_coconut_credential(
|
||||
state.amount.to_string(),
|
||||
|
||||
@@ -7,15 +7,15 @@ use credential_storage::error::StorageError;
|
||||
use credentials::error::Error as CredentialError;
|
||||
use crypto::asymmetric::encryption::KeyRecoveryError;
|
||||
use crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nyxd::error::NyxdError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CredentialClientError>;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CredentialClientError {
|
||||
#[error("Nymd error: {0}")]
|
||||
Nymd(#[from] NymdError),
|
||||
#[error("Nyxd error: {0}")]
|
||||
Nyxd(#[from] NyxdError),
|
||||
|
||||
#[error("Validator client error: {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
@@ -31,7 +31,7 @@ cfg_if::cfg_if! {
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
let bin_name = "nym-credential-client";
|
||||
|
||||
match args.command {
|
||||
@@ -39,11 +39,11 @@ cfg_if::cfg_if! {
|
||||
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME);
|
||||
let shared_storage = credential_storage::initialise_storage(db_path).await;
|
||||
|
||||
let state = deposit(&r.nymd_url, &r.mnemonic, r.amount).await?;
|
||||
let state = deposit(&r.nyxd_url, &r.mnemonic, r.amount).await?;
|
||||
get_credential(&state, shared_storage).await?;
|
||||
}
|
||||
Command::Completions(c) => c.generate(&mut crate::Cli::into_app(), bin_name),
|
||||
Command::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
|
||||
Command::Completions(c) => c.generate(&mut crate::Cli::command(), bin_name),
|
||||
Command::GenerateFigSpec => fig_generate(&mut crate::Cli::command(), bin_name)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.2"
|
||||
version = "1.1.6"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
@@ -20,19 +20,22 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
|
||||
# and the single instance of abortable we have should really be refactored anyway
|
||||
url = "2.2"
|
||||
|
||||
clap = { version = "3.2", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4" # self explanatory
|
||||
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
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0.34"
|
||||
tap = "1.0.1"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } # async runtime
|
||||
tokio-tungstenite = "0.14" # websocket
|
||||
|
||||
## internal
|
||||
client-core = { path = "../client-core" }
|
||||
build-information = { path = "../../common/build-information" }
|
||||
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
@@ -48,16 +51,12 @@ nymsphinx = { path = "../../common/nymsphinx" }
|
||||
pemstore = { path = "../../common/pemstore" }
|
||||
task = { path = "../../common/task" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
websocket-requests = { path = "websocket-requests" }
|
||||
tap = "1.0.1"
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0" # for the "textsend" example
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use vergen::{vergen, Config};
|
||||
|
||||
fn main() {
|
||||
vergen(Config::default()).expect("failed to extract build metadata")
|
||||
}
|
||||
+400
-256
File diff suppressed because it is too large
Load Diff
@@ -25,58 +25,59 @@ async fn get_self_address(ws_stream: &mut WebSocketStream<MaybeTlsStream<TcpStre
|
||||
let response = send_message_and_get_response(ws_stream, self_address_request).await;
|
||||
|
||||
match response {
|
||||
ServerResponse::SelfAddress(recipient) => recipient,
|
||||
ServerResponse::SelfAddress(recipient) => *recipient,
|
||||
_ => panic!("received an unexpected response!"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_file_with_reply() {
|
||||
let uri = "ws://localhost:1977";
|
||||
let (mut ws_stream, _) = connect_async(uri).await.unwrap();
|
||||
|
||||
let recipient = get_self_address(&mut ws_stream).await;
|
||||
println!("our full address is: {}", recipient);
|
||||
|
||||
let read_data = std::fs::read("examples/dummy_file").unwrap();
|
||||
|
||||
let send_request = ClientRequest::Send {
|
||||
recipient,
|
||||
message: read_data,
|
||||
with_reply_surb: true,
|
||||
connection_id: Some(0),
|
||||
};
|
||||
|
||||
println!("sending content of 'dummy_file' over the mix network...");
|
||||
let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await;
|
||||
|
||||
let received = match response {
|
||||
ServerResponse::Received(received) => received,
|
||||
_ => panic!("received an unexpected response!"),
|
||||
};
|
||||
|
||||
println!("writing the file back to the disk!");
|
||||
std::fs::write("examples/received_file_withreply", received.message).unwrap();
|
||||
|
||||
let reply_message = b"hello from reply SURB! - thanks for sending me the file!".to_vec();
|
||||
let reply_request = ClientRequest::Reply {
|
||||
message: reply_message.clone(),
|
||||
reply_surb: received.reply_surb.unwrap(),
|
||||
};
|
||||
|
||||
println!(
|
||||
"sending {:?} (using reply SURB!) over the mix network...",
|
||||
String::from_utf8(reply_message).unwrap()
|
||||
);
|
||||
let response = send_message_and_get_response(&mut ws_stream, reply_request.serialize()).await;
|
||||
let received = match response {
|
||||
ServerResponse::Received(received) => received,
|
||||
_ => panic!("received an unexpected response!"),
|
||||
};
|
||||
|
||||
println!(
|
||||
"received {:#?} from the mix network!",
|
||||
String::from_utf8(received.message).unwrap()
|
||||
);
|
||||
todo!("reimplement surb usage here : )")
|
||||
// let uri = "ws://localhost:1977";
|
||||
// let (mut ws_stream, _) = connect_async(uri).await.unwrap();
|
||||
//
|
||||
// let recipient = get_self_address(&mut ws_stream).await;
|
||||
// println!("our full address is: {}", recipient);
|
||||
//
|
||||
// let read_data = std::fs::read("examples/dummy_file").unwrap();
|
||||
//
|
||||
// let send_request = ClientRequest::Send {
|
||||
// recipient,
|
||||
// message: read_data,
|
||||
// with_reply_surb: true,
|
||||
// connection_id: Some(0),
|
||||
// };
|
||||
//
|
||||
// println!("sending content of 'dummy_file' over the mix network...");
|
||||
// let response = send_message_and_get_response(&mut ws_stream, send_request.serialize()).await;
|
||||
//
|
||||
// let received = match response {
|
||||
// ServerResponse::Received(received) => received,
|
||||
// _ => panic!("received an unexpected response!"),
|
||||
// };
|
||||
//
|
||||
// println!("writing the file back to the disk!");
|
||||
// std::fs::write("examples/received_file_withreply", received.message).unwrap();
|
||||
//
|
||||
// let reply_message = b"hello from reply SURB! - thanks for sending me the file!".to_vec();
|
||||
// let reply_request = ClientRequest::Reply {
|
||||
// message: reply_message.clone(),
|
||||
// reply_surb: received.reply_surb.unwrap(),
|
||||
// };
|
||||
//
|
||||
// println!(
|
||||
// "sending {:?} (using reply SURB!) over the mix network...",
|
||||
// String::from_utf8(reply_message).unwrap()
|
||||
// );
|
||||
// let response = send_message_and_get_response(&mut ws_stream, reply_request.serialize()).await;
|
||||
// let received = match response {
|
||||
// ServerResponse::Received(received) => received,
|
||||
// _ => panic!("received an unexpected response!"),
|
||||
// };
|
||||
//
|
||||
// println!(
|
||||
// "received {:#?} from the mix network!",
|
||||
// String::from_utf8(received.message).unwrap()
|
||||
// );
|
||||
}
|
||||
|
||||
async fn send_file_without_reply() {
|
||||
@@ -91,7 +92,6 @@ async fn send_file_without_reply() {
|
||||
let send_request = ClientRequest::Send {
|
||||
recipient,
|
||||
message: read_data,
|
||||
with_reply_surb: false,
|
||||
connection_id: Some(0),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::template::config_template;
|
||||
use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::MISSING_VALUE;
|
||||
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
|
||||
use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT;
|
||||
use config::NymConfig;
|
||||
use config::{NymConfig, OptionalSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod template;
|
||||
|
||||
@@ -73,6 +76,12 @@ impl NymConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCoreConfigTrait for Config {
|
||||
fn get_gateway_endpoint(&self) -> &client_core::config::GatewayEndpointConfig {
|
||||
self.base.get_gateway_endpoint()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||
Config {
|
||||
@@ -86,6 +95,15 @@ impl Config {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_disabled_socket(mut self, disabled: bool) -> Self {
|
||||
if disabled {
|
||||
self.socket.socket_type = SocketType::None;
|
||||
} else {
|
||||
self.socket.socket_type = SocketType::WebSocket;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socket.listening_port = port;
|
||||
self
|
||||
@@ -104,6 +122,10 @@ impl Config {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_debug_settings(&self) -> &DebugConfig {
|
||||
self.get_base().get_debug_config()
|
||||
}
|
||||
|
||||
pub fn get_socket_type(&self) -> SocketType {
|
||||
self.socket.socket_type
|
||||
}
|
||||
@@ -111,6 +133,50 @@ impl Config {
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socket.listening_port
|
||||
}
|
||||
|
||||
// poor man's 'builder' method
|
||||
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
{
|
||||
self.base = f(self.base, val);
|
||||
self
|
||||
}
|
||||
|
||||
// helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config
|
||||
// (plz, lets refactor it)
|
||||
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
{
|
||||
self.base = self.base.with_optional(f, val);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
self.base = self.base.with_optional_env(f, val, env_var);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_custom_env_ext<F, T, G>(
|
||||
mut self,
|
||||
f: F,
|
||||
val: Option<T>,
|
||||
env_var: &str,
|
||||
parser: G,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
G: Fn(&str) -> T,
|
||||
{
|
||||
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
|
||||
@@ -23,16 +23,16 @@ id = '{{ client.id }}'
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to nymd validators via which the client can communicate with the chain.
|
||||
validator_urls = [
|
||||
{{#each client.validator_urls }}
|
||||
# Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
nyxd_urls = [
|
||||
{{#each client.nyxd_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls = [
|
||||
{{#each client.validator_api_urls }}
|
||||
nym_api_urls = [
|
||||
{{#each client.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
@@ -49,13 +49,12 @@ private_encryption_key_file = '{{ client.private_encryption_key_file }}'
|
||||
# Path to file containing public encryption key.
|
||||
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
|
||||
# Full path to file containing reply encryption keys of all reply-SURBs we have ever
|
||||
# sent but not received back.
|
||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::ClientError;
|
||||
use crate::websocket;
|
||||
use client_connections::TransmissionLane;
|
||||
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
|
||||
use client_core::client::base_client::{
|
||||
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
|
||||
};
|
||||
use client_core::client::inbound_messages::InputMessage;
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
|
||||
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use task::{wait_for_signal, ShutdownNotifier};
|
||||
use task::TaskManager;
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
@@ -47,18 +52,18 @@ impl SocketClient {
|
||||
let mut client_config =
|
||||
validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nymd_url = config
|
||||
let nyxd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nymd validator endpoint provided");
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_validator_api_endpoints()
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nymd_url, api_url);
|
||||
client_config = client_config.with_urls(nyxd_url, api_url);
|
||||
let client = validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
let coconut_api_clients =
|
||||
@@ -82,52 +87,51 @@ impl SocketClient {
|
||||
config: &Config,
|
||||
client_input: ClientInput,
|
||||
client_output: ClientOutput,
|
||||
self_address: Recipient,
|
||||
self_address: &Recipient,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: task::TaskClient,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
|
||||
let ClientInput {
|
||||
shared_lane_queue_lengths,
|
||||
connection_command_sender,
|
||||
input_sender,
|
||||
} = client_input;
|
||||
|
||||
let received_buffer_request_sender = client_output.received_buffer_request_sender;
|
||||
let ClientOutput {
|
||||
shared_lane_queue_lengths,
|
||||
received_buffer_request_sender,
|
||||
} = client_output;
|
||||
|
||||
let websocket_handler = websocket::Handler::new(
|
||||
let websocket_handler = websocket::HandlerBuilder::new(
|
||||
input_sender,
|
||||
connection_command_sender,
|
||||
received_buffer_request_sender,
|
||||
self_address,
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
websocket::Listener::new(config.get_listening_port()).start(websocket_handler);
|
||||
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_socket_forever(self) -> Result<(), ClientError> {
|
||||
let shutdown = self.start_socket().await?;
|
||||
wait_for_signal().await;
|
||||
pub async fn run_socket_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut shutdown = self.start_socket().await?;
|
||||
|
||||
println!(
|
||||
"Received signal - the client will terminate now (threads are not yet nicely stopped, if you see stack traces that's alright)."
|
||||
);
|
||||
let res = task::wait_for_signal_and_error(&mut shutdown).await;
|
||||
|
||||
log::info!("Sending shutdown");
|
||||
shutdown.signal_shutdown().ok();
|
||||
|
||||
// Some of these components have shutdown signalling implemented as part of socks5 work,
|
||||
// but since it's not fully implemented (yet) for all the components of the native client,
|
||||
// we don't try to wait and instead just stop immediately.
|
||||
//log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||
//shutdown.wait_for_shutdown().await;
|
||||
log::info!("Waiting for tasks to finish... (Press ctrl-c to force)");
|
||||
shutdown.wait_for_shutdown().await;
|
||||
|
||||
log::info!("Stopping nym-client");
|
||||
Ok(())
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<ShutdownNotifier, ClientError> {
|
||||
pub async fn start_socket(self) -> Result<TaskManager, ClientError> {
|
||||
if !self.config.get_socket_type().is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
@@ -136,6 +140,11 @@ impl SocketClient {
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
Some(Self::create_bandwidth_controller(&self.config).await),
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
self.config.get_base().get_reply_surb_database_path(),
|
||||
self.config.get_debug_settings(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let self_address = base_builder.as_mix_recipient();
|
||||
@@ -143,12 +152,19 @@ impl SocketClient {
|
||||
let client_input = started_client.client_input.register_producer();
|
||||
let client_output = started_client.client_output.register_consumer();
|
||||
|
||||
Self::start_websocket_listener(&self.config, client_input, client_output, self_address);
|
||||
Self::start_websocket_listener(
|
||||
&self.config,
|
||||
client_input,
|
||||
client_output,
|
||||
&self_address,
|
||||
started_client.reply_controller_sender,
|
||||
started_client.task_manager.subscribe(),
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self_address);
|
||||
|
||||
Ok(started_client.shutdown_notifier)
|
||||
Ok(started_client.task_manager)
|
||||
}
|
||||
|
||||
pub async fn start_direct(self) -> Result<DirectClient, ClientError> {
|
||||
@@ -160,6 +176,11 @@ impl SocketClient {
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
Some(Self::create_bandwidth_controller(&self.config).await),
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
self.config.get_base().get_reply_surb_database_path(),
|
||||
self.config.get_debug_settings(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let mut started_client = base_client.start_base().await?;
|
||||
@@ -180,7 +201,7 @@ impl SocketClient {
|
||||
Ok(DirectClient {
|
||||
client_input,
|
||||
reconstructed_receiver,
|
||||
_shutdown_notifier: started_client.shutdown_notifier,
|
||||
_shutdown_notifier: started_client.task_manager,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -190,21 +211,16 @@ pub struct DirectClient {
|
||||
reconstructed_receiver: ReconstructedMessagesReceiver,
|
||||
|
||||
// we need to keep reference to this guy otherwise things will start dropping
|
||||
_shutdown_notifier: ShutdownNotifier,
|
||||
_shutdown_notifier: TaskManager,
|
||||
}
|
||||
|
||||
impl DirectClient {
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
) {
|
||||
pub async fn send_regular_message(&mut self, recipient: Recipient, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_fresh(recipient, message, with_reply_surb, lane);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
@@ -216,8 +232,28 @@ impl DirectClient {
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_reply(&mut self, reply_surb: ReplySurb, message: Vec<u8>) {
|
||||
let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
pub async fn send_anonymous_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
/// EXPERIMENTAL DIRECT RUST API
|
||||
/// It's untested and there are absolutely no guarantees about it (but seems to have worked
|
||||
/// well enough in local tests)
|
||||
pub async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: Vec<u8>) {
|
||||
let lane = TransmissionLane::General;
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
|
||||
|
||||
self.client_input
|
||||
.input_sender
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Args;
|
||||
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Init {
|
||||
@@ -18,24 +22,26 @@ pub(crate) struct Init {
|
||||
|
||||
/// Id of the gateway we are going to connect to.
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nymd validators
|
||||
#[clap(long)]
|
||||
nymd_validators: Option<String>,
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long)]
|
||||
api_validators: Option<String>,
|
||||
#[clap(long, alias = "api_validators", value_delimiter = ',')]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Whether to not start the websocket
|
||||
#[clap(long)]
|
||||
disable_socket: bool,
|
||||
disable_socket: Option<bool>,
|
||||
|
||||
/// Port for the socket (if applicable) to listen on in all subsequent runs
|
||||
#[clap(short, long)]
|
||||
@@ -43,37 +49,65 @@ pub(crate) struct Init {
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
#[clap(long)]
|
||||
output_json: bool,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
nymd_validators: init_config.nymd_validators,
|
||||
api_validators: init_config.api_validators,
|
||||
nym_apis: init_config.nym_apis,
|
||||
disable_socket: init_config.disable_socket,
|
||||
port: init_config.port,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
nyxd_urls: init_config.nyxd_urls,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: client_core::init::InitResults,
|
||||
client_listening_port: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: client_core::init::InitResults::new(config.get_base(), address),
|
||||
client_listening_port: config.get_listening_port().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.client_core)?;
|
||||
write!(f, "Client listening port: {}", self.client_listening_port)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -97,25 +131,44 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
let user_chosen_gateway_id = args.gateway;
|
||||
|
||||
let mut config = Config::new(id);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
// Load and potentially override config
|
||||
let mut config = override_config(Config::new(id), OverrideConfig::from(args.clone()));
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = client_core::init::setup_gateway::<Config, _>(
|
||||
register_gateway,
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
config.get_base(),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{init_results}");
|
||||
|
||||
// Output summary to a json file, if specified
|
||||
if args.output_json {
|
||||
client_core::init::output_to_json(&init_results, "client_init_results.json");
|
||||
}
|
||||
|
||||
println!("\nThe address of this client is: {address}\n");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
@@ -124,63 +177,5 @@ pub(crate) async fn execute(args: &Init) {
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
id: &str,
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
if register {
|
||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
// the chosen one if it's among the available ones.
|
||||
println!("Configuring gateway");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
// valid for the gateway being used
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
println!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -1,53 +1,31 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, SocketType};
|
||||
use crate::error::ClientError;
|
||||
use crate::client::config::{BaseConfig, Config};
|
||||
use build_information::BinaryBuildInformation;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
use config::OptionalSet;
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
)
|
||||
lazy_static! {
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String =
|
||||
BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print();
|
||||
}
|
||||
|
||||
fn long_version_static() -> &'static str {
|
||||
Box::leak(long_version().into_boxed_str())
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(short, long)]
|
||||
@@ -75,75 +53,57 @@ pub(crate) enum Commands {
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
nymd_validators: Option<String>,
|
||||
api_validators: Option<String>,
|
||||
disable_socket: bool,
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
disable_socket: Option<bool>,
|
||||
port: Option<u16>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let bin_name = "nym-native-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Init(m) => init::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),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
if let Some(raw_validators) = args.nymd_validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validators(config::parse_validators(&raw_validators));
|
||||
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
|
||||
let raw_validators = std::env::var(network_defaults::var_names::NYMD_VALIDATOR)
|
||||
.expect("nymd validator not set");
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validators(config::parse_validators(&raw_validators));
|
||||
}
|
||||
if let Some(raw_validators) = args.api_validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(config::parse_validators(&raw_validators));
|
||||
} else if std::env::var(network_defaults::var_names::CONFIGURED).is_ok() {
|
||||
let raw_validators = std::env::var(network_defaults::var_names::API_VALIDATOR)
|
||||
.expect("api validator not set");
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(config::parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if args.disable_socket {
|
||||
config = config.with_socket(SocketType::None);
|
||||
}
|
||||
|
||||
if let Some(port) = args.port {
|
||||
config = config.with_port(port);
|
||||
}
|
||||
config = config
|
||||
.with_optional(Config::with_disabled_socket, args.disable_socket)
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
network_defaults::var_names::NYM_API,
|
||||
config::parse_urls,
|
||||
);
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
}
|
||||
|
||||
if args.fastmode {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
if args.no_cover {
|
||||
config.get_base_mut().set_no_cover_traffic();
|
||||
config = config
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nyxd,
|
||||
args.nyxd_urls,
|
||||
network_defaults::var_names::NYXD,
|
||||
config::parse_urls,
|
||||
)
|
||||
.with_optional_ext(
|
||||
BaseConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
);
|
||||
}
|
||||
|
||||
config
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use crate::{
|
||||
client::{config::Config, SocketClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
@@ -9,6 +11,7 @@ use crate::{
|
||||
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use log::*;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
@@ -18,22 +21,24 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
id: String,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nymd validators
|
||||
#[clap(long)]
|
||||
nymd_validators: Option<String>,
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long)]
|
||||
api_validators: Option<String>,
|
||||
#[clap(long, alias = "api_validators", value_delimiter = ',')]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
|
||||
/// ensure prior registration happened
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Whether to not start the websocket
|
||||
#[clap(long)]
|
||||
disable_socket: bool,
|
||||
disable_socket: Option<bool>,
|
||||
|
||||
/// Port for the socket to listen on
|
||||
#[clap(short, long)]
|
||||
@@ -41,29 +46,31 @@ pub(crate) struct Run {
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
nymd_validators: run_config.nymd_validators,
|
||||
api_validators: run_config.api_validators,
|
||||
nym_apis: run_config.nym_apis,
|
||||
disable_socket: run_config.disable_socket,
|
||||
port: run_config.port,
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
}
|
||||
@@ -89,23 +96,27 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
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 Err(ClientError::FailedToLoadConfig(id.to_string()));
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
return Err(Box::new(ClientError::FailedToLoadConfig(id.to_string())));
|
||||
}
|
||||
};
|
||||
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if config.get_base_mut().set_empty_fields_to_defaults() {
|
||||
warn!("some of the core config options were left unset. the default values are going to get used instead.");
|
||||
}
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(ClientError::FailedLocalVersionCheck);
|
||||
return Err(Box::new(ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
SocketClient::new(config).run_socket_forever().await
|
||||
|
||||
@@ -59,7 +59,7 @@ pub(crate) struct Upgrade {
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
|
||||
eprintln!("failed to parse client version! - {:?}", err);
|
||||
eprintln!("failed to parse client version! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
config.save_to_file(None).unwrap_or_else(|err| {
|
||||
eprintln!("failed to overwrite config file! - {:?}", err);
|
||||
eprintln!("failed to overwrite config file! - {err}");
|
||||
print_failed_upgrade(config_version, &to_version);
|
||||
process::exit(1);
|
||||
});
|
||||
@@ -146,7 +146,7 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {:?}", err);
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
|
||||
@@ -2,11 +2,15 @@ use client_core::error::ClientCoreError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[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,
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use clap::{crate_version, Parser};
|
||||
use error::ClientError;
|
||||
use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
|
||||
@@ -12,12 +13,12 @@ pub mod error;
|
||||
pub mod websocket;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), ClientError> {
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
|
||||
use client_core::client::{
|
||||
inbound_messages::{InputMessage, InputMessageSender},
|
||||
received_buffer::{
|
||||
@@ -14,9 +15,11 @@ use futures::channel::mpsc;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
use tokio_tungstenite::{
|
||||
accept_async,
|
||||
tungstenite::{protocol::Message as WsMessage, Error as WsError},
|
||||
@@ -35,19 +38,36 @@ impl Default for ReceivedResponseType {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Handler {
|
||||
pub(crate) struct HandlerBuilder {
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
socket: Option<WebSocketStream<TcpStream>>,
|
||||
received_response_type: ReceivedResponseType,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
// clone is used to use handler on a new connection, which initially is `None`
|
||||
impl Clone for Handler {
|
||||
fn clone(&self) -> Self {
|
||||
impl HandlerBuilder {
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: &Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
client_connection_tx,
|
||||
buffer_requester,
|
||||
self_full_address: *self_full_address,
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make sure we only ever have one active handler
|
||||
pub fn create_active_handler(&self) -> Handler {
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
client_connection_tx: self.client_connection_tx.clone(),
|
||||
@@ -56,106 +76,168 @@ impl Clone for Handler {
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Handler {
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
socket: Option<WebSocketStream<TcpStream>>,
|
||||
received_response_type: ReceivedResponseType,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
self.buffer_requester
|
||||
if self
|
||||
.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
.expect("the buffer request failed!")
|
||||
.is_err()
|
||||
{
|
||||
error!("we failed to disconnect the receiver from the buffer! presumably the shutdown procedure has been initiated!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub(crate) fn new(
|
||||
msg_input: InputMessageSender,
|
||||
client_connection_tx: ConnectionCommandSender,
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
) -> Self {
|
||||
Handler {
|
||||
msg_input,
|
||||
client_connection_tx,
|
||||
buffer_requester,
|
||||
self_full_address,
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
lane_queue_lengths,
|
||||
async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> Option<ServerResponse> {
|
||||
let req_start = Instant::now();
|
||||
|
||||
// get the base queue length
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
let conn_lane = TransmissionLane::ConnectionId(connection_id);
|
||||
let Ok(base_length) = self
|
||||
.lane_queue_lengths
|
||||
.lock()
|
||||
.map(|guard| guard.get(&conn_lane).unwrap_or_default()) else {
|
||||
// I'd argue we should panic here as this error it not recoverable
|
||||
error!("The lane queue length lock is poisoned!!");
|
||||
return None
|
||||
};
|
||||
|
||||
// get the number of pending replies waiting for reply surbs
|
||||
let reply_queue_length = self
|
||||
.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.await;
|
||||
|
||||
let queue_length = base_length + reply_queue_length;
|
||||
|
||||
let time_taken = req_start.elapsed();
|
||||
let msg =
|
||||
format!("it took {time_taken:?} to get lane length for connection {connection_id}. The length is: {queue_length} = {base_length} (already queued up) + {reply_queue_length} (waiting for reply SURBs)");
|
||||
|
||||
if time_taken > Duration::from_millis(1) {
|
||||
info!("{msg}");
|
||||
} else if time_taken > Duration::from_millis(10) {
|
||||
warn!("{msg}");
|
||||
} else if time_taken > Duration::from_millis(50) {
|
||||
error!("{msg}");
|
||||
}
|
||||
|
||||
Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_send(
|
||||
&mut self,
|
||||
recipient: &Recipient,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
connection_id: Option<u64>,
|
||||
) -> Option<ServerResponse> {
|
||||
info!(
|
||||
"Attempting to send {:.2} kiB message to {recipient} on connection_id {connection_id:?}",
|
||||
message.len() as f64 / 1024.0
|
||||
);
|
||||
|
||||
// We map the absence of a connection id as going into the general lane.
|
||||
let lane = connection_id.map_or(TransmissionLane::General, |id| {
|
||||
TransmissionLane::ConnectionId(id)
|
||||
});
|
||||
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_fresh(*recipient, message, with_reply_surb, lane);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let connection_id = match lane {
|
||||
TransmissionLane::General
|
||||
| TransmissionLane::Reply
|
||||
| TransmissionLane::Retransmission
|
||||
| TransmissionLane::Control => return None,
|
||||
TransmissionLane::ConnectionId(id) => id,
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
// on receiving a send, we reply back the current lane queue length for that connection id.
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
|
||||
log::warn!(
|
||||
"Failed to get the lane queue length lock, \
|
||||
not responding back with the current queue length"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
Some(ServerResponse::LaneQueueLength(connection_id, queue_length))
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_reply(
|
||||
async fn handle_send_anonymous(
|
||||
&mut self,
|
||||
reply_surb: ReplySurb,
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
connection_id: Option<u64>,
|
||||
) -> Option<ServerResponse> {
|
||||
if message.len() > ReplySurb::max_msg_len(Default::default()) {
|
||||
return Some(
|
||||
ServerResponse::new_error(
|
||||
format!(
|
||||
"too long message to put inside a reply SURB. Received: {} bytes and maximum is {} bytes",
|
||||
message.len(), ReplySurb::max_msg_len(Default::default()))
|
||||
)
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Attempting to anonymously send {:.2} kiB message to {recipient} on connection_id {connection_id:?} while attaching {reply_surbs} replySURBs.",
|
||||
message.len() as f64 / 1024.0
|
||||
);
|
||||
|
||||
let input_msg = InputMessage::new_reply(reply_surb, message);
|
||||
// We map the absence of a connection id as going into the general lane.
|
||||
let lane = connection_id.map_or(TransmissionLane::General, |id| {
|
||||
TransmissionLane::ConnectionId(id)
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_anonymous(recipient, message, reply_surbs, lane);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
None
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_reply(
|
||||
&mut self,
|
||||
recipient_tag: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
connection_id: Option<u64>,
|
||||
) -> Option<ServerResponse> {
|
||||
info!("Attempting to send {:.2} kiB reply message to {recipient_tag} on connection_id {connection_id:?}", message.len() as f64 / 1024.0);
|
||||
|
||||
// We map the absence of a connection id as going into the general lane.
|
||||
let lane = connection_id.map_or(TransmissionLane::General, |id| {
|
||||
TransmissionLane::ConnectionId(id)
|
||||
});
|
||||
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane);
|
||||
self.msg_input
|
||||
.send(input_msg)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
fn handle_self_address(&self) -> ServerResponse {
|
||||
ServerResponse::SelfAddress(self.self_full_address)
|
||||
ServerResponse::SelfAddress(Box::new(self.self_full_address))
|
||||
}
|
||||
|
||||
fn handle_closed_connection(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
@@ -165,17 +247,8 @@ impl Handler {
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
|
||||
log::warn!(
|
||||
"Failed to get the lane queue length lock, not responding back with the current queue length"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let lane = TransmissionLane::ConnectionId(connection_id);
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
Some(ServerResponse::LaneQueueLength(connection_id, queue_length))
|
||||
async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
|
||||
@@ -183,19 +256,28 @@ impl Handler {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => self.handle_send(recipient, message, connection_id).await,
|
||||
|
||||
ClientRequest::SendAnonymous {
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
connection_id,
|
||||
} => {
|
||||
self.handle_send(&recipient, message, with_reply_surb, connection_id)
|
||||
self.handle_send_anonymous(recipient, message, reply_surbs, connection_id)
|
||||
.await
|
||||
}
|
||||
|
||||
ClientRequest::Reply {
|
||||
message,
|
||||
reply_surb,
|
||||
} => self.handle_reply(reply_surb, message).await,
|
||||
sender_tag,
|
||||
connection_id,
|
||||
} => self.handle_reply(sender_tag, message, connection_id).await,
|
||||
|
||||
ClientRequest::SelfAddress => Some(self.handle_self_address()),
|
||||
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
|
||||
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id),
|
||||
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,8 +358,12 @@ impl Handler {
|
||||
}
|
||||
}
|
||||
|
||||
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||
loop {
|
||||
async fn listen_for_requests(
|
||||
&mut self,
|
||||
mut msg_receiver: ReconstructedMessagesReceiver,
|
||||
mut task_client: task::TaskClient,
|
||||
) {
|
||||
while !task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
// we can either get a client request from the websocket
|
||||
socket_msg = self.next_websocket_request() => {
|
||||
@@ -287,7 +373,7 @@ impl Handler {
|
||||
let socket_msg = match socket_msg.unwrap() {
|
||||
Ok(socket_msg) => socket_msg,
|
||||
Err(err) => {
|
||||
warn!("failed to obtain message from websocket stream! stopping connection handler: {}", err);
|
||||
warn!("failed to obtain message from websocket stream! stopping connection handler: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -299,8 +385,7 @@ impl Handler {
|
||||
if let Some(response) = self.handle_ws_request(socket_msg).await {
|
||||
if let Err(err) = self.send_websocket_response(response).await {
|
||||
warn!(
|
||||
"Failed to send message over websocket: {}. Assuming the connection is dead.",
|
||||
err
|
||||
"Failed to send message over websocket: {err}. Assuming the connection is dead.",
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -308,24 +393,37 @@ impl Handler {
|
||||
}
|
||||
// or a reconstructed mix message that we need to push back to the client
|
||||
mix_messages = msg_receiver.next() => {
|
||||
let mix_messages = mix_messages.expect(
|
||||
"mix messages sender was unexpectedly closed! this shouldn't have ever happened!",
|
||||
);
|
||||
if let Err(e) = self.push_websocket_received_plaintexts(mix_messages).await {
|
||||
warn!("failed to send sphinx packets back to the client - {:?}, assuming the connection is dead", e);
|
||||
let Some(mix_messages) = mix_messages else {
|
||||
error!("mix messages sender was unexpectedly closed! this shouldn't have ever happened! (unless we're shutting down - TODO: implement proper graceful shutdown handler)");
|
||||
return
|
||||
};
|
||||
if let Err(err) = self.push_websocket_received_plaintexts(mix_messages).await {
|
||||
warn!("failed to send sphinx packets back to the client - {err}, assuming the connection is dead");
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("Websocket handler: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Websocket handler: Exiting");
|
||||
}
|
||||
|
||||
// consume self to make sure `drop` is called after this is done
|
||||
pub(crate) async fn handle_connection(mut self, socket: TcpStream) {
|
||||
pub(crate) async fn handle_connection(
|
||||
mut self,
|
||||
socket: TcpStream,
|
||||
mut task_client: task::TaskClient,
|
||||
) {
|
||||
// We don't want a crash in the connection handler to trigger a shutdown of the whole
|
||||
// process.
|
||||
task_client.mark_as_success();
|
||||
|
||||
let ws_stream = match accept_async(socket).await {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(err) => {
|
||||
warn!("error while performing the websocket handshake - {:?}", err);
|
||||
warn!("error while performing the websocket handshake - {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -340,7 +438,8 @@ impl Handler {
|
||||
))
|
||||
.expect("the buffer request failed!");
|
||||
|
||||
self.listen_for_requests(reconstructed_receiver).await;
|
||||
self.listen_for_requests(reconstructed_receiver, task_client)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::handler::Handler;
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -32,11 +32,11 @@ impl Listener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self, handler: Handler) {
|
||||
pub(crate) async fn run(&mut self, handler: HandlerBuilder, mut task_client: task::TaskClient) {
|
||||
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
error!("Failed to bind to {} - {}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address, err);
|
||||
error!("Failed to bind to {} - {err}. Are you sure nothing else is running on the specified port and your user has sufficient permission to bind to the requested address?", self.address);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
@@ -45,18 +45,30 @@ impl Listener {
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// When the handler finishes we check if shutdown is signalled
|
||||
_ = notify.notified() => {
|
||||
if task_client.is_shutdown() {
|
||||
log::trace!("Websocket listener: detected shutdown after connection closed");
|
||||
break;
|
||||
}
|
||||
// our connection terminated - we are open to a new one now!
|
||||
self.state = State::AwaitingConnection;
|
||||
}
|
||||
// ... but when there is no connected client at the time of shutdown being
|
||||
// signalled, we handle it here.
|
||||
_ = task_client.recv() => {
|
||||
if !self.state.is_connected() {
|
||||
log::trace!("Not connected: shutting down");
|
||||
break;
|
||||
}
|
||||
}
|
||||
new_conn = tcp_listener.accept() => {
|
||||
match new_conn {
|
||||
Ok((mut socket, remote_addr)) => {
|
||||
debug!("Received connection from {:?}", remote_addr);
|
||||
if self.state.is_connected() {
|
||||
warn!("tried to duplicate!");
|
||||
warn!("Tried to open a duplicate websocket connection. The request came from {}", remote_addr);
|
||||
// if we've already got a connection, don't allow another one
|
||||
debug!("but there was already a connection present!");
|
||||
// while we only ever want to accept a single connection, we don't want
|
||||
// to leave clients hanging (and also allow for reconnection if it somehow
|
||||
// was dropped)
|
||||
@@ -64,31 +76,37 @@ impl Listener {
|
||||
Ok(_) => trace!(
|
||||
"closed the connection between attempting websocket handshake"
|
||||
),
|
||||
Err(e) => warn!("failed to cleanly close the connection - {:?}", e),
|
||||
Err(err) => warn!("failed to cleanly close the connection - {err}"),
|
||||
};
|
||||
} else {
|
||||
// even though we're spawning a new task with the handler here, we will only ever spawn a single one.
|
||||
// it's done so that any new connections to this listener could be rejected rather than left
|
||||
// hanging because the executor doesn't come back here
|
||||
let notify_clone = Arc::clone(¬ify);
|
||||
let fresh_handler = handler.clone();
|
||||
let fresh_handler = handler.create_active_handler();
|
||||
let task_client_handler = task_client.clone();
|
||||
tokio::spawn(async move {
|
||||
fresh_handler.handle_connection(socket).await;
|
||||
fresh_handler.handle_connection(socket, task_client_handler).await;
|
||||
notify_clone.notify_one();
|
||||
});
|
||||
self.state = State::Connected;
|
||||
}
|
||||
}
|
||||
Err(e) => warn!("failed to get client: {:?}", e),
|
||||
Err(err) => warn!("failed to get client: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Websocket listener: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> {
|
||||
pub(crate) fn start(
|
||||
mut self,
|
||||
handler: HandlerBuilder,
|
||||
shutdown: task::TaskClient,
|
||||
) -> JoinHandle<()> {
|
||||
info!("Running websocket on {:?}", self.address.to_string());
|
||||
|
||||
tokio::spawn(async move { self.run(handler).await })
|
||||
tokio::spawn(async move { self.run(handler, shutdown).await })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) use handler::Handler;
|
||||
pub(crate) use handler::HandlerBuilder;
|
||||
pub(crate) use listener::Listener;
|
||||
|
||||
pub(crate) mod handler;
|
||||
|
||||
@@ -24,8 +24,11 @@ impl fmt::Debug for Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(kind: ErrorKind, message: String) -> Self {
|
||||
Error { kind, message }
|
||||
pub fn new<S: Into<String>>(kind: ErrorKind, message: S) -> Self {
|
||||
Error {
|
||||
kind,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +65,31 @@ pub enum ErrorKind {
|
||||
Other = 0xFF,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ErrorKind {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
_ if value == (ErrorKind::EmptyRequest as u8) => Ok(ErrorKind::EmptyRequest),
|
||||
_ if value == (ErrorKind::TooShortRequest as u8) => Ok(ErrorKind::TooShortRequest),
|
||||
_ if value == (ErrorKind::UnknownRequest as u8) => Ok(ErrorKind::UnknownRequest),
|
||||
_ if value == (ErrorKind::MalformedRequest as u8) => Ok(ErrorKind::MalformedRequest),
|
||||
|
||||
_ if value == (ErrorKind::EmptyResponse as u8) => Ok(ErrorKind::EmptyResponse),
|
||||
_ if value == (ErrorKind::TooShortResponse as u8) => Ok(ErrorKind::TooShortResponse),
|
||||
_ if value == (ErrorKind::UnknownResponse as u8) => Ok(ErrorKind::UnknownResponse),
|
||||
_ if value == (ErrorKind::MalformedResponse as u8) => Ok(ErrorKind::MalformedResponse),
|
||||
|
||||
_ if value == (ErrorKind::Other as u8) => Ok(ErrorKind::Other),
|
||||
|
||||
n => Err(Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("invalid error code {}", n),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorKind {
|
||||
pub(crate) fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all variable size data is always prefixed with u64 length
|
||||
@@ -7,69 +7,115 @@
|
||||
use crate::error::{self, ErrorKind};
|
||||
use crate::text::ClientRequestText;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::mem::size_of;
|
||||
|
||||
/// Value tag representing [`Send`] variant of the [`ClientRequest`]
|
||||
pub const SEND_REQUEST_TAG: u8 = 0x00;
|
||||
#[repr(u8)]
|
||||
enum ClientRequestTag {
|
||||
/// Value tag representing [`Send`] variant of the [`ClientRequest`]
|
||||
Send = 0x00,
|
||||
|
||||
/// Value tag representing [`Reply`] variant of the [`ClientRequest`]
|
||||
pub const REPLY_REQUEST_TAG: u8 = 0x01;
|
||||
/// Value tag representing [`SendAnonymous`] variant of the [`ClientRequest`]
|
||||
SendAnonymous = 0x01,
|
||||
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
|
||||
pub const SELF_ADDRESS_REQUEST_TAG: u8 = 0x02;
|
||||
/// Value tag representing [`Reply`] variant of the [`ClientRequest`]
|
||||
Reply = 0x02,
|
||||
|
||||
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
|
||||
pub const CLOSED_CONNECTION_REQUEST_TAG: u8 = 0x03;
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ClientRequest`]
|
||||
SelfAddress = 0x03,
|
||||
|
||||
/// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`]
|
||||
pub const GET_LANE_QUEUE_LENGHT_TAG: u8 = 0x04;
|
||||
/// Value tag representing [`ClosedConnection`] variant of the [`ClientRequest`]
|
||||
ClosedConnection = 0x04,
|
||||
|
||||
/// Value tag representing [`GetLaneQueueLength`] variant of the [`ClientRequest`]
|
||||
GetLaneQueueLength = 0x05,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ClientRequestTag {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
_ if value == (Self::Send as u8) => Ok(Self::Send),
|
||||
_ if value == (Self::SendAnonymous as u8) => Ok(Self::SendAnonymous),
|
||||
_ if value == (Self::Reply as u8) => Ok(Self::Reply),
|
||||
_ if value == (Self::SelfAddress as u8) => Ok(Self::SelfAddress),
|
||||
_ if value == (Self::ClosedConnection as u8) => Ok(Self::ClosedConnection),
|
||||
_ if value == (Self::GetLaneQueueLength as u8) => Ok(Self::GetLaneQueueLength),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownRequest,
|
||||
format!("{n} does not correspond to any valid request tag"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug)]
|
||||
pub enum ClientRequest {
|
||||
/// The simplest message variant where no additional information is attached.
|
||||
/// You're simply sending your `data` to specified `recipient` without any tagging.
|
||||
///
|
||||
/// Ends up with `NymMessage::Plain` variant
|
||||
Send {
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
// Perhaps we could change it to a number to indicate how many reply_SURBs we want to include?
|
||||
with_reply_surb: bool,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
Reply {
|
||||
|
||||
/// Create a message used for a duplex anonymous communication where the recipient
|
||||
/// will never learn of our true identity. This is achieved by carefully sending `reply_surbs`.
|
||||
///
|
||||
/// Note that if reply_surbs is set to zero then
|
||||
/// this variant requires the client having sent some reply_surbs in the past
|
||||
/// (and thus the recipient also knowing our sender tag).
|
||||
///
|
||||
/// Ends up with `NymMessage::Repliable` variant
|
||||
SendAnonymous {
|
||||
recipient: Recipient,
|
||||
message: Vec<u8>,
|
||||
reply_surb: ReplySurb,
|
||||
reply_surbs: u32,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
|
||||
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||
/// to specified recipient whilst not knowing its full identity (or even gateway).
|
||||
///
|
||||
/// Ends up with `NymMessage::Reply` variant
|
||||
Reply {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
|
||||
SelfAddress,
|
||||
|
||||
ClosedConnection(u64),
|
||||
|
||||
GetLaneQueueLength(u64),
|
||||
}
|
||||
|
||||
// we could have been parsing it directly TryFrom<WsMessage>, but we want to retain
|
||||
// information about whether it came from binary or text to send appropriate response back
|
||||
impl ClientRequest {
|
||||
// SEND_REQUEST_TAG || with_surb || recipient || conn_id || data_len || data
|
||||
fn serialize_send(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
with_reply_surb: bool,
|
||||
connection_id: Option<u64>,
|
||||
) -> Vec<u8> {
|
||||
// SEND_REQUEST_TAG || recipient || conn_id || data_len || data
|
||||
fn serialize_send(recipient: Recipient, data: Vec<u8>, connection_id: Option<u64>) -> Vec<u8> {
|
||||
let data_len_bytes = (data.len() as u64).to_be_bytes();
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
std::iter::once(SEND_REQUEST_TAG)
|
||||
.chain(std::iter::once(with_reply_surb as u8))
|
||||
.chain(recipient.to_bytes().iter().cloned()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.iter().cloned())
|
||||
.chain(data_len_bytes.iter().cloned())
|
||||
|
||||
std::iter::once(ClientRequestTag::Send as u8)
|
||||
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(data_len_bytes.into_iter())
|
||||
.chain(data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// SEND_REQUEST_TAG || with_reply || recipient || conn_id || data_len || data
|
||||
// SEND_REQUEST_TAG || recipient || conn_id || data_len || data
|
||||
fn deserialize_send(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// we need to have at least 1 (tag) + 1 (reply flag) + Recipient::LEN + 2*sizeof<u64> bytes
|
||||
if b.len() < 2 + Recipient::LEN + 2 * size_of::<u64>() {
|
||||
// we need to have at least 1 (tag) + Recipient::LEN + 2*sizeof<u64> bytes
|
||||
if b.len() < 1 + Recipient::LEN + 2 * size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortRequest,
|
||||
"not enough data provided to recover 'send'".to_string(),
|
||||
@@ -77,34 +123,23 @@ impl ClientRequest {
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], SEND_REQUEST_TAG);
|
||||
|
||||
let with_reply_surb = match b[1] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
n => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!("invalid reply surb flag {}", n),
|
||||
))
|
||||
}
|
||||
};
|
||||
debug_assert_eq!(b[0], ClientRequestTag::Send as u8);
|
||||
|
||||
let mut recipient_bytes = [0u8; Recipient::LEN];
|
||||
recipient_bytes.copy_from_slice(&b[2..2 + Recipient::LEN]);
|
||||
recipient_bytes.copy_from_slice(&b[1..1 + Recipient::LEN]);
|
||||
let recipient = match Recipient::try_from_bytes(recipient_bytes) {
|
||||
Ok(recipient) => recipient,
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!("malformed recipient: {:?}", err),
|
||||
format!("malformed recipient: {err}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes
|
||||
.copy_from_slice(&b[2 + Recipient::LEN..2 + Recipient::LEN + size_of::<u64>()]);
|
||||
.copy_from_slice(&b[1 + Recipient::LEN..1 + Recipient::LEN + size_of::<u64>()]);
|
||||
let connection_id = u64::from_be_bytes(connection_id_bytes);
|
||||
let connection_id = if connection_id == 0 {
|
||||
None
|
||||
@@ -113,9 +148,9 @@ impl ClientRequest {
|
||||
};
|
||||
|
||||
let data_len_bytes =
|
||||
&b[2 + Recipient::LEN + size_of::<u64>()..2 + Recipient::LEN + 2 * size_of::<u64>()];
|
||||
&b[1 + Recipient::LEN + size_of::<u64>()..1 + Recipient::LEN + 2 * size_of::<u64>()];
|
||||
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
|
||||
let data = &b[2 + Recipient::LEN + 2 * size_of::<u64>()..];
|
||||
let data = &b[1 + Recipient::LEN + 2 * size_of::<u64>()..];
|
||||
if data.len() as u64 != data_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
@@ -128,33 +163,111 @@ impl ClientRequest {
|
||||
}
|
||||
|
||||
Ok(ClientRequest::Send {
|
||||
with_reply_surb,
|
||||
recipient,
|
||||
message: data.to_vec(),
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
|
||||
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
|
||||
fn serialize_reply(message: Vec<u8>, reply_surb: &ReplySurb) -> Vec<u8> {
|
||||
let reply_surb_bytes = reply_surb.to_bytes();
|
||||
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
|
||||
let message_len_bytes = (message.len() as u64).to_be_bytes();
|
||||
// SEND_ANONYMOUS_REQUEST_TAG || reply_surbs || recipient || conn_id || data_len || data
|
||||
fn serialize_send_anonymous(
|
||||
recipient: Recipient,
|
||||
data: Vec<u8>,
|
||||
reply_surbs: u32,
|
||||
connection_id: Option<u64>,
|
||||
) -> Vec<u8> {
|
||||
let data_len_bytes = (data.len() as u64).to_be_bytes();
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
|
||||
std::iter::once(REPLY_REQUEST_TAG)
|
||||
.chain(surb_len_bytes.iter().cloned())
|
||||
.chain(reply_surb_bytes.into_iter())
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
std::iter::once(ClientRequestTag::SendAnonymous as u8)
|
||||
.chain(reply_surbs.to_be_bytes().into_iter())
|
||||
.chain(recipient.to_bytes().into_iter()) // will not be length prefixed because the length is constant
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(data_len_bytes.into_iter())
|
||||
.chain(data.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// SEND_ANONYMOUS_REQUEST_TAG || reply_surbs || recipient || data_len || data
|
||||
fn deserialize_send_anonymous(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// we need to have at least 1 (tag) + sizeof<u32> (num surbs) + Recipient::LEN + 2 *sizeof<u64> bytes
|
||||
if b.len() < 1 + size_of::<u32>() + Recipient::LEN + 2 * size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortRequest,
|
||||
"not enough data provided to recover 'send_anonymous'".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], ClientRequestTag::SendAnonymous as u8);
|
||||
|
||||
let reply_surbs = u32::from_be_bytes([b[1], b[2], b[3], b[4]]);
|
||||
|
||||
let mut recipient_bytes = [0u8; Recipient::LEN];
|
||||
recipient_bytes.copy_from_slice(&b[5..5 + Recipient::LEN]);
|
||||
let recipient = match Recipient::try_from_bytes(recipient_bytes) {
|
||||
Ok(recipient) => recipient,
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!("malformed recipient: {err}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes
|
||||
.copy_from_slice(&b[5 + Recipient::LEN..5 + Recipient::LEN + size_of::<u64>()]);
|
||||
let connection_id = u64::from_be_bytes(connection_id_bytes);
|
||||
let connection_id = if connection_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(connection_id)
|
||||
};
|
||||
|
||||
let data_len_bytes =
|
||||
&b[5 + Recipient::LEN + size_of::<u64>()..5 + Recipient::LEN + 2 * size_of::<u64>()];
|
||||
let data_len = u64::from_be_bytes(data_len_bytes.try_into().unwrap());
|
||||
let data = &b[5 + Recipient::LEN + 2 * size_of::<u64>()..];
|
||||
if data.len() as u64 != data_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!(
|
||||
"data len has inconsistent length. specified: {} got: {}",
|
||||
data_len,
|
||||
data.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ClientRequest::SendAnonymous {
|
||||
reply_surbs,
|
||||
recipient,
|
||||
message: data.to_vec(),
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
|
||||
// REPLY_REQUEST_TAG || SENDER_TAG || conn_id || message_len || message
|
||||
fn serialize_reply(
|
||||
message: Vec<u8>,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
connection_id: Option<u64>,
|
||||
) -> Vec<u8> {
|
||||
let message_len_bytes = (message.len() as u64).to_be_bytes();
|
||||
let conn_id_bytes = connection_id.unwrap_or(0).to_be_bytes();
|
||||
|
||||
std::iter::once(ClientRequestTag::Reply as u8)
|
||||
.chain(sender_tag.to_bytes().into_iter())
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.chain(message_len_bytes.into_iter())
|
||||
.chain(message.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// REPLY_REQUEST_TAG || surb_len || surb || message_len || message
|
||||
// REPLY_REQUEST_TAG || SENDER_TAG || conn_id || message_len || message
|
||||
fn deserialize_reply(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// we need to have at the very least 2 * sizeof<u64> bytes (in case, for some peculiar reason
|
||||
// message and reply surb were 0 len - the request would still be malformed, but would in theory
|
||||
// be parse'able)
|
||||
if b.len() < 1 + 2 * size_of::<u64>() {
|
||||
if b.len() < 1 + SENDER_TAG_SIZE + 2 * size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortRequest,
|
||||
"not enough data provided to recover 'reply'".to_string(),
|
||||
@@ -162,42 +275,28 @@ impl ClientRequest {
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], REPLY_REQUEST_TAG);
|
||||
debug_assert_eq!(b[0], ClientRequestTag::Reply as u8);
|
||||
|
||||
let reply_surb_len =
|
||||
u64::from_be_bytes(b[1..1 + size_of::<u64>()].as_ref().try_into().unwrap());
|
||||
// the unwrap here is fine as we're definitely using exactly SENDER_TAG_SIZE bytes
|
||||
let sender_tag =
|
||||
AnonymousSenderTag::from_bytes(b[1..1 + SENDER_TAG_SIZE].try_into().unwrap());
|
||||
|
||||
// make sure we won't go out of bounds here
|
||||
if reply_surb_len > (b.len() - 1 + 2 * size_of::<u64>()) as u64 {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!(
|
||||
"not enough data to recover reply surb with specified length {}",
|
||||
reply_surb_len
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let surb_bound = 1 + size_of::<u64>() + reply_surb_len as usize;
|
||||
|
||||
let reply_surb_bytes = &b[1 + size_of::<u64>()..surb_bound];
|
||||
let reply_surb = match ReplySurb::from_bytes(reply_surb_bytes) {
|
||||
Ok(reply_surb) => reply_surb,
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
format!("malformed reply surb: {:?}", err),
|
||||
))
|
||||
}
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes
|
||||
.copy_from_slice(&b[1 + SENDER_TAG_SIZE..1 + SENDER_TAG_SIZE + size_of::<u64>()]);
|
||||
let connection_id = u64::from_be_bytes(connection_id_bytes);
|
||||
let connection_id = if connection_id == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(connection_id)
|
||||
};
|
||||
|
||||
let message_len = u64::from_be_bytes(
|
||||
b[surb_bound..surb_bound + size_of::<u64>()]
|
||||
.as_ref()
|
||||
b[1 + SENDER_TAG_SIZE + size_of::<u64>()..1 + SENDER_TAG_SIZE + 2 * size_of::<u64>()]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let message = &b[surb_bound + size_of::<u64>()..];
|
||||
let message = &b[1 + SENDER_TAG_SIZE + 2 * size_of::<u64>()..];
|
||||
if message.len() as u64 != message_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
@@ -208,33 +307,32 @@ impl ClientRequest {
|
||||
),
|
||||
));
|
||||
}
|
||||
// TODO: should this blow HERE, i.e. during deserialization that the data you're trying
|
||||
// to send via reply is too long?
|
||||
|
||||
Ok(ClientRequest::Reply {
|
||||
reply_surb,
|
||||
message: message.to_vec(),
|
||||
sender_tag,
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
|
||||
// SELF_ADDRESS_REQUEST_TAG
|
||||
fn serialize_self_address() -> Vec<u8> {
|
||||
std::iter::once(SELF_ADDRESS_REQUEST_TAG).collect()
|
||||
vec![ClientRequestTag::SelfAddress as u8]
|
||||
}
|
||||
|
||||
// SELF_ADDRESS_REQUEST_TAG
|
||||
fn deserialize_self_address(b: &[u8]) -> Self {
|
||||
fn deserialize_self_address(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], SELF_ADDRESS_REQUEST_TAG);
|
||||
debug_assert_eq!(b[0], ClientRequestTag::SelfAddress as u8);
|
||||
|
||||
ClientRequest::SelfAddress
|
||||
Ok(ClientRequest::SelfAddress)
|
||||
}
|
||||
|
||||
// CLOSED_CONNECTION_REQUEST_TAG
|
||||
fn serialize_closed_connection(connection_id: u64) -> Vec<u8> {
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(CLOSED_CONNECTION_REQUEST_TAG)
|
||||
.chain(conn_id_bytes.iter().copied())
|
||||
std::iter::once(ClientRequestTag::ClosedConnection as u8)
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -243,12 +341,12 @@ impl ClientRequest {
|
||||
if b.len() != 1 + size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
"the received closed connection has invalid length".to_string(),
|
||||
"The received closed connection has invalid length",
|
||||
));
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], CLOSED_CONNECTION_REQUEST_TAG);
|
||||
debug_assert_eq!(b[0], ClientRequestTag::ClosedConnection as u8);
|
||||
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
|
||||
@@ -260,8 +358,8 @@ impl ClientRequest {
|
||||
// GET_LANE_QUEUE_LENGHT_TAG
|
||||
fn serialize_get_lane_queue_lengths(connection_id: u64) -> Vec<u8> {
|
||||
let conn_id_bytes = connection_id.to_be_bytes();
|
||||
std::iter::once(GET_LANE_QUEUE_LENGHT_TAG)
|
||||
.chain(conn_id_bytes.iter().copied())
|
||||
std::iter::once(ClientRequestTag::GetLaneQueueLength as u8)
|
||||
.chain(conn_id_bytes.into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -270,12 +368,12 @@ impl ClientRequest {
|
||||
if b.len() != 1 + size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedRequest,
|
||||
"the received get lane queue length has invalid length".to_string(),
|
||||
"The received get lane queue lengths has invalid length",
|
||||
));
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], GET_LANE_QUEUE_LENGHT_TAG);
|
||||
debug_assert_eq!(b[0], ClientRequestTag::GetLaneQueueLength as u8);
|
||||
|
||||
let mut connection_id_bytes = [0u8; size_of::<u64>()];
|
||||
connection_id_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
|
||||
@@ -289,14 +387,21 @@ impl ClientRequest {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => Self::serialize_send(recipient, message, with_reply_surb, connection_id),
|
||||
} => Self::serialize_send(recipient, message, connection_id),
|
||||
|
||||
ClientRequest::SendAnonymous {
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
connection_id,
|
||||
} => Self::serialize_send_anonymous(recipient, message, reply_surbs, connection_id),
|
||||
|
||||
ClientRequest::Reply {
|
||||
message,
|
||||
reply_surb,
|
||||
} => Self::serialize_reply(message, &reply_surb),
|
||||
sender_tag,
|
||||
connection_id,
|
||||
} => Self::serialize_reply(message, sender_tag, connection_id),
|
||||
|
||||
ClientRequest::SelfAddress => Self::serialize_self_address(),
|
||||
|
||||
@@ -316,28 +421,16 @@ impl ClientRequest {
|
||||
));
|
||||
}
|
||||
|
||||
if b.len() < size_of::<u8>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortRequest,
|
||||
format!(
|
||||
"not enough data provided to recover request tag. Provided only {} bytes",
|
||||
b.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
let request_tag = b[0];
|
||||
let request_tag = ClientRequestTag::try_from(b[0])?;
|
||||
|
||||
// determine what kind of request that is and try to deserialize it
|
||||
match request_tag {
|
||||
SEND_REQUEST_TAG => Self::deserialize_send(b),
|
||||
REPLY_REQUEST_TAG => Self::deserialize_reply(b),
|
||||
SELF_ADDRESS_REQUEST_TAG => Ok(Self::deserialize_self_address(b)),
|
||||
CLOSED_CONNECTION_REQUEST_TAG => Self::deserialize_closed_connection(b),
|
||||
GET_LANE_QUEUE_LENGHT_TAG => Self::deserialize_get_lane_queue_length(b),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownRequest,
|
||||
format!("type {n}"),
|
||||
)),
|
||||
ClientRequestTag::Send => Self::deserialize_send(b),
|
||||
ClientRequestTag::SendAnonymous => Self::deserialize_send_anonymous(b),
|
||||
ClientRequestTag::Reply => Self::deserialize_reply(b),
|
||||
ClientRequestTag::SelfAddress => Self::deserialize_self_address(b),
|
||||
ClientRequestTag::ClosedConnection => Self::deserialize_closed_connection(b),
|
||||
ClientRequestTag::GetLaneQueueLength => Self::deserialize_get_lane_queue_length(b),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,50 +458,52 @@ mod tests {
|
||||
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
|
||||
let recipient_string = recipient.to_string();
|
||||
|
||||
let send_request_no_surb = ClientRequest::Send {
|
||||
let send_request = ClientRequest::Send {
|
||||
recipient,
|
||||
message: b"foomp".to_vec(),
|
||||
with_reply_surb: false,
|
||||
connection_id: Some(42),
|
||||
};
|
||||
|
||||
let bytes = send_request_no_surb.serialize();
|
||||
let bytes = send_request.serialize();
|
||||
let recovered = ClientRequest::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ClientRequest::Send {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
assert_eq!(recipient.to_string(), recipient_string);
|
||||
assert_eq!(message, b"foomp".to_vec());
|
||||
assert!(!with_reply_surb);
|
||||
assert_eq!(connection_id, Some(42))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let send_request_surb = ClientRequest::Send {
|
||||
recipient,
|
||||
#[test]
|
||||
fn send_anonymous_request_serialization_works() {
|
||||
let original_recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
|
||||
|
||||
let send_anonymous_request = ClientRequest::SendAnonymous {
|
||||
recipient: original_recipient,
|
||||
message: b"foomp".to_vec(),
|
||||
with_reply_surb: true,
|
||||
connection_id: None,
|
||||
reply_surbs: 666,
|
||||
connection_id: Some(42),
|
||||
};
|
||||
|
||||
let bytes = send_request_surb.serialize();
|
||||
let bytes = send_anonymous_request.serialize();
|
||||
let recovered = ClientRequest::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ClientRequest::Send {
|
||||
ClientRequest::SendAnonymous {
|
||||
recipient,
|
||||
message,
|
||||
with_reply_surb,
|
||||
reply_surbs,
|
||||
connection_id,
|
||||
} => {
|
||||
assert_eq!(recipient.to_string(), recipient_string);
|
||||
assert_eq!(recipient, original_recipient);
|
||||
assert_eq!(message, b"foomp".to_vec());
|
||||
assert!(with_reply_surb);
|
||||
assert_eq!(connection_id, None)
|
||||
assert_eq!(connection_id, Some(42));
|
||||
assert_eq!(reply_surbs, 666)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -416,22 +511,23 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reply_request_serialization_works() {
|
||||
let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X";
|
||||
let reply_surb = ReplySurb::from_base58_string(reply_surb_string).unwrap();
|
||||
let reply_request = ClientRequest::Reply {
|
||||
sender_tag: [8u8; SENDER_TAG_SIZE].into(),
|
||||
message: b"foomp".to_vec(),
|
||||
reply_surb,
|
||||
connection_id: Some(42),
|
||||
};
|
||||
|
||||
let bytes = reply_request.serialize();
|
||||
let recovered = ClientRequest::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ClientRequest::Reply {
|
||||
reply_surb,
|
||||
sender_tag,
|
||||
message,
|
||||
connection_id,
|
||||
} => {
|
||||
assert_eq!(reply_surb.to_base58_string(), reply_surb_string);
|
||||
assert_eq!(sender_tag, [8u8; SENDER_TAG_SIZE].into());
|
||||
assert_eq!(message, b"foomp".to_vec());
|
||||
assert_eq!(connection_id, Some(42));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -458,4 +554,15 @@ mod tests {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_lane_queue_length_request_serialization_works() {
|
||||
let close_connection_request = ClientRequest::GetLaneQueueLength(42);
|
||||
let bytes = close_connection_request.serialize();
|
||||
let recovered = ClientRequest::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ClientRequest::GetLaneQueueLength(id) => assert_eq!(id, 42),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,54 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all variable size data is always prefixed with u64 length
|
||||
// tags are u8
|
||||
|
||||
#![allow(unknown_lints)] // due to using `clippy::branches_sharing_code` which does not exist on `stable` just yet
|
||||
|
||||
use crate::error::{self, ErrorKind};
|
||||
use crate::text::ServerResponseText;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::{AnonymousSenderTag, SENDER_TAG_SIZE};
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use std::convert::TryInto;
|
||||
use std::mem::size_of;
|
||||
|
||||
/// Value tag representing [`Error`] variant of the [`ServerResponse`]
|
||||
pub const ERROR_RESPONSE_TAG: u8 = 0x00;
|
||||
#[repr(u8)]
|
||||
enum ServerResponseTag {
|
||||
/// Value tag representing [`Error`] variant of the [`ServerResponse`]
|
||||
Error = 0x00,
|
||||
|
||||
/// Value tag representing [`Received`] variant of the [`ServerResponse`]
|
||||
pub const RECEIVED_RESPONSE_TAG: u8 = 0x01;
|
||||
/// Value tag representing [`Received`] variant of the [`ServerResponse`]
|
||||
Received = 0x01,
|
||||
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
|
||||
pub const SELF_ADDRESS_RESPONSE_TAG: u8 = 0x02;
|
||||
/// Value tag representing [`SelfAddress`] variant of the [`ServerResponse`]
|
||||
SelfAddress = 0x02,
|
||||
|
||||
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
|
||||
pub const LANE_QUEUE_LENGTH_RESPONSE_TAG: u8 = 0x03;
|
||||
/// Value tag representing [`LaneQueueLength`] variant of the [`ServerResponse`]
|
||||
LaneQueueLength = 0x03,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ServerResponseTag {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, error::Error> {
|
||||
match value {
|
||||
_ if value == (Self::Error as u8) => Ok(Self::Error),
|
||||
_ if value == (Self::Received as u8) => Ok(Self::Received),
|
||||
_ if value == (Self::SelfAddress as u8) => Ok(Self::SelfAddress),
|
||||
_ if value == (Self::LaneQueueLength as u8) => Ok(Self::LaneQueueLength),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownResponse,
|
||||
format!("{n} does not correspond to any valid response tag"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerResponse {
|
||||
Received(ReconstructedMessage),
|
||||
SelfAddress(Recipient),
|
||||
LaneQueueLength(u64, usize),
|
||||
SelfAddress(Box<Recipient>),
|
||||
LaneQueueLength { lane: u64, queue_length: usize },
|
||||
Error(error::Error),
|
||||
}
|
||||
|
||||
@@ -42,24 +60,19 @@ impl ServerResponse {
|
||||
})
|
||||
}
|
||||
|
||||
// RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
|
||||
// RECEIVED_RESPONSE_TAG || 1 | 0 indicating sender_tag || Option<sender_tag> || msg_len || msg
|
||||
fn serialize_received(reconstructed_message: ReconstructedMessage) -> Vec<u8> {
|
||||
let message_len_bytes = (reconstructed_message.message.len() as u64).to_be_bytes();
|
||||
if let Some(reply_surb) = reconstructed_message.reply_surb {
|
||||
let reply_surb_bytes = reply_surb.to_bytes();
|
||||
let surb_len_bytes = (reply_surb_bytes.len() as u64).to_be_bytes();
|
||||
|
||||
// with_reply || surb_len || surb || msg_len || msg
|
||||
std::iter::once(RECEIVED_RESPONSE_TAG)
|
||||
if let Some(sender_tag) = reconstructed_message.sender_tag {
|
||||
std::iter::once(ServerResponseTag::Received as u8)
|
||||
.chain(std::iter::once(true as u8))
|
||||
.chain(surb_len_bytes.iter().cloned())
|
||||
.chain(reply_surb_bytes.iter().cloned())
|
||||
.chain(sender_tag.to_bytes().into_iter())
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
.chain(reconstructed_message.message.into_iter())
|
||||
.collect()
|
||||
} else {
|
||||
// without_reply || msg_len || msg
|
||||
std::iter::once(RECEIVED_RESPONSE_TAG)
|
||||
std::iter::once(ServerResponseTag::Received as u8)
|
||||
.chain(std::iter::once(false as u8))
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
.chain(reconstructed_message.message.into_iter())
|
||||
@@ -67,10 +80,9 @@ impl ServerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// RECEIVED_RESPONSE_TAG || with_reply || (surb_len || surb) || msg_len || msg
|
||||
// RECEIVED_RESPONSE_TAG || 1 | 0 indicating sender_tag || Option<sender_tag> || msg_len || msg
|
||||
fn deserialize_received(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], RECEIVED_RESPONSE_TAG);
|
||||
|
||||
// we must be able to read at the very least if it has a reply_surb and length of some field
|
||||
if b.len() < 2 + size_of::<u64>() {
|
||||
@@ -79,101 +91,70 @@ impl ServerResponse {
|
||||
"not enough data provided to recover 'received'".to_string(),
|
||||
));
|
||||
}
|
||||
debug_assert_eq!(b[0], ServerResponseTag::Received as u8);
|
||||
|
||||
let with_reply_surb = match b[1] {
|
||||
let has_sender_tag = match b[1] {
|
||||
0 => false,
|
||||
1 => true,
|
||||
n => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("invalid reply flag {}", n),
|
||||
format!("invalid sender tag flag {n}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// this is a false positive as even though the code is the same, it refers to different things
|
||||
#[allow(clippy::branches_sharing_code)]
|
||||
if with_reply_surb {
|
||||
let reply_surb_len =
|
||||
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
|
||||
|
||||
// make sure we won't go out of bounds here
|
||||
if reply_surb_len > (b.len() - 2 + 2 * size_of::<u64>()) as u64 {
|
||||
let mut i = 2;
|
||||
let sender_tag = if has_sender_tag {
|
||||
if b[2..].len() < SENDER_TAG_SIZE {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
"not enough bytes to read reply_surb bytes!".to_string(),
|
||||
ErrorKind::TooShortResponse,
|
||||
"not enough data provided to recover 'received'".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let surb_bound = 2 + size_of::<u64>() + reply_surb_len as usize;
|
||||
|
||||
let reply_surb_bytes = &b[2 + size_of::<u64>()..surb_bound];
|
||||
let reply_surb = match ReplySurb::from_bytes(reply_surb_bytes) {
|
||||
Ok(reply_surb) => reply_surb,
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("malformed reply SURB: {:?}", err),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let message_len = u64::from_be_bytes(
|
||||
b[surb_bound..surb_bound + size_of::<u64>()]
|
||||
.as_ref()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let message = &b[surb_bound + size_of::<u64>()..];
|
||||
if message.len() as u64 != message_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!(
|
||||
"message len has inconsistent length. specified: {} got: {}",
|
||||
message_len,
|
||||
message.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ServerResponse::Received(ReconstructedMessage {
|
||||
message: message.to_vec(),
|
||||
reply_surb: Some(reply_surb),
|
||||
}))
|
||||
i += SENDER_TAG_SIZE;
|
||||
Some(AnonymousSenderTag::from_bytes(
|
||||
b[2..2 + SENDER_TAG_SIZE].try_into().unwrap(),
|
||||
))
|
||||
} else {
|
||||
let message_len =
|
||||
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
|
||||
let message = &b[2 + size_of::<u64>()..];
|
||||
if message.len() as u64 != message_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!(
|
||||
"message len has inconsistent length. specified: {} got: {}",
|
||||
message_len,
|
||||
message.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
Ok(ServerResponse::Received(ReconstructedMessage {
|
||||
message: message.to_vec(),
|
||||
reply_surb: None,
|
||||
}))
|
||||
if b[i..].len() < size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortResponse,
|
||||
"not enough data provided to recover 'received'".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let message_len = u64::from_be_bytes(b[i..i + size_of::<u64>()].try_into().unwrap());
|
||||
let message = &b[i + size_of::<u64>()..];
|
||||
if message.len() as u64 != message_len {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!(
|
||||
"message len has inconsistent length. specified: {} got: {}",
|
||||
message_len,
|
||||
message.len()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ServerResponse::Received(ReconstructedMessage {
|
||||
message: message.to_vec(),
|
||||
sender_tag,
|
||||
}))
|
||||
}
|
||||
|
||||
// SELF_ADDRESS_RESPONSE_TAG || self_address
|
||||
fn serialize_self_address(address: Recipient) -> Vec<u8> {
|
||||
std::iter::once(SELF_ADDRESS_RESPONSE_TAG)
|
||||
.chain(address.to_bytes().iter().cloned())
|
||||
std::iter::once(ServerResponseTag::SelfAddress as u8)
|
||||
.chain(address.to_bytes().into_iter())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// SELF_ADDRESS_RESPONSE_TAG || self_address
|
||||
fn deserialize_self_address(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], SELF_ADDRESS_RESPONSE_TAG);
|
||||
|
||||
if b.len() != 1 + Recipient::LEN {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::TooShortResponse,
|
||||
@@ -181,6 +162,9 @@ impl ServerResponse {
|
||||
));
|
||||
}
|
||||
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], ServerResponseTag::SelfAddress as u8);
|
||||
|
||||
let mut recipient_bytes = [0u8; Recipient::LEN];
|
||||
recipient_bytes.copy_from_slice(&b[1..1 + Recipient::LEN]);
|
||||
|
||||
@@ -189,17 +173,17 @@ impl ServerResponse {
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("malformed Recipient: {:?}", err),
|
||||
format!("malformed Recipient: {err}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ServerResponse::SelfAddress(recipient))
|
||||
Ok(ServerResponse::SelfAddress(Box::new(recipient)))
|
||||
}
|
||||
|
||||
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
|
||||
fn serialize_lane_queue_length(lane: u64, queue_length: usize) -> Vec<u8> {
|
||||
std::iter::once(LANE_QUEUE_LENGTH_RESPONSE_TAG)
|
||||
std::iter::once(ServerResponseTag::LaneQueueLength as u8)
|
||||
.chain(lane.to_be_bytes().iter().cloned())
|
||||
.chain(queue_length.to_be_bytes().iter().cloned())
|
||||
.collect()
|
||||
@@ -208,7 +192,7 @@ impl ServerResponse {
|
||||
// LANE_QUEUE_LENGTH_RESPONSE_TAG || lane || queue_length
|
||||
fn deserialize_lane_queue_length(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], LANE_QUEUE_LENGTH_RESPONSE_TAG);
|
||||
debug_assert_eq!(b[0], ServerResponseTag::LaneQueueLength as u8);
|
||||
|
||||
let mut lane_bytes = [0u8; size_of::<u64>()];
|
||||
lane_bytes.copy_from_slice(&b[1..=size_of::<u64>()]);
|
||||
@@ -219,15 +203,15 @@ impl ServerResponse {
|
||||
.copy_from_slice(&b[1 + size_of::<u64>()..1 + size_of::<u64>() + size_of::<usize>()]);
|
||||
let queue_length = usize::from_be_bytes(queue_length_bytes);
|
||||
|
||||
Ok(ServerResponse::LaneQueueLength(lane, queue_length))
|
||||
Ok(ServerResponse::LaneQueueLength { lane, queue_length })
|
||||
}
|
||||
|
||||
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
|
||||
fn serialize_error(error: error::Error) -> Vec<u8> {
|
||||
let message_len_bytes = (error.message.len() as u64).to_be_bytes();
|
||||
std::iter::once(ERROR_RESPONSE_TAG)
|
||||
std::iter::once(ServerResponseTag::Error as u8)
|
||||
.chain(std::iter::once(error.kind as u8))
|
||||
.chain(message_len_bytes.iter().cloned())
|
||||
.chain(message_len_bytes.into_iter())
|
||||
.chain(error.message.into_bytes().into_iter())
|
||||
.collect()
|
||||
}
|
||||
@@ -235,7 +219,7 @@ impl ServerResponse {
|
||||
// ERROR_RESPONSE_TAG || err_code || msg_len || msg
|
||||
fn deserialize_error(b: &[u8]) -> Result<Self, error::Error> {
|
||||
// this MUST match because it was called by 'deserialize'
|
||||
debug_assert_eq!(b[0], ERROR_RESPONSE_TAG);
|
||||
debug_assert_eq!(b[0], ServerResponseTag::Error as u8);
|
||||
|
||||
if b.len() < size_of::<u8>() + size_of::<u64>() {
|
||||
return Err(error::Error::new(
|
||||
@@ -244,26 +228,7 @@ impl ServerResponse {
|
||||
));
|
||||
}
|
||||
|
||||
let error_kind = match b[1] {
|
||||
_ if b[1] == (ErrorKind::EmptyRequest as u8) => ErrorKind::EmptyRequest,
|
||||
_ if b[1] == (ErrorKind::TooShortRequest as u8) => ErrorKind::TooShortRequest,
|
||||
_ if b[1] == (ErrorKind::UnknownRequest as u8) => ErrorKind::UnknownRequest,
|
||||
_ if b[1] == (ErrorKind::MalformedRequest as u8) => ErrorKind::MalformedRequest,
|
||||
|
||||
_ if b[1] == (ErrorKind::EmptyResponse as u8) => ErrorKind::EmptyResponse,
|
||||
_ if b[1] == (ErrorKind::TooShortResponse as u8) => ErrorKind::TooShortResponse,
|
||||
_ if b[1] == (ErrorKind::UnknownResponse as u8) => ErrorKind::UnknownResponse,
|
||||
_ if b[1] == (ErrorKind::MalformedResponse as u8) => ErrorKind::MalformedResponse,
|
||||
|
||||
_ if b[1] == (ErrorKind::Other as u8) => ErrorKind::Other,
|
||||
|
||||
n => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("invalid error code {}", n),
|
||||
))
|
||||
}
|
||||
};
|
||||
let error_kind = ErrorKind::try_from(b[1])?;
|
||||
|
||||
let message_len =
|
||||
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
|
||||
@@ -284,7 +249,7 @@ impl ServerResponse {
|
||||
Err(err) => {
|
||||
return Err(error::Error::new(
|
||||
ErrorKind::MalformedResponse,
|
||||
format!("malformed error message: {:?}", err),
|
||||
format!("malformed error message: {err}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
@@ -300,8 +265,8 @@ impl ServerResponse {
|
||||
ServerResponse::Received(reconstructed_message) => {
|
||||
Self::serialize_received(reconstructed_message)
|
||||
}
|
||||
ServerResponse::SelfAddress(address) => Self::serialize_self_address(address),
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
ServerResponse::SelfAddress(address) => Self::serialize_self_address(*address),
|
||||
ServerResponse::LaneQueueLength { lane, queue_length } => {
|
||||
Self::serialize_lane_queue_length(lane, queue_length)
|
||||
}
|
||||
ServerResponse::Error(err) => Self::serialize_error(err),
|
||||
@@ -328,18 +293,14 @@ impl ServerResponse {
|
||||
));
|
||||
}
|
||||
|
||||
let response_tag = b[0];
|
||||
let response_tag = ServerResponseTag::try_from(b[0])?;
|
||||
|
||||
// determine what kind of response that is and try to deserialize it
|
||||
match response_tag {
|
||||
RECEIVED_RESPONSE_TAG => Self::deserialize_received(b),
|
||||
SELF_ADDRESS_RESPONSE_TAG => Self::deserialize_self_address(b),
|
||||
LANE_QUEUE_LENGTH_RESPONSE_TAG => Self::deserialize_lane_queue_length(b),
|
||||
ERROR_RESPONSE_TAG => Self::deserialize_error(b),
|
||||
n => Err(error::Error::new(
|
||||
ErrorKind::UnknownResponse,
|
||||
format!("type {}", n),
|
||||
)),
|
||||
ServerResponseTag::Received => Self::deserialize_received(b),
|
||||
ServerResponseTag::SelfAddress => Self::deserialize_self_address(b),
|
||||
ServerResponseTag::LaneQueueLength => Self::deserialize_lane_queue_length(b),
|
||||
ServerResponseTag::Error => Self::deserialize_error(b),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,35 +322,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn received_response_serialization_works() {
|
||||
let reply_surb_string = "CjfVbHbfAjbC3W1BvNHGXmM8KNAnDNYGaHMLqVDxRYeo352csAihstup9bvqXam4dTWgfHak6KYwL9STaxWJ47E8XFZbSEvs7hEsfCkxr6K9WJuSBPK84GDDEvad8ZAuMCoaXsAd5S2Lj9a5eYyzG4SL1jHzhSMni55LyJwumxo1ZTGZNXggxw1RREosvyzNrW9Rsi3owyPqLCwXpiei2tHZty8w8midVvg8vDa7ZEJD842CLv8D4ohynSG7gDpqTrhkRaqYAuz7dzqNbMXLJRM7v823Jn16fA1L7YQxmcaUdUigyRSgTdb4i9ebiLGSyJ1iDe6Acz613PQZh6Ua3bZ2zVKq3dSycpDm9ngarRK4zJrAaUxRkdih8YzW3BY4nL9eqkfKA4N1TWCLaRU7zpSaf8yMEwrAZReU3d5zLV8c5KBfa2w8R5anhQeBojduZEGEad8kkHuKU52Zg93FeWHvH1qgZaEJMHH4nN7gKXz9mvWDhYwyF4vt3Uy2NhCHC3N5pL1gMme27YcoPcTEia1fxKZtnt6rtEozzTrAgCJGswigkFbkafiV5QaJwLKTUxtzhkZ57eEuLPte9UvJHzhhXUQ2CV7R2BUkJjYZy3Zsx6YYvdYWiAFFkWUwNEGA4QpShUHciBfsQVHQ7pN41YcyYUhbywQDFnTVgEmdUZ1XCBi3gyK5U3tDQmFzP1u9m3mWrUA8qB9mRDE7ptNDm5c3c1458L6uXLUth7sdMaa1Was5LCmCdmNDtvNpCDAEt1in6q6mrZFR85aCSU9b1baNGwZoCqPpPvydkVe63gXWoi8ebvdyxARrqACFrSB3ZdY3uJBw8CTMNkKK6MvcefMkSVVsbLd36TQAtYSCqrpiMc5dQuKcEu5QfciwvWYXYx8WFNAgKwP2mv49KCTvfozNDUCbjzDwSx92Zv5zjG8HbFpB13bY9UZGeyTPvv7gGxCzjGjJGbW6FRAheRQaaje5fUgCNM95Tv7wBmAMRHHFgWafeK1sdFH7dtCX9u898HucGTaboSKLsVh8J78gbbkHErwjMh7y9YRkceq5TTYS5da4kHnyNKYWSbxgZrmFg44XGKoeYcqoHB3XTZrdsf7F5fFeNwnihkmADvhAcaxXUmVqq4rQFZH84a1iC3WBWXYcqiZH2L7ujGWV7mMDT4HBEerDYjc8rNY4xGTPfivCrBCJW1i14aqW8xRdsdgTM88eTksvC3WPJLJ7iMzfKXeL7fMW1Ek6QGyQtLBW98vEESpdcDg6DeZ5rMz6VqjTGGqcCaFGfHoqtfxMDaBAEsyQ8h7XDX6dg1wq9wH6j4Tw7Tj1MEv1b8uj5NJkozZdzVdYA2QyE2Dp8vuurQG6uVdTDNww2d88RBQ8sVgjxN8gR45y4woJLhFAaNTAtrY6wDTxyXST13ni6oyqdYxjFVk9Am4v3DzH7Y2K8iRVSHfTk4FRbPULyaeK6wt2anvMJH1XdvVRgc14h67MnBxMgMD1UFk8AErN7CDj26fppe3c5G6KozJe4cSqQUGbBjVzBnrHCruqrfZBn5hNZHTV37bQiomqhRQXohxhuKEnNrGbAe1xNvJr9X";
|
||||
|
||||
let received_with_surb = ServerResponse::Received(ReconstructedMessage {
|
||||
let received_with_sender_tag = ServerResponse::Received(ReconstructedMessage {
|
||||
message: b"foomp".to_vec(),
|
||||
reply_surb: Some(ReplySurb::from_base58_string(reply_surb_string).unwrap()),
|
||||
sender_tag: Some([42u8; SENDER_TAG_SIZE].into()),
|
||||
});
|
||||
let bytes = received_with_surb.serialize();
|
||||
let bytes = received_with_sender_tag.serialize();
|
||||
let recovered = ServerResponse::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ServerResponse::Received(reconstructed) => {
|
||||
assert_eq!(reconstructed.message, b"foomp".to_vec());
|
||||
assert_eq!(
|
||||
reconstructed.reply_surb.unwrap().to_base58_string(),
|
||||
reply_surb_string
|
||||
reconstructed.sender_tag,
|
||||
Some([42u8; SENDER_TAG_SIZE].into())
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let received_without_surb = ServerResponse::Received(ReconstructedMessage {
|
||||
let received_without_sender_tag = ServerResponse::Received(ReconstructedMessage {
|
||||
message: b"foomp".to_vec(),
|
||||
reply_surb: None,
|
||||
sender_tag: None,
|
||||
});
|
||||
let bytes = received_without_surb.serialize();
|
||||
let bytes = received_without_sender_tag.serialize();
|
||||
let recovered = ServerResponse::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ServerResponse::Received(reconstructed) => {
|
||||
assert_eq!(reconstructed.message, b"foomp".to_vec());
|
||||
assert!(reconstructed.reply_surb.is_none())
|
||||
assert!(reconstructed.sender_tag.is_none())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -400,7 +359,7 @@ mod tests {
|
||||
let recipient = Recipient::try_from_base58_string("CytBseW6yFXUMzz4SGAKdNLGR7q3sJLLYxyBGvutNEQV.4QXYyEVc5fUDjmmi8PrHN9tdUFV4PCvSJE1278cHyvoe@4sBbL1ngf1vtNqykydQKTFh26sQCw888GpUqvPvyNB4f").unwrap();
|
||||
let recipient_string = recipient.to_string();
|
||||
|
||||
let self_address_response = ServerResponse::SelfAddress(recipient);
|
||||
let self_address_response = ServerResponse::SelfAddress(Box::new(recipient));
|
||||
let bytes = self_address_response.serialize();
|
||||
let recovered = ServerResponse::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
@@ -413,11 +372,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn lane_queue_length_response_serialization_works() {
|
||||
let lane_queue_length_response = ServerResponse::LaneQueueLength(13, 42);
|
||||
let lane_queue_length_response = ServerResponse::LaneQueueLength {
|
||||
lane: 13,
|
||||
queue_length: 42,
|
||||
};
|
||||
let bytes = lane_queue_length_response.serialize();
|
||||
let recovered = ServerResponse::deserialize(&bytes).unwrap();
|
||||
match recovered {
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
ServerResponse::LaneQueueLength { lane, queue_length } => {
|
||||
assert_eq!(lane, 13);
|
||||
assert_eq!(queue_length, 42)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// 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::error::ErrorKind;
|
||||
use crate::requests::ClientRequest;
|
||||
use crate::responses::ServerResponse;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
@@ -19,15 +19,22 @@ pub(super) enum ClientRequestText {
|
||||
Send {
|
||||
message: String,
|
||||
recipient: String,
|
||||
with_reply_surb: bool,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SendAnonymous {
|
||||
recipient: String,
|
||||
message: String,
|
||||
reply_surbs: u32,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Reply {
|
||||
sender_tag: String,
|
||||
message: String,
|
||||
connection_id: Option<u64>,
|
||||
},
|
||||
SelfAddress,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Reply {
|
||||
message: String,
|
||||
reply_surb: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ClientRequestText {
|
||||
@@ -46,7 +53,6 @@ impl TryInto<ClientRequest> for ClientRequestText {
|
||||
ClientRequestText::Send {
|
||||
message,
|
||||
recipient,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
let message_bytes = message.into_bytes();
|
||||
@@ -57,23 +63,42 @@ impl TryInto<ClientRequest> for ClientRequestText {
|
||||
Ok(ClientRequest::Send {
|
||||
message: message_bytes,
|
||||
recipient,
|
||||
with_reply_surb,
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
ClientRequestText::SendAnonymous {
|
||||
recipient,
|
||||
message,
|
||||
reply_surbs,
|
||||
connection_id,
|
||||
} => {
|
||||
let message_bytes = message.into_bytes();
|
||||
let recipient = Recipient::try_from_base58_string(recipient).map_err(|err| {
|
||||
Self::Error::new(ErrorKind::MalformedRequest, err.to_string())
|
||||
})?;
|
||||
Ok(ClientRequest::SendAnonymous {
|
||||
recipient,
|
||||
message: message_bytes,
|
||||
reply_surbs,
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
ClientRequestText::SelfAddress => Ok(ClientRequest::SelfAddress),
|
||||
ClientRequestText::Reply {
|
||||
sender_tag,
|
||||
message,
|
||||
reply_surb,
|
||||
connection_id,
|
||||
} => {
|
||||
let message_bytes = message.into_bytes();
|
||||
let reply_surb = ReplySurb::from_base58_string(reply_surb).map_err(|err| {
|
||||
Self::Error::new(ErrorKind::MalformedRequest, err.to_string())
|
||||
})?;
|
||||
let sender_tag =
|
||||
AnonymousSenderTag::try_from_base58_string(sender_tag).map_err(|err| {
|
||||
Self::Error::new(ErrorKind::MalformedRequest, err.to_string())
|
||||
})?;
|
||||
|
||||
Ok(ClientRequest::Reply {
|
||||
sender_tag,
|
||||
message: message_bytes,
|
||||
reply_surb,
|
||||
connection_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -89,7 +114,7 @@ pub(super) enum ServerResponseText {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Received {
|
||||
message: String,
|
||||
reply_surb: Option<String>,
|
||||
sender_tag: Option<String>,
|
||||
},
|
||||
SelfAddress {
|
||||
address: String,
|
||||
@@ -131,15 +156,13 @@ impl From<ServerResponse> for ServerResponseText {
|
||||
// TODO: ask DH what is more appropriate, lossy utf8 conversion or returning error and then
|
||||
// pure binary later
|
||||
message: String::from_utf8_lossy(&reconstructed.message).into_owned(),
|
||||
reply_surb: reconstructed
|
||||
.reply_surb
|
||||
.map(|reply_surb| reply_surb.to_base58_string()),
|
||||
sender_tag: reconstructed.sender_tag.map(|tag| tag.to_base58_string()),
|
||||
}
|
||||
}
|
||||
ServerResponse::SelfAddress(recipient) => ServerResponseText::SelfAddress {
|
||||
address: recipient.to_string(),
|
||||
},
|
||||
ServerResponse::LaneQueueLength(lane, queue_length) => {
|
||||
ServerResponse::LaneQueueLength { lane, queue_length } => {
|
||||
ServerResponseText::LaneQueueLength { lane, queue_length }
|
||||
}
|
||||
ServerResponse::Error(err) => ServerResponseText::Error {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.2"
|
||||
version = "1.1.5"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -11,21 +11,24 @@ name = "nym_socks5"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.2", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.0", features = ["cargo", "derive"] }
|
||||
dirs = "4.0"
|
||||
futures = "0.3"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
pin-project = "1.0"
|
||||
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"
|
||||
serde_json = "1.0.89"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] }
|
||||
url = "2.2"
|
||||
|
||||
# internal
|
||||
client-core = { path = "../client-core" }
|
||||
build-information = { path = "../../common/build-information" }
|
||||
client-core = { path = "../client-core", features = ["fs-surb-storage"] }
|
||||
client-connections = { path = "../../common/client-connections" }
|
||||
coconut-interface = { path = "../../common/coconut-interface", optional = true }
|
||||
config = { path = "../../common/config" }
|
||||
@@ -44,13 +47,9 @@ proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
|
||||
socks5-requests = { path = "../../common/socks5/requests" }
|
||||
task = { path = "../../common/task" }
|
||||
topology = { path = "../../common/topology" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = ["nyxd-client"] }
|
||||
version-checker = { path = "../../common/version-checker" }
|
||||
tap = "1.0.1"
|
||||
|
||||
[features]
|
||||
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
|
||||
eth = []
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::template::config_template;
|
||||
use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::MISSING_VALUE;
|
||||
use client_core::config::{ClientCoreConfigTrait, DebugConfig};
|
||||
use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT;
|
||||
use config::NymConfig;
|
||||
use config::{NymConfig, OptionalSet};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod template;
|
||||
|
||||
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
|
||||
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
@@ -19,6 +25,9 @@ pub struct Config {
|
||||
base: BaseConfig<Config>,
|
||||
|
||||
socks5: Socks5,
|
||||
|
||||
#[serde(default)]
|
||||
socks5_debug: Socks5Debug,
|
||||
}
|
||||
|
||||
impl NymConfig for Config {
|
||||
@@ -52,25 +61,51 @@ impl NymConfig for Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientCoreConfigTrait for Config {
|
||||
fn get_gateway_endpoint(&self) -> &client_core::config::GatewayEndpointConfig {
|
||||
self.base.get_gateway_endpoint()
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S, provider_mix_address: S) -> Self {
|
||||
Config {
|
||||
base: BaseConfig::new(id),
|
||||
socks5: Socks5::new(provider_mix_address),
|
||||
socks5_debug: Socks5Debug::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_port(mut self, port: u16) -> Self {
|
||||
self.socks5.listening_port = port;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_provider_mix_address(mut self, address: String) -> Self {
|
||||
self.socks5.provider_mix_address = address;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_anonymous_replies(mut self, anonymous_replies: bool) -> Self {
|
||||
self.socks5.send_anonymously = anonymous_replies;
|
||||
self
|
||||
}
|
||||
|
||||
// getters
|
||||
pub fn get_base(&self) -> &BaseConfig<Self> {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
pub fn get_debug_settings(&self) -> &DebugConfig {
|
||||
self.get_base().get_debug_config()
|
||||
}
|
||||
|
||||
pub fn get_config_file_save_location(&self) -> PathBuf {
|
||||
self.config_directory().join(Self::config_file_name())
|
||||
}
|
||||
@@ -80,17 +115,65 @@ impl Config {
|
||||
.expect("malformed provider address")
|
||||
}
|
||||
|
||||
pub fn get_base(&self) -> &BaseConfig<Self> {
|
||||
&self.base
|
||||
}
|
||||
|
||||
pub fn get_base_mut(&mut self) -> &mut BaseConfig<Self> {
|
||||
&mut self.base
|
||||
pub fn get_send_anonymously(&self) -> bool {
|
||||
self.socks5.send_anonymously
|
||||
}
|
||||
|
||||
pub fn get_listening_port(&self) -> u16 {
|
||||
self.socks5.listening_port
|
||||
}
|
||||
|
||||
pub fn get_connection_start_surbs(&self) -> u32 {
|
||||
self.socks5_debug.connection_start_surbs
|
||||
}
|
||||
|
||||
pub fn get_per_request_surbs(&self) -> u32 {
|
||||
self.socks5_debug.per_request_surbs
|
||||
}
|
||||
|
||||
// poor man's 'builder' method
|
||||
pub fn with_base<F, T>(mut self, f: F, val: T) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
{
|
||||
self.base = f(self.base, val);
|
||||
self
|
||||
}
|
||||
|
||||
// helper methods to use `OptionalSet` trait. Those are defined due to very... ehm. 'specific' structure of this config
|
||||
// (plz, lets refactor it)
|
||||
pub fn with_optional_ext<F, T>(mut self, f: F, val: Option<T>) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
{
|
||||
self.base = self.base.with_optional(f, val);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_env_ext<F, T>(mut self, f: F, val: Option<T>, env_var: &str) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
self.base = self.base.with_optional_env(f, val, env_var);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_optional_custom_env_ext<F, T, G>(
|
||||
mut self,
|
||||
f: F,
|
||||
val: Option<T>,
|
||||
env_var: &str,
|
||||
parser: G,
|
||||
) -> Self
|
||||
where
|
||||
F: Fn(BaseConfig<Self>, T) -> BaseConfig<Self>,
|
||||
G: Fn(&str) -> T,
|
||||
{
|
||||
self.base = self.base.with_optional_custom_env(f, val, env_var, parser);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -101,6 +184,14 @@ pub struct Socks5 {
|
||||
|
||||
/// The mix address of the provider to which all requests are going to be sent.
|
||||
provider_mix_address: String,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
/// While this is going to hide its actual address information, it will make the actual communication
|
||||
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
///
|
||||
/// Note that some service providers might not support this.
|
||||
#[serde(default)]
|
||||
send_anonymously: bool,
|
||||
}
|
||||
|
||||
impl Socks5 {
|
||||
@@ -108,6 +199,7 @@ impl Socks5 {
|
||||
Socks5 {
|
||||
listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
|
||||
provider_mix_address: provider_mix_address.into(),
|
||||
send_anonymously: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +209,26 @@ impl Default for Socks5 {
|
||||
Socks5 {
|
||||
listening_port: DEFAULT_SOCKS5_LISTENING_PORT,
|
||||
provider_mix_address: "".into(),
|
||||
send_anonymously: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socks5Debug {
|
||||
/// Number of reply SURBs attached to each `Request::Connect` message.
|
||||
connection_start_surbs: u32,
|
||||
|
||||
/// Number of reply SURBs attached to each `Request::Send` message.
|
||||
per_request_surbs: u32,
|
||||
}
|
||||
|
||||
impl Default for Socks5Debug {
|
||||
fn default() -> Self {
|
||||
Socks5Debug {
|
||||
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
|
||||
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,16 +23,16 @@ id = '{{ client.id }}'
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to nymd validators via which the client can communicate with the chain.
|
||||
validator_urls = [
|
||||
{{#each client.validator_urls }}
|
||||
# Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
nyxd_urls = [
|
||||
{{#each client.nyxd_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls = [
|
||||
{{#each client.validator_api_urls }}
|
||||
nym_api_urls = [
|
||||
{{#each client.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
@@ -49,13 +49,12 @@ private_encryption_key_file = '{{ client.private_encryption_key_file }}'
|
||||
# Path to file containing public encryption key.
|
||||
public_encryption_key_file = '{{ client.public_encryption_key_file }}'
|
||||
|
||||
# Full path to file containing reply encryption keys of all reply-SURBs we have ever
|
||||
# sent but not received back.
|
||||
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
database_path = '{{ client.database_path }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database_path = '{{ client.reply_surb_database_path }}'
|
||||
|
||||
##### additional client config options #####
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
@@ -92,6 +91,12 @@ provider_mix_address = '{{ socks5.provider_mix_address }}'
|
||||
# The port on which the client will be listening for incoming requests
|
||||
listening_port = {{ socks5.listening_port }}
|
||||
|
||||
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
# While this is going to hide its actual address information, it will make the actual communication
|
||||
# slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
#
|
||||
# Note that some service providers might not support this.
|
||||
send_anonymously = {{ socks5.send_anonymously }}
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
@@ -104,6 +109,9 @@ listening_port = {{ socks5.listening_port }}
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
# [socks5_debug]
|
||||
|
||||
|
||||
[debug]
|
||||
|
||||
average_packet_delay = '{{ debug.average_packet_delay }}'
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::error::Socks5ClientError;
|
||||
use crate::socks;
|
||||
use crate::socks::{
|
||||
authentication::{AuthenticationMethods, Authenticator, User},
|
||||
server::SphinxSocksServer,
|
||||
};
|
||||
use client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
|
||||
use client_core::client::base_client::{
|
||||
non_wasm_helpers, BaseClientBuilder, ClientInput, ClientOutput,
|
||||
};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use futures::channel::mpsc;
|
||||
@@ -16,7 +19,7 @@ use gateway_client::bandwidth::BandwidthController;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use std::error::Error;
|
||||
use task::{wait_for_signal_and_error, ShutdownListener, ShutdownNotifier};
|
||||
use task::{wait_for_signal_and_error, TaskClient, TaskManager};
|
||||
|
||||
pub mod config;
|
||||
|
||||
@@ -57,18 +60,18 @@ impl NymClient {
|
||||
let mut client_config =
|
||||
validator_client::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct validator client config");
|
||||
let nymd_url = config
|
||||
let nyxd_url = config
|
||||
.get_base()
|
||||
.get_validator_endpoints()
|
||||
.pop()
|
||||
.expect("No nymd validator endpoint provided");
|
||||
.expect("No nyxd validator endpoint provided");
|
||||
let api_url = config
|
||||
.get_base()
|
||||
.get_validator_api_endpoints()
|
||||
.get_nym_api_endpoints()
|
||||
.pop()
|
||||
.expect("No validator api endpoint provided");
|
||||
// overwrite env configuration with config URLs
|
||||
client_config = client_config.with_urls(nymd_url, api_url);
|
||||
client_config = client_config.with_urls(nyxd_url, api_url);
|
||||
let client = validator_client::Client::new_query(client_config)
|
||||
.expect("Could not construct query client");
|
||||
let coconut_api_clients =
|
||||
@@ -93,19 +96,21 @@ impl NymClient {
|
||||
client_input: ClientInput,
|
||||
client_output: ClientOutput,
|
||||
self_address: Recipient,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting socks5 listener...");
|
||||
let auth_methods = vec![AuthenticationMethods::NoAuth as u8];
|
||||
let allowed_users: Vec<User> = Vec::new();
|
||||
|
||||
let ClientInput {
|
||||
shared_lane_queue_lengths,
|
||||
connection_command_sender,
|
||||
input_sender,
|
||||
} = client_input;
|
||||
|
||||
let received_buffer_request_sender = client_output.received_buffer_request_sender;
|
||||
let ClientOutput {
|
||||
shared_lane_queue_lengths,
|
||||
received_buffer_request_sender,
|
||||
} = client_output;
|
||||
|
||||
let authenticator = Authenticator::new(auth_methods, allowed_users);
|
||||
let mut sphinx_socks = SphinxSocksServer::new(
|
||||
@@ -114,6 +119,11 @@ impl NymClient {
|
||||
config.get_provider_mix_address(),
|
||||
self_address,
|
||||
shared_lane_queue_lengths,
|
||||
socks::client::Config::new(
|
||||
config.get_send_anonymously(),
|
||||
config.get_connection_start_surbs(),
|
||||
config.get_per_request_surbs(),
|
||||
),
|
||||
shutdown.clone(),
|
||||
);
|
||||
task::spawn_with_report_error(
|
||||
@@ -131,11 +141,8 @@ impl NymClient {
|
||||
}
|
||||
|
||||
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send>> {
|
||||
let mut shutdown = self
|
||||
.start()
|
||||
.await
|
||||
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
|
||||
pub async fn run_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut shutdown = self.start().await?;
|
||||
|
||||
let res = wait_for_signal_and_error(&mut shutdown).await;
|
||||
|
||||
@@ -153,12 +160,13 @@ impl NymClient {
|
||||
pub async fn run_and_listen(
|
||||
self,
|
||||
mut receiver: Socks5ControlMessageReceiver,
|
||||
) -> Result<(), Box<dyn Error + Send>> {
|
||||
sender: task::StatusSender,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
// Start the main task
|
||||
let mut shutdown = self
|
||||
.start()
|
||||
.await
|
||||
.map_err(|err| Box::new(err) as Box<dyn Error + Send>)?;
|
||||
let mut shutdown = self.start().await?;
|
||||
|
||||
// Listen to status messages from task, that we forward back to the caller
|
||||
shutdown.start_status_listener(sender).await;
|
||||
|
||||
let res = tokio::select! {
|
||||
biased;
|
||||
@@ -194,11 +202,16 @@ impl NymClient {
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn start(self) -> Result<ShutdownNotifier, Socks5ClientError> {
|
||||
pub async fn start(self) -> Result<TaskManager, Socks5ClientError> {
|
||||
let base_builder = BaseClientBuilder::new_from_base_config(
|
||||
self.config.get_base(),
|
||||
self.key_manager,
|
||||
Some(Self::create_bandwidth_controller(&self.config).await),
|
||||
non_wasm_helpers::setup_fs_reply_surb_backend(
|
||||
self.config.get_base().get_reply_surb_database_path(),
|
||||
self.config.get_debug_settings(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
let self_address = base_builder.as_mix_recipient();
|
||||
@@ -211,12 +224,12 @@ impl NymClient {
|
||||
client_input,
|
||||
client_output,
|
||||
self_address,
|
||||
started_client.shutdown_notifier.subscribe(),
|
||||
started_client.task_manager.subscribe(),
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {}", self_address);
|
||||
|
||||
Ok(started_client.shutdown_notifier)
|
||||
Ok(started_client.task_manager)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Args;
|
||||
use client_core::{config::GatewayEndpointConfig, error::ClientCoreError};
|
||||
use config::NymConfig;
|
||||
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use tap::TapFallible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Init {
|
||||
@@ -18,24 +22,35 @@ pub(crate) struct Init {
|
||||
|
||||
/// Address of the socks5 provider to send messages to.
|
||||
#[clap(long)]
|
||||
provider: String,
|
||||
provider: Recipient,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
/// While this is going to hide its actual address information, it will make the actual communication
|
||||
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
///
|
||||
/// Note that some service providers might not support this.
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
#[clap(long, alias = "use_anonymous_sender_tag")]
|
||||
use_reply_surbs: Option<bool>,
|
||||
|
||||
/// Id of the gateway we are going to connect to.
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
force_register_gateway: bool,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nymd validators
|
||||
#[clap(long)]
|
||||
nymd_validators: Option<String>,
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long)]
|
||||
api_validators: Option<String>,
|
||||
#[clap(long, alias = "api_validators", value_delimiter = ',')]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Port for the socket to listen on in all subsequent runs
|
||||
#[clap(short, long)]
|
||||
@@ -43,35 +58,65 @@ pub(crate) struct Init {
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Save a summary of the initialization to a json file
|
||||
#[clap(long)]
|
||||
output_json: bool,
|
||||
}
|
||||
|
||||
impl From<Init> for OverrideConfig {
|
||||
fn from(init_config: Init) -> Self {
|
||||
OverrideConfig {
|
||||
nymd_validators: init_config.nymd_validators,
|
||||
api_validators: init_config.api_validators,
|
||||
nym_apis: init_config.nym_apis,
|
||||
port: init_config.port,
|
||||
use_anonymous_replies: init_config.use_reply_surbs,
|
||||
fastmode: init_config.fastmode,
|
||||
no_cover: init_config.no_cover,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
nyxd_urls: init_config.nyxd_urls,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: init_config.enabled_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct InitResults {
|
||||
#[serde(flatten)]
|
||||
client_core: client_core::init::InitResults,
|
||||
socks5_listening_port: String,
|
||||
}
|
||||
|
||||
impl InitResults {
|
||||
fn new(config: &Config, address: &Recipient) -> Self {
|
||||
Self {
|
||||
client_core: client_core::init::InitResults::new(config.get_base(), address),
|
||||
socks5_listening_port: config.get_listening_port().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InitResults {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.client_core)?;
|
||||
write!(f, "SOCKS5 listening port: {}", self.socks5_listening_port)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
println!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -96,25 +141,47 @@ pub(crate) async fn execute(args: &Init) {
|
||||
let register_gateway = !already_init || user_wants_force_register;
|
||||
|
||||
// Attempt to use a user-provided gateway, if possible
|
||||
let user_chosen_gateway_id = args.gateway.as_deref();
|
||||
let user_chosen_gateway_id = args.gateway;
|
||||
|
||||
let mut config = Config::new(id, provider_address);
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
// Load and potentially override config
|
||||
let mut config = override_config(
|
||||
Config::new(id, &provider_address.to_string()),
|
||||
OverrideConfig::from(args.clone()),
|
||||
);
|
||||
|
||||
// Setup gateway by either registering a new one, or creating a new config from the selected
|
||||
// one but with keys kept, or reusing the gateway configuration.
|
||||
let gateway = client_core::init::setup_gateway::<Config, _>(
|
||||
register_gateway,
|
||||
user_chosen_gateway_id.map(|id| id.to_base58_string()),
|
||||
config.get_base(),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to setup gateway\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
config.get_base_mut().with_gateway_endpoint(gateway);
|
||||
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
config
|
||||
.save_to_file(None)
|
||||
.expect("Failed to save the config file");
|
||||
config.save_to_file(None).tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config);
|
||||
|
||||
let address = client_core::init::get_client_address_from_stored_keys(config.get_base())?;
|
||||
let init_results = InitResults::new(&config, &address);
|
||||
println!("{}", init_results);
|
||||
|
||||
// Output summary to a json file, if specified
|
||||
if args.output_json {
|
||||
client_core::init::output_to_json(&init_results, "socks5_client_init_results.json");
|
||||
}
|
||||
|
||||
println!("\nThe address of this client is: {}\n", address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config) {
|
||||
let config_save_location = config.get_config_file_save_location();
|
||||
println!("Saved configuration file to {:?}", config_save_location);
|
||||
println!("Using gateway: {}", config.get_base().get_gateway_id());
|
||||
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
|
||||
@@ -123,62 +190,5 @@ pub(crate) async fn execute(args: &Init) {
|
||||
"Gateway listener: {}",
|
||||
config.get_base().get_gateway_listener()
|
||||
);
|
||||
println!("Client configuration completed.");
|
||||
|
||||
client_core::init::show_address(config.get_base()).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to show address\nError: {err}");
|
||||
std::process::exit(1)
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_gateway(
|
||||
id: &str,
|
||||
register: bool,
|
||||
user_chosen_gateway_id: Option<&str>,
|
||||
config: &Config,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
if register {
|
||||
// Get the gateway details by querying the validator-api. Either pick one at random or use
|
||||
// the chosen one if it's among the available ones.
|
||||
println!("Configuring gateway");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// Registering with gateway by setting up and writing shared keys to disk
|
||||
log::trace!("Registering gateway");
|
||||
client_core::init::register_with_gateway_and_store_keys(gateway.clone(), config.get_base())
|
||||
.await?;
|
||||
println!("Saved all generated keys");
|
||||
|
||||
Ok(gateway.into())
|
||||
} else if user_chosen_gateway_id.is_some() {
|
||||
// Just set the config, don't register or create any keys
|
||||
// This assumes that the user knows what they are doing, and that the existing keys are
|
||||
// valid for the gateway being used
|
||||
println!("Using gateway provided by user, keeping existing keys");
|
||||
let gateway = client_core::init::query_gateway_details(
|
||||
config.get_base().get_validator_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
Ok(gateway.into())
|
||||
} else {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
let existing_config = Config::load_from_file(Some(id)).map_err(|err| {
|
||||
log::error!(
|
||||
"Unable to configure gateway: {err}. \n
|
||||
Seems like the client was already initialized but it was not possible to read \
|
||||
the existing configuration file. \n
|
||||
CAUTION: Consider backing up your gateway keys and try force gateway registration, or \
|
||||
removing the existing configuration and starting over."
|
||||
);
|
||||
ClientCoreError::CouldNotLoadExistingGatewayConfiguration(err)
|
||||
})?;
|
||||
Ok(existing_config.get_base().get_gateway_endpoint().clone())
|
||||
}
|
||||
println!("Client configuration completed.\n");
|
||||
}
|
||||
|
||||
@@ -1,55 +1,31 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::client::config::{BaseConfig, Config};
|
||||
use build_information::BinaryBuildInformation;
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use completions::{fig_generate, ArgShell};
|
||||
use config::parse_validators;
|
||||
use config::OptionalSet;
|
||||
use lazy_static::lazy_static;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
fn long_version() -> String {
|
||||
format!(
|
||||
r#"
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
"Build Version:",
|
||||
env!("VERGEN_BUILD_SEMVER"),
|
||||
"Commit SHA:",
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
"Commit Date:",
|
||||
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
|
||||
"Commit Branch:",
|
||||
env!("VERGEN_GIT_BRANCH"),
|
||||
"rustc Version:",
|
||||
env!("VERGEN_RUSTC_SEMVER"),
|
||||
"rustc Channel:",
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
)
|
||||
lazy_static! {
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String =
|
||||
BinaryBuildInformation::new(env!("CARGO_PKG_VERSION")).pretty_print();
|
||||
}
|
||||
|
||||
fn long_version_static() -> &'static str {
|
||||
Box::leak(long_version().into_boxed_str())
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the client.
|
||||
#[clap(short, long)]
|
||||
@@ -79,66 +55,57 @@ pub(crate) enum Commands {
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
nymd_validators: Option<String>,
|
||||
api_validators: Option<String>,
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
port: Option<u16>,
|
||||
use_anonymous_replies: Option<bool>,
|
||||
fastmode: bool,
|
||||
no_cover: bool,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: bool,
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send>> {
|
||||
pub(crate) async fn execute(args: &Cli) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let bin_name = "nym-socks5-client";
|
||||
|
||||
match &args.command {
|
||||
Commands::Init(m) => init::execute(m).await,
|
||||
Commands::Init(m) => init::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),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
|
||||
if let Some(raw_validators) = args.nymd_validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validators(parse_validators(&raw_validators));
|
||||
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::NYMD_VALIDATOR) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validators(parse_validators(&raw_validators));
|
||||
}
|
||||
if let Some(raw_validators) = args.api_validators {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
} else if let Ok(raw_validators) = std::env::var(network_defaults::var_names::API_VALIDATOR) {
|
||||
config
|
||||
.get_base_mut()
|
||||
.set_custom_validator_apis(parse_validators(&raw_validators));
|
||||
}
|
||||
|
||||
if let Some(port) = args.port {
|
||||
config = config.with_port(port);
|
||||
}
|
||||
config = config
|
||||
.with_base(BaseConfig::with_high_default_traffic_volume, args.fastmode)
|
||||
.with_base(BaseConfig::with_disabled_cover_traffic, args.no_cover)
|
||||
.with_optional(Config::with_anonymous_replies, args.use_anonymous_replies)
|
||||
.with_optional(Config::with_port, args.port)
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nym_apis,
|
||||
args.nym_apis,
|
||||
network_defaults::var_names::NYM_API,
|
||||
config::parse_urls,
|
||||
);
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
{
|
||||
if args.enabled_credentials_mode {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
}
|
||||
|
||||
if args.fastmode {
|
||||
config.get_base_mut().set_high_default_traffic_volume();
|
||||
}
|
||||
|
||||
if args.no_cover {
|
||||
config.get_base_mut().set_no_cover_traffic();
|
||||
config = config
|
||||
.with_optional_custom_env_ext(
|
||||
BaseConfig::with_custom_nyxd,
|
||||
args.nyxd_urls,
|
||||
network_defaults::var_names::NYXD,
|
||||
config::parse_urls,
|
||||
)
|
||||
.with_optional_ext(
|
||||
BaseConfig::with_disabled_credentials,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
);
|
||||
}
|
||||
|
||||
config
|
||||
|
||||
@@ -9,7 +9,9 @@ use crate::{
|
||||
|
||||
use clap::Args;
|
||||
use config::NymConfig;
|
||||
use crypto::asymmetric::identity;
|
||||
use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use version_checker::is_minor_version_compatible;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
@@ -22,22 +24,32 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
config: Option<String>,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
/// While this is going to hide its actual address information, it will make the actual communication
|
||||
/// slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
///
|
||||
/// Note that some service providers might not support this.
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
#[clap(long, alias = "use_anonymous_sender_tag")]
|
||||
use_anonymous_replies: Option<bool>,
|
||||
|
||||
/// Address of the socks5 provider to send messages to.
|
||||
#[clap(long)]
|
||||
provider: Option<String>,
|
||||
provider: Option<Recipient>,
|
||||
|
||||
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
|
||||
/// ensure prior registration happened
|
||||
#[clap(long)]
|
||||
gateway: Option<String>,
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the nymd validators
|
||||
#[clap(long)]
|
||||
nymd_validators: Option<String>,
|
||||
/// Comma separated list of rest endpoints of the nyxd validators
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long, alias = "nymd_validators", value_delimiter = ',')]
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long)]
|
||||
api_validators: Option<String>,
|
||||
/// Comma separated list of rest endpoints of the Nym APIs
|
||||
#[clap(long, value_delimiter = ',')]
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Port for the socket to listen on
|
||||
#[clap(short, long)]
|
||||
@@ -45,28 +57,31 @@ pub(crate) struct Run {
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
fastmode: bool,
|
||||
|
||||
/// Disable loop cover traffic and the Poisson rate limiter (for debugging only)
|
||||
#[clap(long, hidden = true)]
|
||||
#[clap(long, hide = true)]
|
||||
no_cover: bool,
|
||||
|
||||
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
|
||||
/// with bandwidth credential requirement.
|
||||
#[cfg(feature = "coconut")]
|
||||
#[clap(long)]
|
||||
enabled_credentials_mode: bool,
|
||||
enabled_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<Run> for OverrideConfig {
|
||||
fn from(run_config: Run) -> Self {
|
||||
OverrideConfig {
|
||||
nymd_validators: run_config.nymd_validators,
|
||||
api_validators: run_config.api_validators,
|
||||
nym_apis: run_config.nym_apis,
|
||||
port: run_config.port,
|
||||
use_anonymous_replies: run_config.use_anonymous_replies,
|
||||
fastmode: run_config.fastmode,
|
||||
no_cover: run_config.no_cover,
|
||||
|
||||
#[cfg(feature = "coconut")]
|
||||
nyxd_urls: run_config.nyxd_urls,
|
||||
#[cfg(feature = "coconut")]
|
||||
enabled_credentials_mode: run_config.enabled_credentials_mode,
|
||||
}
|
||||
@@ -95,13 +110,13 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send>> {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
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);
|
||||
error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id);
|
||||
return Err(Box::new(Socks5ClientError::FailedToLoadConfig(
|
||||
id.to_string(),
|
||||
)));
|
||||
@@ -111,6 +126,10 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
|
||||
let override_config_fields = OverrideConfig::from(args.clone());
|
||||
config = override_config(config, override_config_fields);
|
||||
|
||||
if config.get_base_mut().set_empty_fields_to_defaults() {
|
||||
warn!("some of the core config options were left unset. the default values are going to get used instead.");
|
||||
}
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
|
||||
|
||||
@@ -58,7 +58,7 @@ pub(crate) struct Upgrade {
|
||||
|
||||
fn parse_config_version(config: &Config) -> Version {
|
||||
let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| {
|
||||
eprintln!("failed to parse client version! - {:?}", err);
|
||||
eprintln!("failed to parse client version! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
@@ -109,7 +109,7 @@ fn minor_0_12_upgrade(
|
||||
.set_custom_version(to_version.to_string().as_ref());
|
||||
|
||||
config.save_to_file(None).unwrap_or_else(|err| {
|
||||
eprintln!("failed to overwrite config file! - {:?}", err);
|
||||
eprintln!("failed to overwrite config file! - {err}");
|
||||
print_failed_upgrade(config_version, &to_version);
|
||||
process::exit(1);
|
||||
});
|
||||
@@ -145,7 +145,7 @@ pub(crate) fn execute(args: &Upgrade) {
|
||||
let id = &args.id;
|
||||
|
||||
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
|
||||
eprintln!("failed to load existing config file! - {:?}", err);
|
||||
eprintln!("failed to load existing config file! - {err}");
|
||||
process::exit(1)
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use client_core::error::ClientCoreError;
|
||||
|
||||
use crate::socks::types::SocksProxyError;
|
||||
use client_core::error::ClientCoreError;
|
||||
use socks5_requests::ConnectionId;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Socks5ClientError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("client-core error: {0}")]
|
||||
ClientCoreError(#[from] ClientCoreError),
|
||||
|
||||
@@ -14,8 +15,16 @@ pub enum Socks5ClientError {
|
||||
|
||||
#[error("Failed to load config for: {0}")]
|
||||
FailedToLoadConfig(String),
|
||||
|
||||
#[error("Failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
|
||||
#[error("Fail to bind address")]
|
||||
FailToBindAddress,
|
||||
|
||||
#[error("Network requester: connection id {connection_id}: {error}")]
|
||||
NetworkRequesterError {
|
||||
connection_id: ConnectionId,
|
||||
error: String,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error + Send>> {
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
setup_logging();
|
||||
println!("{}", banner());
|
||||
|
||||
let args = commands::Cli::parse();
|
||||
setup_env(args.config_env_file.clone());
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
commands::execute(&args).await
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use task::ShutdownListener;
|
||||
use task::TaskClient;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
||||
use tokio::{self, net::TcpStream};
|
||||
|
||||
@@ -126,11 +126,33 @@ impl AsyncWrite for StreamState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) struct Config {
|
||||
use_surbs_for_responses: bool,
|
||||
connection_start_surbs: u32,
|
||||
per_request_surbs: u32,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn new(
|
||||
use_surbs_for_responses: bool,
|
||||
connection_start_surbs: u32,
|
||||
per_request_surbs: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
use_surbs_for_responses,
|
||||
connection_start_surbs,
|
||||
per_request_surbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A client connecting to the Socks proxy server, because
|
||||
/// it wants to make a Nym-protected outbound request. Typically, this is
|
||||
/// something like e.g. a wallet app running on your laptop connecting to
|
||||
/// `SphinxSocksServer`.
|
||||
pub(crate) struct SocksClient {
|
||||
config: Config,
|
||||
controller_sender: ControllerSender,
|
||||
stream: StreamState,
|
||||
auth_nmethods: u8,
|
||||
@@ -142,7 +164,7 @@ pub(crate) struct SocksClient {
|
||||
self_address: Recipient,
|
||||
started_proxy: bool,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
shutdown_listener: ShutdownListener,
|
||||
shutdown_listener: TaskClient,
|
||||
}
|
||||
|
||||
impl Drop for SocksClient {
|
||||
@@ -160,6 +182,7 @@ impl Drop for SocksClient {
|
||||
impl SocksClient {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
config: Config,
|
||||
stream: TcpStream,
|
||||
authenticator: Authenticator,
|
||||
input_sender: InputMessageSender,
|
||||
@@ -167,7 +190,7 @@ impl SocksClient {
|
||||
controller_sender: ControllerSender,
|
||||
self_address: &Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
mut shutdown_listener: ShutdownListener,
|
||||
mut shutdown_listener: TaskClient,
|
||||
) -> Self {
|
||||
// If this task fails and exits, we don't want to send shutdown signal
|
||||
shutdown_listener.mark_as_success();
|
||||
@@ -175,6 +198,7 @@ impl SocksClient {
|
||||
let connection_id = Self::generate_random();
|
||||
|
||||
SocksClient {
|
||||
config,
|
||||
controller_sender,
|
||||
connection_id,
|
||||
stream: StreamState::Available(stream),
|
||||
@@ -196,7 +220,7 @@ impl SocksClient {
|
||||
}
|
||||
|
||||
pub async fn send_error(&mut self, err: SocksProxyError) -> Result<(), SocksProxyError> {
|
||||
let error_text = format!("{}", err);
|
||||
let error_text = format!("{err}");
|
||||
let Some(ref version) = self.socks_version else {
|
||||
log::error!("Trying to send error without knowing the version");
|
||||
return Ok(());
|
||||
@@ -268,14 +292,14 @@ impl SocksClient {
|
||||
self.handle_request().await
|
||||
}
|
||||
|
||||
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
|
||||
async fn send_anonymous_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
let req = Request::new_connect(self.connection_id, remote_address, None);
|
||||
let msg = Message::Request(req);
|
||||
|
||||
let input_message = InputMessage::new_fresh(
|
||||
let input_message = InputMessage::new_anonymous(
|
||||
self.service_provider,
|
||||
msg.into_bytes(),
|
||||
false,
|
||||
self.config.connection_start_surbs,
|
||||
TransmissionLane::ConnectionId(self.connection_id),
|
||||
);
|
||||
self.input_sender
|
||||
@@ -284,6 +308,30 @@ impl SocksClient {
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
async fn send_connect_to_mixnet_with_return_address(&mut self, remote_address: RemoteAddress) {
|
||||
let req = Request::new_connect(self.connection_id, remote_address, Some(self.self_address));
|
||||
let msg = Message::Request(req);
|
||||
|
||||
let input_message = InputMessage::new_regular(
|
||||
self.service_provider,
|
||||
msg.into_bytes(),
|
||||
TransmissionLane::ConnectionId(self.connection_id),
|
||||
);
|
||||
self.input_sender
|
||||
.send(input_message)
|
||||
.await
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
}
|
||||
|
||||
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
if self.config.use_surbs_for_responses {
|
||||
self.send_anonymous_connect_to_mixnet(remote_address).await
|
||||
} else {
|
||||
self.send_connect_to_mixnet_with_return_address(remote_address)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_proxy(&mut self, conn_receiver: ConnectionReceiver, remote_proxy_target: String) {
|
||||
self.send_connect_to_mixnet(remote_proxy_target.clone())
|
||||
.await;
|
||||
@@ -300,6 +348,8 @@ impl SocksClient {
|
||||
|
||||
let connection_id = self.connection_id;
|
||||
let input_sender = self.input_sender.clone();
|
||||
let anonymous = self.config.use_surbs_for_responses;
|
||||
let per_request_surbs = self.config.per_request_surbs;
|
||||
|
||||
let recipient = self.service_provider;
|
||||
let (stream, _) = ProxyRunner::new(
|
||||
@@ -316,7 +366,16 @@ impl SocksClient {
|
||||
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
|
||||
let provider_message = Message::Request(provider_request);
|
||||
let lane = TransmissionLane::ConnectionId(conn_id);
|
||||
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false, lane)
|
||||
if anonymous {
|
||||
InputMessage::new_anonymous(
|
||||
recipient,
|
||||
provider_message.into_bytes(),
|
||||
per_request_surbs,
|
||||
lane,
|
||||
)
|
||||
} else {
|
||||
InputMessage::new_regular(recipient, provider_message.into_bytes(), lane)
|
||||
}
|
||||
})
|
||||
.await
|
||||
.into_inner();
|
||||
|
||||
@@ -9,13 +9,15 @@ use client_core::client::received_buffer::{ReceivedBufferMessage, ReceivedBuffer
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use proxy_helpers::connection_controller::{ControllerCommand, ControllerSender};
|
||||
use socks5_requests::Message;
|
||||
use task::ShutdownListener;
|
||||
use task::TaskClient;
|
||||
|
||||
use crate::error::Socks5ClientError;
|
||||
|
||||
pub(crate) struct MixnetResponseListener {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
mix_response_receiver: ReconstructedMessagesReceiver,
|
||||
controller_sender: ControllerSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl Drop for MixnetResponseListener {
|
||||
@@ -25,9 +27,9 @@ impl Drop for MixnetResponseListener {
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
{
|
||||
if self.shutdown.is_shutdown_poll() {
|
||||
log::debug!("The buffer request failed: {}", err);
|
||||
log::debug!("The buffer request failed: {err}");
|
||||
} else {
|
||||
log::error!("The buffer request failed: {}", err);
|
||||
log::error!("The buffer request failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +39,7 @@ impl MixnetResponseListener {
|
||||
pub(crate) fn new(
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
controller_sender: ControllerSender,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
let (mix_response_sender, mix_response_receiver) = mpsc::unbounded();
|
||||
buffer_requester
|
||||
@@ -52,20 +54,23 @@ impl MixnetResponseListener {
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_message(&self, reconstructed_message: ReconstructedMessage) {
|
||||
fn on_message(
|
||||
&self,
|
||||
reconstructed_message: ReconstructedMessage,
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
let raw_message = reconstructed_message.message;
|
||||
if reconstructed_message.reply_surb.is_some() {
|
||||
warn!("this message had a surb - we didn't do anything with it");
|
||||
if reconstructed_message.sender_tag.is_some() {
|
||||
warn!("this message was sent anonymously - it couldn't have come from the service provider");
|
||||
}
|
||||
|
||||
let response = match Message::try_from_bytes(&raw_message) {
|
||||
Err(err) => {
|
||||
warn!("failed to parse received response - {:?}", err);
|
||||
return;
|
||||
warn!("failed to parse received response - {err}");
|
||||
return Ok(());
|
||||
}
|
||||
Ok(Message::Request(_)) => {
|
||||
warn!("unexpected request");
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
Ok(Message::Response(data)) => data,
|
||||
Ok(Message::NetworkRequesterResponse(r)) => {
|
||||
@@ -73,7 +78,10 @@ impl MixnetResponseListener {
|
||||
"Network requester failed on connection id {} with error: {}",
|
||||
r.connection_id, r.network_requester_error
|
||||
);
|
||||
return;
|
||||
return Err(Socks5ClientError::NetworkRequesterError {
|
||||
connection_id: r.connection_id,
|
||||
error: r.network_requester_error,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,18 +92,21 @@ impl MixnetResponseListener {
|
||||
response.is_closed,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
received_responses = self.mix_response_receiver.next() => match received_responses {
|
||||
Some(received_responses) => {
|
||||
received_responses = self.mix_response_receiver.next() => {
|
||||
if let Some(received_responses) = received_responses {
|
||||
for reconstructed_message in received_responses {
|
||||
self.on_message(reconstructed_message).await;
|
||||
if let Err(err) = self.on_message(reconstructed_message) {
|
||||
self.shutdown.send_status_msg(Box::new(err));
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
} else {
|
||||
log::trace!("MixnetResponseListener: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::convert::TryFrom;
|
||||
use self::types::SocksProxyError;
|
||||
|
||||
pub mod authentication;
|
||||
mod client;
|
||||
pub(crate) mod client;
|
||||
pub(crate) mod mixnet_responses;
|
||||
mod request;
|
||||
pub mod server;
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::error::Socks5ClientError;
|
||||
use super::{
|
||||
authentication::Authenticator, client::SocksClient, mixnet_responses::MixnetResponseListener,
|
||||
};
|
||||
use crate::socks::client;
|
||||
use client_connections::{ConnectionCommandSender, LaneQueueLengths};
|
||||
use client_core::client::{
|
||||
inbound_messages::InputMessageSender, received_buffer::ReceivedBufferRequestSender,
|
||||
@@ -12,7 +13,7 @@ use nymsphinx::addressing::clients::Recipient;
|
||||
use proxy_helpers::connection_controller::{BroadcastActiveConnections, Controller};
|
||||
use std::net::SocketAddr;
|
||||
use tap::TapFallible;
|
||||
use task::ShutdownListener;
|
||||
use task::TaskClient;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
/// A Socks5 server that listens for connections.
|
||||
@@ -21,8 +22,9 @@ pub struct SphinxSocksServer {
|
||||
listening_address: SocketAddr,
|
||||
service_provider: Recipient,
|
||||
self_address: Recipient,
|
||||
client_config: client::Config,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
shutdown: ShutdownListener,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl SphinxSocksServer {
|
||||
@@ -33,7 +35,8 @@ impl SphinxSocksServer {
|
||||
service_provider: Recipient,
|
||||
self_address: Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
shutdown: ShutdownListener,
|
||||
client_config: client::Config,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
// hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can
|
||||
// just modify the config
|
||||
@@ -44,6 +47,7 @@ impl SphinxSocksServer {
|
||||
listening_address: format!("{}:{}", ip, port).parse().unwrap(),
|
||||
service_provider,
|
||||
self_address,
|
||||
client_config,
|
||||
lane_queue_lengths,
|
||||
shutdown,
|
||||
}
|
||||
@@ -86,6 +90,7 @@ impl SphinxSocksServer {
|
||||
tokio::select! {
|
||||
Ok((stream, _remote)) = listener.accept() => {
|
||||
let mut client = SocksClient::new(
|
||||
self.client_config,
|
||||
stream,
|
||||
self.authenticator.clone(),
|
||||
input_sender.clone(),
|
||||
@@ -98,7 +103,7 @@ impl SphinxSocksServer {
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = client.run().await {
|
||||
error!("Error! {}", err);
|
||||
error!("Error! {err}");
|
||||
if client.send_error(err).await.is_err() {
|
||||
warn!("Failed to send error code");
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use snafu::Snafu;
|
||||
|
||||
/// SOCKS4 Response codes
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum ResponseCodeV4 {
|
||||
@@ -10,24 +8,26 @@ pub(crate) enum ResponseCodeV4 {
|
||||
}
|
||||
|
||||
/// Possible SOCKS5 Response Codes
|
||||
#[derive(Debug, Snafu)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum ResponseCodeV5 {
|
||||
#[error("SOCKS5 Server Success")]
|
||||
Success = 0x00,
|
||||
#[snafu(display("SOCKS5 Server Failure"))]
|
||||
#[error("SOCKS5 Server Failure")]
|
||||
Failure = 0x01,
|
||||
#[snafu(display("SOCKS5 Rule failure"))]
|
||||
#[error("SOCKS5 Rule failure")]
|
||||
RuleFailure = 0x02,
|
||||
#[snafu(display("network unreachable"))]
|
||||
#[error("network unreachable")]
|
||||
NetworkUnreachable = 0x03,
|
||||
#[snafu(display("host unreachable"))]
|
||||
#[error("host unreachable")]
|
||||
HostUnreachable = 0x04,
|
||||
#[snafu(display("connection refused"))]
|
||||
#[error("connection refused")]
|
||||
ConnectionRefused = 0x05,
|
||||
#[snafu(display("TTL expired"))]
|
||||
#[error("TTL expired")]
|
||||
TtlExpired = 0x06,
|
||||
#[snafu(display("Command not supported"))]
|
||||
#[error("Command not supported")]
|
||||
CommandNotSupported = 0x07,
|
||||
#[snafu(display("Addr Type not supported"))]
|
||||
#[error("Addr Type not supported")]
|
||||
AddrTypeNotSupported = 0x08,
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum SocksProxyError {
|
||||
impl std::fmt::Display for SocksProxyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SocksProxyError::GenericError(err) => write!(f, "GenericError - {}", err),
|
||||
SocksProxyError::GenericError(err) => write!(f, "GenericError - {err}"),
|
||||
SocksProxyError::UnsupportedProxyVersion(version) => {
|
||||
write!(f, "Unsupported proxy version {}", version)
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ export default class ValidatorClient implements INymClient {
|
||||
|
||||
static async connect(
|
||||
mnemonic: string,
|
||||
nymdUrl: string,
|
||||
validatorApiUrl: string,
|
||||
nyxdUrl: string,
|
||||
nymApiUrl: string,
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
@@ -94,19 +94,19 @@ export default class ValidatorClient implements INymClient {
|
||||
): Promise<ValidatorClient> {
|
||||
const wallet = await ValidatorClient.buildWallet(mnemonic, prefix);
|
||||
|
||||
const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom);
|
||||
const signingClient = await SigningClient.connectWithNymSigner(wallet, nyxdUrl, nymApiUrl, prefix, denom);
|
||||
return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom);
|
||||
}
|
||||
|
||||
static async connectForQuery(
|
||||
nymdUrl: string,
|
||||
validatorApiUrl: string,
|
||||
nyxdUrl: string,
|
||||
nymApiUrl: string,
|
||||
prefix: string,
|
||||
mixnetContract: string,
|
||||
vestingContract: string,
|
||||
denom: string,
|
||||
): Promise<ValidatorClient> {
|
||||
const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl);
|
||||
const queryClient = await QueryClient.connectWithNym(nyxdUrl, nymApiUrl);
|
||||
return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom);
|
||||
}
|
||||
|
||||
@@ -191,11 +191,11 @@ export default class ValidatorClient implements INymClient {
|
||||
return this.client.getIntervalRewardPercent(this.mixnetContract);
|
||||
}
|
||||
|
||||
public async getAllNymdMixnodes(): Promise<MixNodeBond[]> {
|
||||
public async getAllNyxdMixnodes(): Promise<MixNodeBond[]> {
|
||||
let mixNodes: MixNodeBond[] = [];
|
||||
const limit = 50;
|
||||
let startAfter;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedMixnodeResponse = await this.client.getMixNodesPaged(this.mixnetContract, limit);
|
||||
mixNodes = mixNodes.concat(pagedResponse.nodes);
|
||||
@@ -209,11 +209,11 @@ export default class ValidatorClient implements INymClient {
|
||||
return mixNodes;
|
||||
}
|
||||
|
||||
public async getAllNymdGateways(): Promise<GatewayBond[]> {
|
||||
public async getAllNyxdGateways(): Promise<GatewayBond[]> {
|
||||
let gateways: GatewayBond[] = [];
|
||||
const limit = 50;
|
||||
let startAfter;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedGatewayResponse = await this.client.getGatewaysPaged(this.mixnetContract, limit);
|
||||
gateways = gateways.concat(pagedResponse.nodes);
|
||||
@@ -232,11 +232,11 @@ export default class ValidatorClient implements INymClient {
|
||||
*
|
||||
* @param mixIdentity identity of the node to which the delegation was sent
|
||||
*/
|
||||
public async getAllNymdSingleMixnodeDelegations(mixIdentity: string): Promise<Delegation[]> {
|
||||
public async getAllNyxdSingleMixnodeDelegations(mixIdentity: string): Promise<Delegation[]> {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedMixDelegationsResponse = await this.client.getMixNodeDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
@@ -255,11 +255,11 @@ export default class ValidatorClient implements INymClient {
|
||||
return delegations;
|
||||
}
|
||||
|
||||
public async getAllNymdDelegatorDelegations(delegator: string): Promise<Delegation[]> {
|
||||
public async getAllNyxdDelegatorDelegations(delegator: string): Promise<Delegation[]> {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedDelegatorDelegationsResponse = await this.client.getDelegatorDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
@@ -278,11 +278,11 @@ export default class ValidatorClient implements INymClient {
|
||||
return delegations;
|
||||
}
|
||||
|
||||
public async getAllNymdNetworkDelegations(): Promise<Delegation[]> {
|
||||
public async getAllNyxdNetworkDelegations(): Promise<Delegation[]> {
|
||||
let delegations: Delegation[] = [];
|
||||
const limit = 250;
|
||||
let startAfter;
|
||||
for (;;) {
|
||||
for (; ;) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pagedResponse: PagedAllDelegationsResponse = await this.client.getAllNetworkDelegationsPaged(
|
||||
this.mixnetContract,
|
||||
|
||||
+18
-18
@@ -6,13 +6,13 @@
|
||||
import axios from 'axios';
|
||||
import { GatewayBond, MixNodeBond } from './types';
|
||||
|
||||
export const VALIDATOR_API_VERSION = '/v1';
|
||||
export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`;
|
||||
export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`;
|
||||
export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`;
|
||||
export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`;
|
||||
export const NYM_API_VERSION = '/v1';
|
||||
export const NYM_API_GATEWAYS_PATH = `${NYM_API_VERSION}/gateways`;
|
||||
export const NYM_API_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes`;
|
||||
export const NYM_API_ACTIVE_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes/active`;
|
||||
export const NYM_API_REWARDED_MIXNODES_PATH = `${NYM_API_VERSION}/mixnodes/rewarded`;
|
||||
|
||||
export interface IValidatorApiQuery {
|
||||
export interface INymApiQuery {
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]>;
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]>;
|
||||
@@ -22,16 +22,16 @@ export interface IValidatorApiQuery {
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]>;
|
||||
}
|
||||
|
||||
export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
validatorApiUrl: string;
|
||||
export default class NymApiQuerier implements INymApiQuery {
|
||||
nymApiUrl: string;
|
||||
|
||||
constructor(validatorApiUrl: string) {
|
||||
this.validatorApiUrl = validatorApiUrl;
|
||||
constructor(nymApiUrl: string) {
|
||||
this.nymApiUrl = nymApiUrl;
|
||||
}
|
||||
|
||||
async getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_MIXNODES_PATH;
|
||||
const url = new URL(this.nymApiUrl);
|
||||
url.pathname += NYM_API_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
@@ -41,8 +41,8 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
}
|
||||
|
||||
async getCachedGateways(): Promise<GatewayBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_GATEWAYS_PATH;
|
||||
const url = new URL(this.nymApiUrl);
|
||||
url.pathname += NYM_API_GATEWAYS_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
@@ -52,8 +52,8 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
}
|
||||
|
||||
async getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH;
|
||||
const url = new URL(this.nymApiUrl);
|
||||
url.pathname += NYM_API_ACTIVE_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
@@ -63,8 +63,8 @@ export default class ValidatorApiQuerier implements IValidatorApiQuery {
|
||||
}
|
||||
|
||||
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
const url = new URL(this.validatorApiUrl);
|
||||
url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH;
|
||||
const url = new URL(this.nymApiUrl);
|
||||
url.pathname += NYM_API_REWARDED_MIXNODES_PATH;
|
||||
|
||||
const response = await axios.get(url.toString());
|
||||
if (response.status === 200) {
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { JsonObject } from '@cosmjs/cosmwasm-stargate';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { INymdQuery } from './query-client';
|
||||
import { INyxdQuery } from './query-client';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
@@ -25,7 +25,7 @@ interface SmartContractQuery {
|
||||
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
|
||||
}
|
||||
|
||||
export default class NymdQuerier implements INymdQuery {
|
||||
export default class NyxdQuerier implements INyxdQuery {
|
||||
client: SmartContractQuery;
|
||||
|
||||
constructor(client: SmartContractQuery) {
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '@cosmjs/stargate';
|
||||
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import NymdQuerier from './nymd-querier';
|
||||
import NyxdQuerier from './nyxd-querier';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
PagedMixnodeResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
|
||||
import NymApiQuerier, { INymApiQuery as INymApiQuery } from './nym-api-querier';
|
||||
|
||||
export interface ICosmWasmQuery {
|
||||
// methods exposed by `CosmWasmClient`
|
||||
@@ -52,7 +52,7 @@ export interface ICosmWasmQuery {
|
||||
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
|
||||
}
|
||||
|
||||
export interface INymdQuery {
|
||||
export interface INyxdQuery {
|
||||
// nym-specific implemented inside NymQuerier
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
|
||||
|
||||
@@ -93,46 +93,46 @@ export interface INymdQuery {
|
||||
): Promise<RewardingStatus>;
|
||||
}
|
||||
|
||||
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {}
|
||||
export interface IQueryClient extends ICosmWasmQuery, INyxdQuery, INymApiQuery { }
|
||||
|
||||
export default class QueryClient extends CosmWasmClient implements IQueryClient {
|
||||
private nymdQuerier: NymdQuerier;
|
||||
private nyxdQuerier: NyxdQuerier;
|
||||
|
||||
private validatorApiQuerier: ValidatorApiQuerier;
|
||||
private nymApiQuerier: NymApiQuerier;
|
||||
|
||||
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
|
||||
private constructor(tmClient: Tendermint34Client, nymApiUrl: string) {
|
||||
super(tmClient);
|
||||
this.nymdQuerier = new NymdQuerier(this);
|
||||
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
|
||||
this.nyxdQuerier = new NyxdQuerier(this);
|
||||
this.nymApiQuerier = new NymApiQuerier(nymApiUrl);
|
||||
}
|
||||
|
||||
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> {
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new QueryClient(tmClient, validatorApiUrl);
|
||||
public static async connectWithNym(nyxdUrl: string, nymApiUrl: string): Promise<QueryClient> {
|
||||
const tmClient = await Tendermint34Client.connect(nyxdUrl);
|
||||
return new QueryClient(tmClient, nymApiUrl);
|
||||
}
|
||||
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
|
||||
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
|
||||
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
|
||||
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
|
||||
return this.nymdQuerier.getStateParams(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
@@ -140,7 +140,7 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse> {
|
||||
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getMixNodeDelegationsPaged(
|
||||
@@ -149,7 +149,7 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse> {
|
||||
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegatorDelegationsPaged(
|
||||
@@ -158,31 +158,31 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse> {
|
||||
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
|
||||
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
|
||||
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardingStatus(
|
||||
@@ -190,22 +190,22 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus> {
|
||||
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
}
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]> {
|
||||
return this.validatorApiQuerier.getCachedGateways();
|
||||
return this.nymApiQuerier.getCachedGateways();
|
||||
}
|
||||
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getCachedMixnodes();
|
||||
return this.nymApiQuerier.getCachedMixnodes();
|
||||
}
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getActiveMixnodes();
|
||||
return this.nymApiQuerier.getActiveMixnodes();
|
||||
}
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getRewardedMixnodes();
|
||||
return this.nymApiQuerier.getRewardedMixnodes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwa
|
||||
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
||||
import { nymGasPrice } from './stargate-helper';
|
||||
import { IQueryClient } from './query-client';
|
||||
import NymdQuerier from './nymd-querier';
|
||||
import NyxdQuerier from './nyxd-querier';
|
||||
import {
|
||||
ContractStateParams,
|
||||
Delegation,
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
PagedMixnodeResponse,
|
||||
RewardingStatus,
|
||||
} from './types';
|
||||
import ValidatorApiQuerier from './validator-api-querier';
|
||||
import NymApiQuerier from './nym-api-querier';
|
||||
|
||||
// methods exposed by `SigningCosmWasmClient`
|
||||
export interface ICosmWasmSigning {
|
||||
@@ -197,29 +197,29 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign
|
||||
}
|
||||
|
||||
export default class SigningClient extends SigningCosmWasmClient implements ISigningClient {
|
||||
private nymdQuerier: NymdQuerier;
|
||||
private nyxdQuerier: NyxdQuerier;
|
||||
|
||||
private validatorApiQuerier: ValidatorApiQuerier;
|
||||
private nymApiQuerier: NymApiQuerier;
|
||||
|
||||
clientAddress: string;
|
||||
|
||||
private constructor(
|
||||
clientAddress: string,
|
||||
validatorApiUrl: string,
|
||||
nymApiUrl: string,
|
||||
tmClient: Tendermint34Client,
|
||||
wallet: DirectSecp256k1HdWallet,
|
||||
signerOptions: SigningCosmWasmClientOptions,
|
||||
) {
|
||||
super(tmClient, wallet, signerOptions);
|
||||
this.clientAddress = clientAddress;
|
||||
this.nymdQuerier = new NymdQuerier(this);
|
||||
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
|
||||
this.nyxdQuerier = new NyxdQuerier(this);
|
||||
this.nymApiQuerier = new NymApiQuerier(nymApiUrl);
|
||||
}
|
||||
|
||||
public static async connectWithNymSigner(
|
||||
wallet: DirectSecp256k1HdWallet,
|
||||
nymdUrl: string,
|
||||
validatorApiUrl: string,
|
||||
nyxdUrl: string,
|
||||
nymApiUrl: string,
|
||||
prefix: string,
|
||||
denom: string,
|
||||
): Promise<SigningClient> {
|
||||
@@ -228,34 +228,34 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
prefix,
|
||||
gasPrice: nymGasPrice(denom),
|
||||
};
|
||||
const tmClient = await Tendermint34Client.connect(nymdUrl);
|
||||
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
|
||||
const tmClient = await Tendermint34Client.connect(nyxdUrl);
|
||||
return new SigningClient(address, nymApiUrl, tmClient, wallet, signerOptions);
|
||||
}
|
||||
|
||||
// query related:
|
||||
|
||||
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
|
||||
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getContractVersion(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
|
||||
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
|
||||
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
return this.nyxdQuerier.ownsMixNode(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
|
||||
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
return this.nyxdQuerier.ownsGateway(mixnetContractAddress, address);
|
||||
}
|
||||
|
||||
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
|
||||
return this.nymdQuerier.getStateParams(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getStateParams(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getAllNetworkDelegationsPaged(
|
||||
@@ -263,7 +263,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
limit?: number,
|
||||
startAfter?: [string, string],
|
||||
): Promise<PagedAllDelegationsResponse> {
|
||||
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
return this.nyxdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
|
||||
}
|
||||
|
||||
getMixNodeDelegationsPaged(
|
||||
@@ -272,7 +272,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedMixDelegationsResponse> {
|
||||
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
return this.nyxdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegatorDelegationsPaged(
|
||||
@@ -281,31 +281,31 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
limit?: number,
|
||||
startAfter?: string,
|
||||
): Promise<PagedDelegatorDelegationsResponse> {
|
||||
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
return this.nyxdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
|
||||
}
|
||||
|
||||
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
|
||||
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
return this.nyxdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
|
||||
}
|
||||
|
||||
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
|
||||
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getLayerDistribution(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardPool(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getRewardPool(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
|
||||
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getCirculatingSupply(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getIntervalRewardPercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getIntervalRewardPercent(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getIntervalRewardPercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
|
||||
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
return this.nyxdQuerier.getSybilResistancePercent(mixnetContractAddress);
|
||||
}
|
||||
|
||||
getRewardingStatus(
|
||||
@@ -313,23 +313,23 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig
|
||||
mixIdentity: string,
|
||||
rewardingIntervalNonce: number,
|
||||
): Promise<RewardingStatus> {
|
||||
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
return this.nyxdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
|
||||
}
|
||||
|
||||
getCachedGateways(): Promise<GatewayBond[]> {
|
||||
return this.validatorApiQuerier.getCachedGateways();
|
||||
return this.nymApiQuerier.getCachedGateways();
|
||||
}
|
||||
|
||||
getCachedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getCachedMixnodes();
|
||||
return this.nymApiQuerier.getCachedMixnodes();
|
||||
}
|
||||
|
||||
getActiveMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getActiveMixnodes();
|
||||
return this.nymApiQuerier.getActiveMixnodes();
|
||||
}
|
||||
|
||||
getRewardedMixnodes(): Promise<MixNodeBond[]> {
|
||||
return this.validatorApiQuerier.getRewardedMixnodes();
|
||||
return this.nymApiQuerier.getRewardedMixnodes();
|
||||
}
|
||||
|
||||
// signing related:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ValidatorClient from '../../dist';
|
||||
import ValidatorClient from '../../validator/index';
|
||||
import expect from 'expect';
|
||||
|
||||
describe('Query: balances', () => {
|
||||
|
||||
@@ -65,7 +65,6 @@ async function main() {
|
||||
*
|
||||
* 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;
|
||||
@@ -96,10 +95,13 @@ function displaySend(message) {
|
||||
/**
|
||||
* Display received text messages in the browser. Colour them green.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {Uint8Array} raw
|
||||
*/
|
||||
function displayReceived(message) {
|
||||
const content = message;
|
||||
function displayReceived(raw, sender_tag) {
|
||||
const content = new TextDecoder().decode(raw);
|
||||
if (sender_tag !== undefined) {
|
||||
console.log("this message also contained some surbs from", sender_tag)
|
||||
}
|
||||
|
||||
let timestamp = new Date().toISOString().substr(11, 12);
|
||||
let receivedDiv = document.createElement('div');
|
||||
@@ -116,7 +118,7 @@ function displayReceived(message) {
|
||||
/**
|
||||
* Display the nymClient's sender address in the user interface
|
||||
*
|
||||
* @param {Client} nymClient
|
||||
* @param {String} address
|
||||
*/
|
||||
function displaySenderAddress(address) {
|
||||
document.getElementById('sender').value = address;
|
||||
|
||||
+434
-166
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user