diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f160260d..b55407afe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +### Added + +- socks5: send status message for service ready, and network-requester error response + +### Changed + +- all-binaries: improved error logging ([#2686]) +- native client: bring shutdown logic up to the same level as socks5-client +- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement ([#2670]) + +[#2686]: https://github.com/nymtech/nym/pull/2686 +[#2670]: https://github.com/nymtech/nym/pull/2670 + ## [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. +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. +The release also include some additional work for distributed key generation in the Coconut signing authority nodes. ### Changed @@ -33,7 +46,7 @@ The release also include some additional work for distributed key generation in - 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]) +- 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]) @@ -53,7 +66,7 @@ The release also include some additional work for distributed key generation in - 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) @@ -89,7 +102,6 @@ The release also include some additional work for distributed key generation in [#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 @@ -116,7 +128,7 @@ The release also include some additional work for distributed key generation in - 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]) @@ -144,7 +156,6 @@ The release also include some additional work for distributed key generation in [#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 @@ -198,8 +209,7 @@ The release also include some additional work for distributed key generation in - 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]) - +- 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 @@ -293,9 +303,9 @@ The release also include some additional work for distributed key generation in - 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)) @@ -304,7 +314,7 @@ The release also include some additional work for distributed key generation in - 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)) @@ -344,14 +354,13 @@ The release also include some additional work for distributed key generation in - 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:** @@ -381,7 +390,7 @@ The release also include some additional work for distributed key generation in - 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:** @@ -396,7 +405,7 @@ The release also include some additional work for distributed key generation in - 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) @@ -406,7 +415,7 @@ The release also include some additional work for distributed key generation in - 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) @@ -462,10 +471,10 @@ The release also include some additional work for distributed key generation in - 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)) @@ -478,7 +487,7 @@ The release also include some additional work for distributed key generation in - 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)) @@ -496,7 +505,7 @@ The release also include some additional work for distributed key generation in - 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)) @@ -512,7 +521,7 @@ The release also include some additional work for distributed key generation in - 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)) @@ -536,8 +545,8 @@ The release also include some additional work for distributed key generation in - 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)) @@ -546,14 +555,14 @@ The release also include some additional work for distributed key generation in - 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)) @@ -565,7 +574,7 @@ The release also include some additional work for distributed key generation in - 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)) @@ -602,6 +611,4 @@ The release also include some additional work for distributed key generation in - 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)_ diff --git a/Cargo.lock b/Cargo.lock index 2256ba25f0..17a3c8ce2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,16 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array 0.14.5", +] + [[package]] name = "aes" version = "0.7.5" @@ -53,7 +63,7 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ - "aead", + "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", "ctr 0.8.0", @@ -81,6 +91,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -443,6 +459,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.73" @@ -474,6 +496,30 @@ dependencies = [ "keystream", ] +[[package]] +name = "chacha20" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.3", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.1", + "chacha20", + "cipher 0.4.3", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.19" @@ -488,6 +534,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.3.0" @@ -505,6 +578,7 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -967,9 +1041,9 @@ dependencies = [ "coconut-interface", "cosmrs", "crypto", + "nym-api-requests", "rand 0.7.3", "thiserror", - "validator-api-requests", "validator-client", ] @@ -980,9 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", - "cast", + "cast 0.2.7", "clap 2.34.0", - "criterion-plot", + "criterion-plot 0.4.4", "csv", "itertools", "lazy_static", @@ -999,13 +1073,49 @@ dependencies = [ "walkdir", ] +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast 0.3.0", + "ciborium", + "clap 3.2.8", + "criterion-plot 0.5.0", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "criterion-plot" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" dependencies = [ - "cast", + "cast 0.2.7", + "itertools", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast 0.3.0", "itertools", ] @@ -1116,6 +1226,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", + "thiserror", "x25519-dalek", ] @@ -1133,11 +1244,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array 0.14.5", + "rand_core 0.6.3", "typenum", ] @@ -1465,7 +1577,7 @@ dependencies = [ "bitvec", "bls12_381 0.6.0", "bs58", - "criterion", + "criterion 0.3.5", "ff 0.11.0", "group 0.11.0", "lazy_static", @@ -1666,6 +1778,9 @@ dependencies = [ "network-defaults", "okapi", "pretty_env_logger", + "rand 0.8.5", + "rand_pcg 0.3.1", + "rand_seeder", "reqwest", "rocket", "rocket_cors", @@ -1697,9 +1812,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -2934,6 +3049,7 @@ dependencies = [ "rand 0.8.5", "serde", "task", + "thiserror", "tokio", "tokio-util 0.7.3", "url", @@ -3089,6 +3205,21 @@ dependencies = [ "libc", ] +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "coconut-interface", + "cosmrs", + "cosmwasm-std", + "getset", + "mixnet-contract-common", + "schemars", + "serde", + "ts-rs", +] + [[package]] name = "nym-bity-integration" version = "0.1.0" @@ -3223,6 +3354,7 @@ dependencies = [ "mixnet-client", "mixnode-common", "network-defaults", + "nym-api-requests", "nymsphinx", "once_cell", "pemstore", @@ -3238,7 +3370,6 @@ dependencies = [ "tokio-tungstenite 0.14.0", "tokio-util 0.7.3", "url", - "validator-api-requests", "validator-client", "vergen 5.1.17", "version-checker", @@ -3334,6 +3465,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "nym-outfox" +version = "0.1.0" +dependencies = [ + "blake3", + "chacha20", + "chacha20poly1305", + "criterion 0.4.0", + "curve25519-dalek", + "fastrand", + "getrandom 0.2.6", + "rayon", + "thiserror", + "zeroize", +] + [[package]] name = "nym-socks5-client" version = "1.1.4" @@ -3434,6 +3581,7 @@ dependencies = [ "logging", "mixnet-contract-common", "multisig-contract-common", + "nym-api-requests", "nymcoconut", "nymsphinx", "okapi", @@ -3459,7 +3607,6 @@ dependencies = [ "topology", "ts-rs", "url", - "validator-api-requests", "validator-client", "vergen 7.2.1", "version-checker", @@ -3492,7 +3639,7 @@ dependencies = [ "bincode", "bls12_381 0.6.0", "bs58", - "criterion", + "criterion 0.3.5", "digest 0.9.0", "dkg", "doc-comment", @@ -3541,6 +3688,7 @@ dependencies = [ "nymsphinx-types", "pemstore", "rand 0.7.3", + "thiserror", "topology", ] @@ -3615,6 +3763,7 @@ dependencies = [ "bytes", "nymsphinx-params", "nymsphinx-types", + "thiserror", "tokio-util 0.7.3", ] @@ -3624,6 +3773,7 @@ version = "0.1.0" dependencies = [ "crypto", "nymsphinx-types", + "thiserror", ] [[package]] @@ -3707,6 +3857,7 @@ name = "ordered-buffer" version = "0.1.0" dependencies = [ "log", + "thiserror", ] [[package]] @@ -4000,6 +4151,17 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.5.0", +] + [[package]] name = "polyval" version = "0.5.3" @@ -4009,7 +4171,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] @@ -4231,7 +4393,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift", "winapi", ] @@ -4395,6 +4557,24 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core 0.6.3", +] + +[[package]] +name = "rand_seeder" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2890aaef0aa82719a50e808de264f9484b74b442e1a3a0e5ee38243ac40bdb" +dependencies = [ + "rand_core 0.6.3", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -6189,10 +6369,10 @@ version = "0.1.0" dependencies = [ "anyhow", "mixnet-contract-common", + "nym-api-requests", "nym-types", "nym-wallet-types", "ts-rs", - "validator-api-requests", "validator-client", "vesting-contract-common", "walkdir", @@ -6367,6 +6547,16 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle 2.4.1", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -6398,21 +6588,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", - "ts-rs", -] - [[package]] name = "validator-client" version = "0.1.0" @@ -6437,6 +6612,7 @@ dependencies = [ "mixnet-contract-common", "multisig-contract-common", "network-defaults", + "nym-api-requests", "prost 0.10.3", "reqwest", "serde", @@ -6446,7 +6622,6 @@ dependencies = [ "tokio", "ts-rs", "url", - "validator-api-requests", "vesting-contract", "vesting-contract-common", ] @@ -6905,9 +7080,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index e03bd46aeb..c3afc28de5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,8 +74,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 +88,7 @@ default-members = [ "service-providers/network-requester", "service-providers/network-statistics", "mixnode", - "validator-api", + "nym-api", "explorer-api", ] diff --git a/Makefile b/Makefile index c7100ed5e0..295274b072 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 9965849481..2c36fb892b 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -3,6 +3,7 @@ name = "client-core" version = "1.1.4" authors = ["Dave Hrycyszyn "] edition = "2021" +rust-version = "1.66" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clients/client-core/src/client/base_client/mod.rs b/clients/client-core/src/client/base_client/mod.rs index 932fb77b31..2a00a780cc 100644 --- a/clients/client-core/src/client/base_client/mod.rs +++ b/clients/client-core/src/client/base_client/mod.rs @@ -36,19 +36,19 @@ use nymsphinx::addressing::nodes::NodeIdentity; use std::sync::Arc; use std::time::Duration; use tap::TapFallible; -use task::{ShutdownListener, ShutdownNotifier}; +use task::{TaskClient, TaskManager}; use url::Url; #[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, } @@ -85,7 +85,7 @@ pub struct BaseClientBuilder<'a, B> { gateway_config: &'a GatewayEndpointConfig, debug_config: &'a DebugConfig, disabled_credentials: bool, - validator_api_endpoints: Vec, + nym_api_endpoints: Vec, reply_storage_backend: B, bandwidth_controller: Option, @@ -106,7 +106,7 @@ where 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, @@ -120,15 +120,15 @@ where bandwidth_controller: Option, reply_storage_backend: B, disabled_credentials: bool, - validator_api_endpoints: Vec, + nym_api_endpoints: Vec, ) -> BaseClientBuilder<'a, B> { BaseClientBuilder { gateway_config, debug_config, disabled_credentials, - validator_api_endpoints, - bandwidth_controller, + nym_api_endpoints, reply_storage_backend, + bandwidth_controller, key_manager, } } @@ -151,7 +151,7 @@ where self_address: Recipient, topology_accessor: TopologyAccessor, mix_tx: BatchMixMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting loop cover traffic stream..."); @@ -185,7 +185,7 @@ where reply_controller_receiver: ReplyControllerReceiver, lane_queue_lengths: LaneQueueLengths, client_connection_rx: ConnectionCommandReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting real traffic stream..."); @@ -212,7 +212,7 @@ where mixnet_receiver: MixnetMessageReceiver, reply_key_storage: SentReplyKeys, reply_controller_sender: ReplyControllerSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting received messages buffer controller..."); ReceivedMessagesBufferController::new( @@ -229,7 +229,7 @@ where &mut self, mixnet_message_sender: MixnetMessageSender, ack_sender: AcknowledgementSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Result> { let gateway_id = self.gateway_config.gateway_id.clone(); if gateway_id.is_empty() { @@ -281,13 +281,13 @@ where // future responsible for periodically polling directory server and updating // the current global view of topology async fn start_topology_refresher( - validator_api_urls: Vec, + nym_api_urls: Vec, refresh_rate: Duration, topology_accessor: TopologyAccessor, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Result<(), ClientCoreError> { let topology_refresher_config = TopologyRefresherConfig::new( - validator_api_urls, + nym_api_urls, refresh_rate, env!("CARGO_PKG_VERSION").to_string(), ); @@ -317,7 +317,7 @@ where // 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); @@ -327,7 +327,7 @@ where async fn setup_persistent_reply_storage( backend: B, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Result> { let persistent_storage = PersistentReplyStorage::new(backend); let mem_store = persistent_storage @@ -367,29 +367,31 @@ where let shared_topology_accessor = TopologyAccessor::new(); // 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::new_control_channels(); + 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. let gateway_client = self - .start_gateway_client(mixnet_messages_sender, ack_sender, shutdown.subscribe()) + .start_gateway_client(mixnet_messages_sender, ack_sender, task_manager.subscribe()) .await?; - let reply_storage = - Self::setup_persistent_reply_storage(self.reply_storage_backend, shutdown.subscribe()) - .await?; + let reply_storage = Self::setup_persistent_reply_storage( + self.reply_storage_backend, + task_manager.subscribe(), + ) + .await?; Self::start_topology_refresher( - self.validator_api_endpoints.clone(), + self.nym_api_endpoints.clone(), self.debug_config.topology_refresh_rate, shared_topology_accessor.clone(), - shutdown.subscribe(), + task_manager.subscribe(), ) .await?; @@ -399,7 +401,7 @@ where mixnet_messages_receiver, reply_storage.key_storage(), reply_controller_sender.clone(), - shutdown.subscribe(), + task_manager.subscribe(), ); // The sphinx_message_sender is the transmitter for any component generating sphinx packets @@ -407,7 +409,7 @@ where // 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. @@ -435,11 +437,11 @@ where input_receiver, sphinx_message_sender.clone(), reply_storage, - reply_controller_sender, + reply_controller_sender.clone(), reply_controller_receiver, shared_lane_queue_lengths.clone(), client_connection_rx, - shutdown.subscribe(), + task_manager.subscribe(), ); if !self.debug_config.disable_loop_cover_traffic_stream { @@ -449,7 +451,7 @@ where self_address, shared_topology_accessor, sphinx_message_sender, - shutdown.subscribe(), + task_manager.subscribe(), ); } @@ -459,17 +461,18 @@ where 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, }) } } @@ -478,5 +481,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, } diff --git a/clients/client-core/src/client/cover_traffic_stream.rs b/clients/client-core/src/client/cover_traffic_stream.rs index e3fd0ca197..a8ecad86b2 100644 --- a/clients/client-core/src/client/cover_traffic_stream.rs +++ b/clients/client-core/src/client/cover_traffic_stream.rs @@ -213,7 +213,7 @@ impl LoopCoverTrafficStream { 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); diff --git a/clients/client-core/src/client/helpers/mod.rs b/clients/client-core/src/client/helpers/mod.rs new file mode 100644 index 0000000000..ffffea0313 --- /dev/null +++ b/clients/client-core/src/client/helpers/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2022 - Nym Technologies SA +// 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::*; diff --git a/clients/client-core/src/client/helpers/non_wasm.rs b/clients/client-core/src/client/helpers/non_wasm.rs new file mode 100644 index 0000000000..ff5c9c0047 --- /dev/null +++ b/clients/client-core/src/client/helpers/non_wasm.rs @@ -0,0 +1,13 @@ +// Copyright 2022 - Nym Technologies SA +// 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)) +} diff --git a/clients/client-core/src/client/helpers/wasm.rs b/clients/client-core/src/client/helpers/wasm.rs new file mode 100644 index 0000000000..f04dd64028 --- /dev/null +++ b/clients/client-core/src/client/helpers/wasm.rs @@ -0,0 +1,16 @@ +// Copyright 2022 - Nym Technologies SA +// 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) +} diff --git a/clients/client-core/src/client/mix_traffic.rs b/clients/client-core/src/client/mix_traffic.rs index f61a83af95..94944cb481 100644 --- a/clients/client-core/src/client/mix_traffic.rs +++ b/clients/client-core/src/client/mix_traffic.rs @@ -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; } } } diff --git a/clients/client-core/src/client/mod.rs b/clients/client-core/src/client/mod.rs index 8110d52909..789dd300de 100644 --- a/clients/client-core/src/client/mod.rs +++ b/clients/client-core/src/client/mod.rs @@ -3,6 +3,7 @@ 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; @@ -10,3 +11,4 @@ pub mod real_messages_control; pub mod received_buffer; pub mod replies; pub mod topology_control; +pub(crate) mod transmission_buffer; diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs index 95c431fd59..8febbfa85d 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/acknowledgement_listener.rs @@ -65,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() { @@ -77,7 +77,7 @@ impl AcknowledgementListener { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("AcknowledgementListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index cb2e0cc7ca..c65592b2f6 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -249,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() { @@ -270,7 +270,7 @@ impl ActionController { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("ActionController: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index cff6b6d719..f492de1aae 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -109,7 +109,7 @@ where }; } - 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() { @@ -123,7 +123,7 @@ where break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("InputMessageListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 5587958f33..befc24b86a 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -249,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; diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index b5c6e6a2a8..d8fb2b0a8d 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -137,7 +137,7 @@ where .await } - 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() { diff --git a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs index c273182d59..a931ce4962 100644 --- a/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs +++ b/clients/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs @@ -37,7 +37,7 @@ impl SentNotificationListener { .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() { @@ -51,7 +51,7 @@ impl SentNotificationListener { break; } }, - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("SentNotificationListener: Received shutdown"); } } diff --git a/clients/client-core/src/client/real_messages_control/message_handler.rs b/clients/client-core/src/client/real_messages_control/message_handler.rs index 1c80ef519f..d6fcaab0c0 100644 --- a/clients/client-core/src/client/real_messages_control/message_handler.rs +++ b/clients/client-core/src/client/real_messages_control/message_handler.rs @@ -20,6 +20,7 @@ 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; @@ -278,7 +279,7 @@ where reply_surb: ReplySurb, amount: u32, ) -> Result<(), SurbWrappedPreparationError> { - debug!("requesting {amount} reply SURBs from {from:?}"); + debug!("requesting {amount} reply SURBs from {from}"); let surbs_request = ReplyMessage::new_surb_request_message(self.config.sender_address, amount); @@ -294,19 +295,11 @@ where ))) } - // the only difference between this method and `try_send_reply_chunks` is that - // here we are not creating acks as acks are already in memory waiting to get cleared. - // we are only updating their existing delays - pub(crate) async fn try_send_retransmission_reply_chunks( + pub(crate) async fn send_retransmission_reply_chunks( &mut self, - fragments: Vec, - reply_surbs: Vec, + prepared_fragments: Vec, lane: TransmissionLane, - ) -> Result<(), SurbWrappedPreparationError> { - let prepared_fragments = self - .prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs) - .await?; - + ) { let mut real_messages = Vec::with_capacity(prepared_fragments.len()); for prepared in prepared_fragments { @@ -315,33 +308,58 @@ where } self.forward_messages(real_messages, lane).await; - Ok(()) } - pub(crate) async fn try_send_reply_chunks( + pub(crate) async fn try_send_reply_chunks_on_lane( &mut self, target: AnonymousSenderTag, fragments: Vec, reply_surbs: Vec, 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, ) -> Result<(), SurbWrappedPreparationError> { let prepared_fragments = self - .prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs) + .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 real_messages = 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(raw, delay, target, false); + let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false); - real_messages.push(real_message); + let entry = to_forward.entry(lane).or_default(); + entry.push(real_message); pending_acks.push(pending_ack); } - self.forward_messages(real_messages, lane).await; + for (lane, real_messages) in to_forward { + self.forward_messages(real_messages, lane).await; + } + self.insert_pending_acks(pending_acks); Ok(()) } @@ -467,7 +485,7 @@ where Ok(prepared_fragment) } - async fn prepare_reply_chunks_for_sending( + pub(crate) async fn prepare_reply_chunks_for_sending( &mut self, fragments: Vec, reply_surbs: Vec, diff --git a/clients/client-core/src/client/real_messages_control/mod.rs b/clients/client-core/src/client/real_messages_control/mod.rs index 8789cbdf6a..df04365c9b 100644 --- a/clients/client-core/src/client/real_messages_control/mod.rs +++ b/clients/client-core/src/client/real_messages_control/mod.rs @@ -263,7 +263,7 @@ impl RealMessagesController { } } - 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; diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs index 9b75669960..2abbd4661e 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -1,9 +1,11 @@ // Copyright 2021 - Nym Technologies SA // 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, }; @@ -29,22 +31,7 @@ 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 { @@ -135,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, /// Incoming channel for being notified of closed connections, so that we can close lanes /// corresponding to connections. To avoid sending traffic unnecessary @@ -162,6 +149,10 @@ impl From for RealMessage { } 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, @@ -207,7 +198,7 @@ where real_receiver, rng, topology_access, - transmission_buffer: Default::default(), + transmission_buffer: TransmissionBuffer::new(), client_connection_rx, lane_queue_lengths, } @@ -264,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 @@ -328,7 +319,9 @@ where fn pop_next_message(&mut self) -> Option { // 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); @@ -469,7 +462,7 @@ where } #[cfg(not(target_arch = "wasm32"))] - fn log_status(&self, shutdown: &mut task::ShutdownListener) { + fn log_status(&self, shutdown: &mut task::TaskClient) { use crate::error::ClientCoreStatusMessage; let packets = self.transmission_buffer.total_size(); @@ -514,7 +507,7 @@ where } } - 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"))] @@ -525,7 +518,7 @@ where while !shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("OutQueueControl: Received shutdown"); } _ = status_timer.tick() => { diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs b/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs index 4ae2632b85..b2d656f763 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs +++ b/clients/client-core/src/client/real_messages_control/real_traffic_stream/sending_delay_controller.rs @@ -1,14 +1,9 @@ // Copyright 2021 - Nym Technologies SA // 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,10 +64,12 @@ 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 } diff --git a/clients/client-core/src/client/received_buffer.rs b/clients/client-core/src/client/received_buffer.rs index 2e7876ae62..afafec44ca 100644 --- a/clients/client-core/src/client/received_buffer.rs +++ b/clients/client-core/src/client/received_buffer.rs @@ -399,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; } }, } @@ -439,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"); } } @@ -490,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; diff --git a/clients/client-core/src/client/replies/reply_controller.rs b/clients/client-core/src/client/replies/reply_controller/mod.rs similarity index 77% rename from clients/client-core/src/client/replies/reply_controller.rs rename to clients/client-core/src/client/replies/reply_controller/mod.rs index 99687527f0..e8b8d980a9 100644 --- a/clients/client-core/src/client/replies/reply_controller.rs +++ b/clients/client-core/src/client/replies/reply_controller/mod.rs @@ -4,8 +4,8 @@ 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::TransmissionLane; -use futures::channel::mpsc; +use client_connections::{ConnectionId, TransmissionLane}; +use futures::channel::oneshot; use futures::StreamExt; use log::{debug, error, info, trace, warn}; use nymsphinx::addressing::clients::Recipient; @@ -15,116 +15,16 @@ 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, VecDeque}; +use std::collections::{BTreeMap, HashMap}; use std::sync::{Arc, Weak}; use std::time::Duration; use time::OffsetDateTime; -#[cfg(not(target_arch = "wasm32"))] -type IntervalStream = tokio_stream::wrappers::IntervalStream; +use crate::client::helpers::new_interval_stream; +use crate::client::transmission_buffer::TransmissionBuffer; +pub(crate) use requests::{ReplyControllerMessage, ReplyControllerReceiver, ReplyControllerSender}; -#[cfg(target_arch = "wasm32")] -type IntervalStream = gloo_timers::future::IntervalStream; - -pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) { - let (tx, rx) = mpsc::unbounded(); - (tx.into(), rx) -} - -#[derive(Debug, Clone)] -pub(crate) struct ReplyControllerSender(mpsc::UnboundedSender); - -impl From> for ReplyControllerSender { - fn from(inner: mpsc::UnboundedSender) -> Self { - ReplyControllerSender(inner) - } -} - -impl ReplyControllerSender { - pub(crate) fn send_retransmission_data( - &self, - recipient: AnonymousSenderTag, - timed_out_ack: Weak, - 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, - 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, - 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(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver; - -#[derive(Debug)] -pub(crate) enum ReplyControllerMessage { - RetransmitReply { - recipient: AnonymousSenderTag, - timed_out_ack: Weak, - extra_surb_request: bool, - }, - - SendReply { - recipient: AnonymousSenderTag, - message: Vec, - lane: TransmissionLane, - }, - - AdditionalSurbs { - sender_tag: AnonymousSenderTag, - reply_surbs: Vec, - from_surb_request: bool, - }, - - // 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, - amount: u32, - }, -} +pub mod requests; pub struct Config { min_surb_request_size: u32, @@ -172,7 +72,7 @@ pub struct ReplyController { // of surbs required to send the message through // expected_reliability: f32, request_receiver: ReplyControllerReceiver, - pending_replies: HashMap>, + pending_replies: HashMap>, /// 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. @@ -204,17 +104,28 @@ where } } - /// Inserts the pending replies into the BACK of the queue fn insert_pending_replies>>( - fn insert_pending_replies>>( + fn insert_pending_replies>( &mut self, recipient: &AnonymousSenderTag, - fragments: V, + fragments: I, + lane: TransmissionLane, ) { - if let Some(existing) = self.pending_replies.get_mut(recipient) { - existing.append(&mut fragments.into()) - } else { - self.pending_replies.insert(*recipient, fragments.into()); - } + 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( @@ -244,7 +155,7 @@ where let pending_queue_size = self .pending_replies .get(target) - .map(|pending_queue| pending_queue.len()) + .map(|pending_queue| pending_queue.total_size()) .unwrap_or_default(); let retransmission_queue = self @@ -299,43 +210,62 @@ where } trace!("handling reply to {:?}", recipient_tag); - let fragments = self.message_handler.split_reply_message(data); + let mut fragments = self.message_handler.split_reply_message(data); + let total_size = fragments.len(); + trace!("This reply requires {:?} SURBs", total_size); - let required_surbs = fragments.len(); - trace!("This reply requires {:?} SURBs", required_surbs); - - // TODO: edge case: - // we're making a lot of requests and have to request a lot of surbs - // (but at some point we run out of surbs for surb requests) - - let (surbs, _surbs_left) = self + let available_surbs = self .full_reply_storage .surbs_storage_ref() - .get_reply_surbs(&recipient_tag, required_surbs); + .available_surbs(&recipient_tag); + let min_surbs_threshold = self + .full_reply_storage + .surbs_storage_ref() + .min_surb_threshold(); - if let Some(reply_surbs) = surbs { - if let Err(err) = self - .message_handler - .try_send_reply_chunks(recipient_tag, fragments, 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 {:?} - {err}", recipient_tag); - - // TODO: should we buffer that data to try again? - } + let max_to_send = if available_surbs > min_surbs_threshold { + min(fragments.len(), available_surbs - min_surbs_threshold) } else { - // we don't have enough surbs for this reply - self.insert_pending_replies(&recipient_tag, fragments); + 0 + }; - if self.should_request_more_surbs(&recipient_tag) { - self.request_reply_surbs_for_queue_clearing(recipient_tag) - .await; + 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::>(); + 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( @@ -398,35 +328,18 @@ where }; let mut to_take = Vec::new(); - let mut to_remove = Vec::new(); - // TODO: once rust 1.66.0 is stabilised on 15.12.22, just change it to - // `.pop_front()` to directly take ownership - for (k, data) in pending.iter() { - let upgraded = match data.upgrade() { - Some(upgraded) => upgraded, - None => { - // we got the ack while the data was waiting in the queue - to_remove.push(*k); - continue; + 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) } - }; - - to_take.push(upgraded); - - // we have taken as many entries as we could have - if to_take.len() >= max_to_clear { + } else { + // our map is empty! break; } - // TODO: use if upgraded.is_extra_surb_request() to bypass the limit - } - - for ack in &to_take { - pending.remove(&ack.inner_fragment_identifier()); - } - - for id in to_remove { - pending.remove(&id); } if to_take.is_empty() { @@ -447,46 +360,47 @@ where let to_send_vec = to_take.iter().map(|ack| ack.fragment_data()).collect(); - if let Err(err) = self + let prepared_fragments = match self .message_handler - .try_send_retransmission_reply_chunks( - to_send_vec, - surbs_for_reply, - TransmissionLane::Retransmission, - ) + .prepare_reply_chunks_for_sending(to_send_vec, surbs_for_reply) .await { - let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target); - self.re_insert_pending_retransmission(&target, to_take); + 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 - ); - } + 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> { + ) -> Option> { // 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)?.len(); + let total = self.pending_replies.get(from)?.total_size(); trace!("pending queue has {total} elements"); if total == 0 { return None; } - if total < amount { - self.pending_replies.remove(from) - } else { - Some( - self.pending_replies - .get_mut(from)? - .drain(..amount) - .collect(), - ) - } + 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) { @@ -510,9 +424,9 @@ where // 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_vec = to_send.iter().cloned().collect::>(); + let to_send_clone = to_send.clone(); - if to_send_vec.is_empty() { + if to_send_clone.is_empty() { panic!( "please let the devs know if you ever see this message (reply_controller.rs)" ); @@ -521,27 +435,22 @@ where let (surbs_for_reply, _) = self .full_reply_storage .surbs_storage_ref() - .get_reply_surbs(&target, to_send_vec.len()); + .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.insert_pending_replies(&target, to_send); + self.re_insert_pending_replies(&target, to_send); return; }; if let Err(err) = self .message_handler - .try_send_reply_chunks( - target, - to_send_vec, - surbs_for_reply, - TransmissionLane::General, - ) + .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.insert_pending_replies(&target, to_send); + self.re_insert_pending_replies(&target, to_send); warn!("failed to clear pending queue for {:?} - {err}", target); } } else { @@ -712,6 +621,30 @@ where } } + // 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, + ) { + // 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 { @@ -735,19 +668,26 @@ where 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.len()) + .map(|pending_queue| pending_queue.total_size()) .unwrap_or_default(); let retransmission_queue = self @@ -787,7 +727,7 @@ where } 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.len()); + 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; }; @@ -883,28 +823,25 @@ where } } - fn create_interval_stream(polling_rate: Duration) -> IntervalStream { - #[cfg(not(target_arch = "wasm32"))] - return tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate)); + // #[cfg(not(target_arch = "wasm32"))] + // async fn log_status(&self) { + // todo!() + // } - #[cfg(target_arch = "wasm32")] - return gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32); - } - - pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::ShutdownListener) { + 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 = Self::create_interval_stream(polling_rate); + 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 = Self::create_interval_stream(polling_rate); + let mut invalidation_inspection = new_interval_stream(polling_rate); while !shutdown.is_shutdown() { tokio::select! { biased; - _ = shutdown.recv() => { + _ = shutdown.recv_with_delay() => { log::trace!("ReplyController: Received shutdown"); }, req = self.request_receiver.next() => match req { diff --git a/clients/client-core/src/client/replies/reply_controller/requests.rs b/clients/client-core/src/client/replies/reply_controller/requests.rs new file mode 100644 index 0000000000..1f47f2ca07 --- /dev/null +++ b/clients/client-core/src/client/replies/reply_controller/requests.rs @@ -0,0 +1,136 @@ +// Copyright 2022 - Nym Technologies SA +// 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); + +impl From> for ReplyControllerSender { + fn from(inner: mpsc::UnboundedSender) -> Self { + ReplyControllerSender(inner) + } +} + +impl ReplyControllerSender { + pub(crate) fn send_retransmission_data( + &self, + recipient: AnonymousSenderTag, + timed_out_ack: Weak, + 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, + 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, + 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; + +#[derive(Debug)] +pub(crate) enum ReplyControllerMessage { + RetransmitReply { + recipient: AnonymousSenderTag, + timed_out_ack: Weak, + extra_surb_request: bool, + }, + + SendReply { + recipient: AnonymousSenderTag, + message: Vec, + lane: TransmissionLane, + }, + + AdditionalSurbs { + sender_tag: AnonymousSenderTag, + reply_surbs: Vec, + from_surb_request: bool, + }, + + // this one doesn't belong here either... + LaneQueueLength { + connection_id: ConnectionId, + response_channel: oneshot::Sender, + }, + + // 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, + amount: u32, + }, +} diff --git a/clients/client-core/src/client/replies/reply_storage/mod.rs b/clients/client-core/src/client/replies/reply_storage/mod.rs index 83c0e0e34f..c74ad2bb6b 100644 --- a/clients/client-core/src/client/replies/reply_storage/mod.rs +++ b/clients/client-core/src/client/replies/reply_storage/mod.rs @@ -37,7 +37,7 @@ where pub async fn flush_on_shutdown( mut self, mem_state: CombinedReplyStorage, - mut shutdown: task::ShutdownListener, + mut shutdown: task::TaskClient, ) { use log::{debug, error, info, warn}; diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index ad9e4a4ce6..c51fd350cd 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -136,19 +136,15 @@ impl Default for TopologyAccessor { } pub struct TopologyRefresherConfig { - validator_api_urls: Vec, + nym_api_urls: Vec, refresh_rate: Duration, client_version: String, } impl TopologyRefresherConfig { - pub fn new( - validator_api_urls: Vec, - refresh_rate: Duration, - client_version: String, - ) -> Self { + pub fn new(nym_api_urls: Vec, refresh_rate: Duration, client_version: String) -> Self { TopologyRefresherConfig { - validator_api_urls, + nym_api_urls, refresh_rate, client_version, } @@ -159,7 +155,7 @@ pub struct TopologyRefresher { validator_client: validator_client::client::ApiClient, client_version: String, - validator_api_urls: Vec, + nym_api_urls: Vec, topology_accessor: TopologyAccessor, refresh_rate: Duration, @@ -169,14 +165,12 @@ 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::ApiClient::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, @@ -184,15 +178,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. @@ -288,7 +282,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 { @@ -310,7 +304,7 @@ impl TopologyRefresher { 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"); diff --git a/clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs b/clients/client-core/src/client/transmission_buffer.rs similarity index 54% rename from clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs rename to clients/client-core/src/client/transmission_buffer.rs index 1f16f472c1..1420ce0a7b 100644 --- a/clients/client-core/src/client/real_messages_control/real_traffic_stream/transmission_buffer.rs +++ b/clients/client-core/src/client/transmission_buffer.rs @@ -1,38 +1,57 @@ // Copyright 2022 - Nym Technologies SA // 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 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 = 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, +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 { + buffer: HashMap>, +} + +impl TransmissionBuffer { + 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 { + pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option> { 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::() }) .sum() @@ -92,42 +113,51 @@ impl TransmissionBuffer { .collect() } - pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec) { + pub(crate) fn store>(&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(&self, rng: &mut R) -> Option<&TransmissionLane> { + let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect(); + lanes.choose(rng).copied() + } + + fn pick_random_small_lane(&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() } // 2/3 chance to pick from the old lanes - fn pick_random_old_lane(&self) -> Option { + fn pick_random_old_lane(&self, rng: &mut R) -> Option { 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().copied() + self.pick_random_lane(rng).copied() } } - fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option { + fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option { let real_msgs_queued = self.buffer.get_mut(lane)?; let real_next = real_msgs_queued.pop_front()?; real_msgs_queued.messages_transmitted += 1; @@ -137,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> { + // 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( + &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)?; @@ -171,35 +230,46 @@ impl TransmissionBuffer { } } -pub(crate) struct LaneBufferEntry { - pub real_messages: VecDeque, +pub(crate) struct LaneBufferEntry { + pub items: VecDeque, 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) -> Self { +impl LaneBufferEntry { + 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) { - self.real_messages.append(&mut real_messages.into()); + fn new(items: VecDeque) -> 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>(&mut self, items: I) { + self.items.extend(items); self.time_for_last_activity = get_time_now(); } - fn pop_front(&mut self) -> Option { - self.real_messages.pop_front() + fn pop_front(&mut self) -> Option { + self.items.pop_front() } fn is_small(&self) -> bool { - self.real_messages.len() < 100 + self.items.len() < 100 } fn is_stale(&self) -> bool { @@ -208,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() } } diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 0ec88a8095..18746545dd 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -176,8 +176,8 @@ impl Config { self.client.validator_urls = validator_urls; } - pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec) { - self.client.validator_api_urls = validator_api_urls; + pub fn set_custom_nym_apis(&mut self, nym_api_urls: Vec) { + self.client.nym_api_urls = nym_api_urls; } pub fn set_high_default_traffic_volume(&mut self) { @@ -237,8 +237,8 @@ impl Config { self.client.validator_urls.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec { - self.client.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec { + self.client.nym_api_urls.clone() } pub fn get_gateway_id(&self) -> String { @@ -425,7 +425,8 @@ pub struct Client { validator_urls: Vec, /// Addresses to APIs running on validator from which the client gets the view of the network. - validator_api_urls: Vec, + #[serde(alias = "validator_api_urls")] + nym_api_urls: Vec, /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -476,7 +477,7 @@ impl Default for Client { id: "".to_string(), disabled_credentials_mode: true, validator_urls: vec![], - validator_api_urls: vec![], + nym_api_urls: vec![], private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_encryption_key_file: Default::default(), diff --git a/clients/client-core/src/error.rs b/clients/client-core/src/error.rs index 3b179567d1..44973c5f30 100644 --- a/clients/client-core/src/error.rs +++ b/clients/client-core/src/error.rs @@ -30,8 +30,8 @@ pub enum ClientCoreError { #[error("Failed to setup gateway")] FailedToSetupGateway, - #[error("List of validator apis is empty")] - ListOfValidatorApisIsEmpty, + #[error("List of nym apis is empty")] + ListOfNymApisIsEmpty, #[error("Could not load existing gateway configuration: {0}")] CouldNotLoadExistingGatewayConfiguration(std::io::Error), diff --git a/clients/client-core/src/init/helpers.rs b/clients/client-core/src/init/helpers.rs index 7947d614a1..31d76e86da 100644 --- a/clients/client-core/src/init/helpers.rs +++ b/clients/client-core/src/init/helpers.rs @@ -24,12 +24,12 @@ pub(super) async fn query_gateway_details( where B: ReplyStorageBackend, { - 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::ApiClient::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() diff --git a/clients/client-core/src/init/mod.rs b/clients/client-core/src/init/mod.rs index cdf210a27e..fa6c62c097 100644 --- a/clients/client-core/src/init/mod.rs +++ b/clients/client-core/src/init/mod.rs @@ -96,7 +96,7 @@ where { println!("Configuring gateway"); let gateway = - query_gateway_details(config.get_validator_api_endpoints(), user_chosen_gateway_id).await?; + 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 @@ -120,11 +120,8 @@ where T: NymConfig, { println!("Using gateway provided by user, keeping existing keys"); - let gateway = query_gateway_details( - config.get_validator_api_endpoints(), - Some(user_chosen_gateway_id), - ) - .await?; + 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()) } @@ -209,8 +206,8 @@ pub fn output_to_json(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 {}: {}", output_file, err), + Err(err) => eprintln!("Could not save {}: {err}", output_file), }, - Err(err) => eprintln!("Could not save {}: {}", output_file, err), + Err(err) => eprintln!("Could not save {}: {err}", output_file), } } diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index 31ca091282..54d4032799 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -31,8 +31,8 @@ validator_urls = [ ] # 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}} ] diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index cabe049c45..ca367018e8 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -1,6 +1,8 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; + use crate::client::config::Config; use crate::error::ClientError; use crate::websocket; @@ -11,6 +13,7 @@ use client_core::client::base_client::{ 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; @@ -18,7 +21,7 @@ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use nymsphinx::receiver::ReconstructedMessage; -use task::{wait_for_signal, ShutdownNotifier}; +use task::TaskManager; pub(crate) mod config; @@ -56,7 +59,7 @@ impl SocketClient { .expect("No nymd 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 @@ -84,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> { + pub async fn run_socket_forever(self) -> Result<(), Box> { let mut shutdown = self.start_socket().await?; - wait_for_signal().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!("Stopping nym-client"); - Ok(()) + res } - pub async fn start_socket(self) -> Result { + pub async fn start_socket(self) -> Result { if !self.config.get_socket_type().is_websocket() { return Err(ClientError::InvalidSocketMode); } @@ -150,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 { @@ -192,7 +201,7 @@ impl SocketClient { Ok(DirectClient { client_input, reconstructed_receiver, - _shutdown_notifier: started_client.shutdown_notifier, + _shutdown_notifier: started_client.task_manager, }) } } @@ -202,7 +211,7 @@ 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 { diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 9dc6a2ac33..cf40470556 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -1,8 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; + use crate::client::config::{Config, SocketType}; -use crate::error::ClientError; use clap::CommandFactory; use clap::{Parser, Subcommand}; use completions::{fig_generate, ArgShell}; @@ -86,7 +87,7 @@ pub(crate) struct OverrideConfig { enabled_credentials_mode: bool, } -pub(crate) async fn execute(args: &Cli) -> Result<(), ClientError> { +pub(crate) async fn execute(args: &Cli) -> Result<(), Box> { let bin_name = "nym-native-client"; match &args.command { @@ -114,13 +115,13 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi if let Some(raw_validators) = args.api_validators { config .get_base_mut() - .set_custom_validator_apis(config::parse_validators(&raw_validators)); + .set_custom_nym_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)); + .set_custom_nym_apis(config::parse_validators(&raw_validators)); } if args.disable_socket { diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 5eb6939944..b5d606a112 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -1,6 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::error::Error; + use crate::{ client::{config::Config, SocketClient}, commands::{override_config, OverrideConfig}, @@ -89,14 +91,14 @@ fn version_check(cfg: &Config) -> bool { } } -pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> { +pub(crate) async fn execute(args: &Run) -> Result<(), Box> { 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()))); } }; @@ -109,7 +111,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), ClientError> { 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 diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index c84073200c..6aaa732624 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -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) }); diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index 00529494e7..972eef1925 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -1,8 +1,9 @@ // Copyright 2021 - Nym Technologies SA // 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,7 +13,7 @@ pub mod error; pub mod websocket; #[tokio::main] -async fn main() -> Result<(), ClientError> { +async fn main() -> Result<(), Box> { setup_logging(); println!("{}", banner()); diff --git a/clients/native/src/websocket/handler.rs b/clients/native/src/websocket/handler.rs index 7d8d16e97f..c929a907cc 100644 --- a/clients/native/src/websocket/handler.rs +++ b/clients/native/src/websocket/handler.rs @@ -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::{ @@ -16,7 +17,9 @@ use log::*; use nymsphinx::addressing::clients::Recipient; 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>, - 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,10 +76,22 @@ 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>, + received_response_type: ReceivedResponseType, + lane_queue_lengths: LaneQueueLengths, + reply_controller_sender: ReplyControllerSender, +} + impl Drop for Handler { fn drop(&mut self) { if self @@ -73,22 +105,46 @@ impl Drop for Handler { } 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 { + 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( @@ -115,27 +171,11 @@ impl Handler { .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::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => 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. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } async fn handle_send_anonymous( @@ -162,27 +202,11 @@ impl Handler { .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::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => 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. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } async fn handle_reply( @@ -205,27 +229,11 @@ impl Handler { .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::ReplySurbRequest - | TransmissionLane::Retransmission - | TransmissionLane::AdditionalReplySurbs => 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. - if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() { - let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0); - return Some(ServerResponse::LaneQueueLength { - lane: connection_id, - queue_length, - }); - } - - log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length"); - None + self.get_lane_queue_length(connection_id).await } fn handle_self_address(&self) -> ServerResponse { @@ -239,20 +247,8 @@ impl Handler { None } - fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option { - 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 { - lane: connection_id, - queue_length, - }) + async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option { + self.get_lane_queue_length(connection_id).await } async fn handle_request(&mut self, request: ClientRequest) -> Option { @@ -281,7 +277,7 @@ impl Handler { 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, } } @@ -362,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() => { @@ -373,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; } }; @@ -397,21 +397,33 @@ impl Handler { 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(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); + 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; } }; @@ -426,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; } } diff --git a/clients/native/src/websocket/listener.rs b/clients/native/src/websocket/listener.rs index c7825fb345..459eb1bc17 100644 --- a/clients/native/src/websocket/listener.rs +++ b/clients/native/src/websocket/listener.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // 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,10 +45,23 @@ 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)) => { @@ -63,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 }) } } diff --git a/clients/native/src/websocket/mod.rs b/clients/native/src/websocket/mod.rs index bd17b6ee08..38dadbd005 100644 --- a/clients/native/src/websocket/mod.rs +++ b/clients/native/src/websocket/mod.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // 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; diff --git a/clients/native/websocket-requests/src/requests.rs b/clients/native/websocket-requests/src/requests.rs index f50364a3cf..637db0daba 100644 --- a/clients/native/websocket-requests/src/requests.rs +++ b/clients/native/websocket-requests/src/requests.rs @@ -132,7 +132,7 @@ impl ClientRequest { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedRequest, - format!("malformed recipient: {:?}", err), + format!("malformed recipient: {err}"), )) } }; @@ -210,7 +210,7 @@ impl ClientRequest { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedRequest, - format!("malformed recipient: {:?}", err), + format!("malformed recipient: {err}"), )) } }; diff --git a/clients/native/websocket-requests/src/responses.rs b/clients/native/websocket-requests/src/responses.rs index cbefe54242..8b4e60963a 100644 --- a/clients/native/websocket-requests/src/responses.rs +++ b/clients/native/websocket-requests/src/responses.rs @@ -173,7 +173,7 @@ impl ServerResponse { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedResponse, - format!("malformed Recipient: {:?}", err), + format!("malformed Recipient: {err}"), )) } }; @@ -249,7 +249,7 @@ impl ServerResponse { Err(err) => { return Err(error::Error::new( ErrorKind::MalformedResponse, - format!("malformed error message: {:?}", err), + format!("malformed error message: {err}"), )) } }; diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 527da38274..90029fb5fd 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -31,8 +31,8 @@ validator_urls = [ ] # 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}} ] diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 2b4bff7852..c7710056f8 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -19,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; @@ -67,7 +67,7 @@ impl NymClient { .expect("No nymd 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 @@ -96,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 = 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( @@ -164,7 +166,7 @@ impl NymClient { let mut shutdown = self.start().await?; // Listen to status messages from task, that we forward back to the caller - shutdown.start_status_listener(sender); + shutdown.start_status_listener(sender).await; let res = tokio::select! { biased; @@ -200,7 +202,7 @@ impl NymClient { res } - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let base_builder = BaseClientBuilder::new_from_base_config( self.config.get_base(), self.key_manager, @@ -222,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) } } diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 5f5bfda1de..4d4345f1ce 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -116,11 +116,11 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi if let Some(raw_validators) = args.api_validators { config .get_base_mut() - .set_custom_validator_apis(parse_validators(&raw_validators)); + .set_custom_nym_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)); + .set_custom_nym_apis(parse_validators(&raw_validators)); } if args.use_anonymous_sender_tag { diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 48d4651d15..807186b7ed 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -43,9 +43,9 @@ pub(crate) struct Run { #[clap(long)] nymd_validators: Option, - /// Comma separated list of rest endpoints of the API validators + /// Comma separated list of rest endpoints of the Nym APIs #[clap(long)] - api_validators: Option, + nym_apis: Option, /// Port for the socket to listen on #[clap(short, long)] @@ -71,7 +71,7 @@ impl From for OverrideConfig { fn from(run_config: Run) -> Self { OverrideConfig { nymd_validators: run_config.nymd_validators, - api_validators: run_config.api_validators, + api_validators: run_config.nym_apis, port: run_config.port, use_anonymous_sender_tag: run_config.use_anonymous_sender_tag, fastmode: run_config.fastmode, @@ -110,7 +110,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box 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(), ))); diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index ff41f3ce6e..db77167f34 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -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) }); diff --git a/clients/socks5/src/error.rs b/clients/socks5/src/error.rs index 23cd715cf5..6318d0ec0a 100644 --- a/clients/socks5/src/error.rs +++ b/clients/socks5/src/error.rs @@ -1,6 +1,7 @@ use crate::socks::types::SocksProxyError; use client_core::client::replies::reply_storage::fs_backend; use client_core::error::ClientCoreError; +use socks5_requests::ConnectionId; #[derive(thiserror::Error, Debug)] pub enum Socks5ClientError { @@ -21,4 +22,10 @@ pub enum Socks5ClientError { #[error("Fail to bind address")] FailToBindAddress, + + #[error("Network requester: connection id {connection_id}: {error}")] + NetworkRequesterError { + connection_id: ConnectionId, + error: String, + }, } diff --git a/clients/socks5/src/socks/client.rs b/clients/socks5/src/socks/client.rs index 3b35a4caf9..ff7caa1b6b 100644 --- a/clients/socks5/src/socks/client.rs +++ b/clients/socks5/src/socks/client.rs @@ -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}; @@ -164,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 { @@ -190,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(); @@ -220,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(()); diff --git a/clients/socks5/src/socks/mixnet_responses.rs b/clients/socks5/src/socks/mixnet_responses.rs index 7613a32019..85d0a7da86 100644 --- a/clients/socks5/src/socks/mixnet_responses.rs +++ b/clients/socks5/src/socks/mixnet_responses.rs @@ -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,7 +54,10 @@ 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.sender_tag.is_some() { warn!("this message was sent anonymously - it couldn't have come from the service provider"); @@ -60,12 +65,12 @@ impl MixnetResponseListener { 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; } diff --git a/clients/socks5/src/socks/server.rs b/clients/socks5/src/socks/server.rs index 0bba59ee0b..0b193dc9d4 100644 --- a/clients/socks5/src/socks/server.rs +++ b/clients/socks5/src/socks/server.rs @@ -13,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. @@ -24,7 +24,7 @@ pub struct SphinxSocksServer { self_address: Recipient, client_config: client::Config, lane_queue_lengths: LaneQueueLengths, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl SphinxSocksServer { @@ -36,7 +36,7 @@ impl SphinxSocksServer { self_address: Recipient, lane_queue_lengths: LaneQueueLengths, client_config: client::Config, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { // hardcode ip as we (presumably) ONLY want to listen locally. If we change it, we can // just modify the config @@ -103,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"); }; diff --git a/clients/socks5/src/socks/types.rs b/clients/socks5/src/socks/types.rs index 669a6b1093..e561d0025d 100644 --- a/clients/socks5/src/socks/types.rs +++ b/clients/socks5/src/socks/types.rs @@ -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) } diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index 642fc2b077..cfbc862a9e 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -86,7 +86,7 @@ export default class ValidatorClient implements INymClient { static async connect( mnemonic: string, nymdUrl: string, - validatorApiUrl: string, + nymApiUrl: string, prefix: string, mixnetContract: string, vestingContract: string, @@ -94,19 +94,19 @@ export default class ValidatorClient implements INymClient { ): Promise { const wallet = await ValidatorClient.buildWallet(mnemonic, prefix); - const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, validatorApiUrl, prefix, denom); + const signingClient = await SigningClient.connectWithNymSigner(wallet, nymdUrl, nymApiUrl, prefix, denom); return new ValidatorClient(signingClient, prefix, mixnetContract, vestingContract, denom); } static async connectForQuery( nymdUrl: string, - validatorApiUrl: string, + nymApiUrl: string, prefix: string, mixnetContract: string, vestingContract: string, denom: string, ): Promise { - const queryClient = await QueryClient.connectWithNym(nymdUrl, validatorApiUrl); + const queryClient = await QueryClient.connectWithNym(nymdUrl, nymApiUrl); return new ValidatorClient(queryClient, prefix, mixnetContract, vestingContract, denom); } diff --git a/clients/validator/src/validator-api-querier.ts b/clients/validator/src/nym-api-querier.ts similarity index 58% rename from clients/validator/src/validator-api-querier.ts rename to clients/validator/src/nym-api-querier.ts index 7c61f91bd7..3fa0471804 100644 --- a/clients/validator/src/validator-api-querier.ts +++ b/clients/validator/src/nym-api-querier.ts @@ -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; getCachedGateways(): Promise; @@ -22,16 +22,16 @@ export interface IValidatorApiQuery { getRewardedMixnodes(): Promise; } -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 { - 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 { - 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 { - 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 { - 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) { diff --git a/clients/validator/src/query-client.ts b/clients/validator/src/query-client.ts index 9e3937a89e..de477f7fd9 100644 --- a/clients/validator/src/query-client.ts +++ b/clients/validator/src/query-client.ts @@ -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` @@ -93,22 +93,22 @@ export interface INymdQuery { ): Promise; } -export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {} +export interface IQueryClient extends ICosmWasmQuery, INymdQuery, INymApiQuery { } export default class QueryClient extends CosmWasmClient implements IQueryClient { private nymdQuerier: NymdQuerier; - 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.nymApiQuerier = new NymApiQuerier(nymApiUrl); } - public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise { + public static async connectWithNym(nymdUrl: string, nymApiUrl: string): Promise { const tmClient = await Tendermint34Client.connect(nymdUrl); - return new QueryClient(tmClient, validatorApiUrl); + return new QueryClient(tmClient, nymApiUrl); } getContractVersion(mixnetContractAddress: string): Promise { @@ -194,18 +194,18 @@ export default class QueryClient extends CosmWasmClient implements IQueryClient } getCachedGateways(): Promise { - return this.validatorApiQuerier.getCachedGateways(); + return this.nymApiQuerier.getCachedGateways(); } getCachedMixnodes(): Promise { - return this.validatorApiQuerier.getCachedMixnodes(); + return this.nymApiQuerier.getCachedMixnodes(); } getActiveMixnodes(): Promise { - return this.validatorApiQuerier.getActiveMixnodes(); + return this.nymApiQuerier.getActiveMixnodes(); } getRewardedMixnodes(): Promise { - return this.validatorApiQuerier.getRewardedMixnodes(); + return this.nymApiQuerier.getRewardedMixnodes(); } } diff --git a/clients/validator/src/signing-client.ts b/clients/validator/src/signing-client.ts index ef8913243d..382077f0a9 100644 --- a/clients/validator/src/signing-client.ts +++ b/clients/validator/src/signing-client.ts @@ -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 { @@ -199,13 +199,13 @@ export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSign export default class SigningClient extends SigningCosmWasmClient implements ISigningClient { private nymdQuerier: NymdQuerier; - private validatorApiQuerier: ValidatorApiQuerier; + private nymApiQuerier: NymApiQuerier; clientAddress: string; private constructor( clientAddress: string, - validatorApiUrl: string, + nymApiUrl: string, tmClient: Tendermint34Client, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions, @@ -213,13 +213,13 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig super(tmClient, wallet, signerOptions); this.clientAddress = clientAddress; this.nymdQuerier = new NymdQuerier(this); - this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl); + this.nymApiQuerier = new NymApiQuerier(nymApiUrl); } public static async connectWithNymSigner( wallet: DirectSecp256k1HdWallet, nymdUrl: string, - validatorApiUrl: string, + nymApiUrl: string, prefix: string, denom: string, ): Promise { @@ -229,7 +229,7 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig gasPrice: nymGasPrice(denom), }; const tmClient = await Tendermint34Client.connect(nymdUrl); - return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions); + return new SigningClient(address, nymApiUrl, tmClient, wallet, signerOptions); } // query related: @@ -317,19 +317,19 @@ export default class SigningClient extends SigningCosmWasmClient implements ISig } getCachedGateways(): Promise { - return this.validatorApiQuerier.getCachedGateways(); + return this.nymApiQuerier.getCachedGateways(); } getCachedMixnodes(): Promise { - return this.validatorApiQuerier.getCachedMixnodes(); + return this.nymApiQuerier.getCachedMixnodes(); } getActiveMixnodes(): Promise { - return this.validatorApiQuerier.getActiveMixnodes(); + return this.nymApiQuerier.getActiveMixnodes(); } getRewardedMixnodes(): Promise { - return this.validatorApiQuerier.getRewardedMixnodes(); + return this.nymApiQuerier.getRewardedMixnodes(); } // signing related: diff --git a/clients/validator/tests/query/balance.test.ts b/clients/validator/tests/query/balance.test.ts index 254e54a745..97be47c2ce 100644 --- a/clients/validator/tests/query/balance.test.ts +++ b/clients/validator/tests/query/balance.test.ts @@ -1,4 +1,4 @@ -import ValidatorClient from '../../dist'; +import ValidatorClient from '../../validator/index'; import expect from 'expect'; describe('Query: balances', () => { diff --git a/clients/webassembly/src/client/config.rs b/clients/webassembly/src/client/config.rs index 4740a02b2a..4efefeba21 100644 --- a/clients/webassembly/src/client/config.rs +++ b/clients/webassembly/src/client/config.rs @@ -17,7 +17,7 @@ pub struct Config { /// ID specifies the human readable ID of this particular client. pub(crate) id: String, - pub(crate) validator_api_url: Url, + pub(crate) nym_api_url: Url, pub(crate) disabled_credentials_mode: bool, @@ -38,7 +38,7 @@ impl Config { ) -> Self { Config { id, - validator_api_url: validator_server + nym_api_url: validator_server .parse() .expect("provided url was malformed"), disabled_credentials_mode: true, diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index 9c3aefe8c6..bd9ceab576 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -14,7 +14,7 @@ use nymsphinx::addressing::clients::Recipient; use nymsphinx::anonymous_replies::requests::AnonymousSenderTag; use rand::rngs::OsRng; use std::sync::Arc; -use task::ShutdownNotifier; +use task::TaskManager; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_utils::{console_error, console_log}; @@ -30,7 +30,7 @@ pub struct NymClient { // even though we don't use graceful shutdowns, other components rely on existence of this struct // and if it's dropped, everything will start going offline - _shutdown: ShutdownNotifier, + _task_manager: TaskManager, } #[wasm_bindgen] @@ -99,7 +99,7 @@ impl NymClientBuilder { self.bandwidth_controller, self.reply_surb_storage_backend, self.disabled_credentials, - vec![self.config.validator_api_url.clone()], + vec![self.config.nym_api_url.clone()], ); let self_address = base_builder.as_mix_recipient().to_string(); @@ -121,7 +121,7 @@ impl NymClientBuilder { Ok(JsValue::from(NymClient { self_address, client_input: Arc::new(client_input), - _shutdown: started_client.shutdown_notifier, + _task_manager: started_client.task_manager, })) }) } diff --git a/common/client-connections/src/lib.rs b/common/client-connections/src/lib.rs index f86270da63..b10963da79 100644 --- a/common/client-connections/src/lib.rs +++ b/common/client-connections/src/lib.rs @@ -1,9 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; - use futures::channel::mpsc; +use std::collections::HashMap; pub type ConnectionId = u64; diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index e1c2654b80..23cae7e4a6 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -22,7 +22,7 @@ use rand::rngs::OsRng; use std::convert::TryFrom; use std::sync::Arc; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use tungstenite::protocol::Message; #[cfg(feature = "coconut")] @@ -67,7 +67,7 @@ pub struct GatewayClient { reconnection_backoff: Duration, /// Listen to shutdown messages. - shutdown: ShutdownListener, + shutdown: TaskClient, } impl GatewayClient { @@ -83,7 +83,7 @@ impl GatewayClient { ack_sender: AcknowledgementSender, response_timeout_duration: Duration, bandwidth_controller: Option>, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { GatewayClient { authenticated: false, @@ -135,7 +135,7 @@ impl GatewayClient { // perfectly fine here, because it's not meant to be used let (ack_tx, _) = mpsc::unbounded(); let (mix_tx, _) = mpsc::unbounded(); - let shutdown = ShutdownListener::dummy(); + let shutdown = TaskClient::dummy(); let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown.clone()); GatewayClient { @@ -305,11 +305,11 @@ impl GatewayClient { if let Some(shared_keys) = &self.shared_key { if let Some(plaintext) = try_decrypt_binary_message(bin_msg, shared_keys) { if let Err(err) = self.packet_router.route_received(vec![plaintext]) { - log::warn!("Route received failed: {:?}", err); + log::warn!("Route received failed: {err}"); } } } else if let Err(err) = self.packet_router.route_received(vec![bin_msg]) { - log::warn!("Route received failed: {:?}", err); + log::warn!("Route received failed: {err}"); } } Message::Text(txt_msg) => { diff --git a/common/client-libs/gateway-client/src/packet_router.rs b/common/client-libs/gateway-client/src/packet_router.rs index fd63077ee6..daeac0e548 100644 --- a/common/client-libs/gateway-client/src/packet_router.rs +++ b/common/client-libs/gateway-client/src/packet_router.rs @@ -9,7 +9,7 @@ use futures::channel::mpsc; use log::*; use nymsphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN; use nymsphinx::params::packet_sizes::PacketSize; -use task::ShutdownListener; +use task::TaskClient; pub type MixnetMessageSender = mpsc::UnboundedSender>>; pub type MixnetMessageReceiver = mpsc::UnboundedReceiver>>; @@ -21,14 +21,14 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver>>; pub struct PacketRouter { ack_sender: AcknowledgementSender, mixnet_message_sender: MixnetMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketRouter { pub fn new( ack_sender: AcknowledgementSender, mixnet_message_sender: MixnetMessageSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { PacketRouter { ack_sender, @@ -89,14 +89,14 @@ impl PacketRouter { } // This should never happen during ordinary operation the way it's currently used. // Abort to be on the safe side - panic!("Failed to send mixnet message: {:?}", err); + panic!("Failed to send mixnet message: {err}"); } } if !received_acks.is_empty() { trace!("routing acks"); - if let Err(e) = self.ack_sender.unbounded_send(received_acks) { - error!("failed to send ack: {:?}", e); + if let Err(err) = self.ack_sender.unbounded_send(received_acks) { + error!("failed to send ack: {err}"); }; } Ok(()) diff --git a/common/client-libs/gateway-client/src/socket_state.rs b/common/client-libs/gateway-client/src/socket_state.rs index aa96b9a4fa..2bedf1e8ed 100644 --- a/common/client-libs/gateway-client/src/socket_state.rs +++ b/common/client-libs/gateway-client/src/socket_state.rs @@ -10,7 +10,7 @@ use futures::{SinkExt, StreamExt}; use gateway_requests::registration::handshake::SharedKeys; use log::*; use std::sync::Arc; -use task::ShutdownListener; +use task::TaskClient; use tungstenite::Message; #[cfg(not(target_arch = "wasm32"))] @@ -84,7 +84,7 @@ impl PartiallyDelegated { conn: WsConn, packet_router: PacketRouter, shared_key: Arc, - mut shutdown: ShutdownListener, + mut shutdown: TaskClient, ) -> Self { // when called for, it NEEDS TO yield back the stream so that we could merge it and // read control request responses. @@ -115,7 +115,7 @@ impl PartiallyDelegated { }; if let Err(err) = Self::route_socket_messages(ws_msgs, &mut packet_router, shared_key.as_ref()) { - log::warn!("Route socket messages failed: {:?}", err); + log::warn!("Route socket messages failed: {err}"); } } }; diff --git a/common/client-libs/mixnet-client/src/client.rs b/common/client-libs/mixnet-client/src/client.rs index 600268b69b..f3a04f1099 100644 --- a/common/client-libs/mixnet-client/src/client.rs +++ b/common/client-libs/mixnet-client/src/client.rs @@ -122,7 +122,7 @@ impl Client { // We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care // about neither receiver nor the connection, it doesn't matter which one gets consumed if let Err(err) = receiver.map(Ok).forward(conn).await { - warn!("Failed to forward packets to {} - {:?}", address, err); + warn!("Failed to forward packets to {} - {err}", address); } debug!( diff --git a/common/client-libs/mixnet-client/src/forwarder.rs b/common/client-libs/mixnet-client/src/forwarder.rs index d92f6ad790..1aa68be8da 100644 --- a/common/client-libs/mixnet-client/src/forwarder.rs +++ b/common/client-libs/mixnet-client/src/forwarder.rs @@ -59,7 +59,7 @@ impl PacketForwarder { self.mixnet_client .send_without_response(next_hop, sphinx_packet, packet_mode) { - debug!("failed to forward the packet - {}", err) + debug!("failed to forward the packet - {err}") } } } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index ec842828e7..18096c3db0 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -29,7 +29,7 @@ futures = "0.3" coconut-interface = { path = "../../coconut-interface" } network-defaults = { path = "../../network-defaults" } -validator-api-requests = { path = "../../../validator-api/validator-api-requests", features = ["coconut"] } +nym-api-requests = { path = "../../../nym-api/nym-api-requests", features = ["coconut"] } # required for nymd-client # at some point it might be possible to make it wasm-compatible diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index b5746ea06c..396efb4c3c 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -1,16 +1,16 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::{validator_api, ValidatorClientError}; +use crate::{nym_api, ValidatorClientError}; use coconut_dkg_common::types::NodeIndex; use coconut_interface::VerificationKey; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::MixId; use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; -use validator_api_requests::coconut::{ +use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; -use validator_api_requests::models::{ +use nym_api_requests::models::{ GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, }; @@ -37,10 +37,10 @@ use mixnet_contract_common::{ #[cfg(feature = "nymd-client")] use network_defaults::NymNetworkDetails; #[cfg(feature = "nymd-client")] +use nym_api_requests::models::MixNodeBondAnnotated; +#[cfg(feature = "nymd-client")] use std::str::FromStr; use url::Url; -#[cfg(feature = "nymd-client")] -use validator_api_requests::models::MixNodeBondAnnotated; #[cfg(feature = "nymd-client")] #[must_use] @@ -142,7 +142,7 @@ pub struct Client { proposals_page_limit: Option, // ideally they would have been read-only, but unfortunately rust doesn't have such features - pub validator_api: validator_api::Client, + pub nym_api: nym_api::Client, pub nymd: NymdClient, } @@ -152,7 +152,7 @@ impl Client { config: Config, mnemonic: bip39::Mnemonic, ) -> Result, ValidatorClientError> { - let validator_api_client = validator_api::Client::new(config.api_url.clone()); + let nym_api_client = nym_api::Client::new(config.api_url.clone()); let nymd_client = NymdClient::connect_with_mnemonic( config.nymd_config.clone(), config.nymd_url.as_str(), @@ -169,7 +169,7 @@ impl Client { dealers_page_limit: config.dealers_page_limit, verification_key_page_limit: config.verification_key_page_limit, proposals_page_limit: config.proposals_page_limit, - validator_api: validator_api_client, + nym_api: nym_api_client, nymd: nymd_client, }) } @@ -192,7 +192,7 @@ impl Client { #[cfg(feature = "nymd-client")] impl Client { pub fn new_query(config: Config) -> Result, ValidatorClientError> { - let validator_api_client = validator_api::Client::new(config.api_url.clone()); + let nym_api_client = nym_api::Client::new(config.api_url.clone()); let nymd_client = NymdClient::connect(config.nymd_config.clone(), config.nymd_url.as_str())?; @@ -205,7 +205,7 @@ impl Client { dealers_page_limit: config.dealers_page_limit, verification_key_page_limit: config.verification_key_page_limit, proposals_page_limit: config.proposals_page_limit, - validator_api: validator_api_client, + nym_api: nym_api_client, nymd: nymd_client, }) } @@ -731,53 +731,53 @@ impl Client { // validator-api wrappers #[cfg(feature = "nymd-client")] impl Client { - pub fn change_validator_api(&mut self, new_endpoint: Url) { - self.validator_api.change_url(new_endpoint) + pub fn change_nym_api(&mut self, new_endpoint: Url) { + self.nym_api.change_url(new_endpoint) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes().await?) + Ok(self.nym_api.get_mixnodes().await?) } pub async fn get_cached_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes_detailed().await?) + Ok(self.nym_api.get_mixnodes_detailed().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes().await?) + Ok(self.nym_api.get_rewarded_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?) + Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?) } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes().await?) + Ok(self.nym_api.get_active_mixnodes().await?) } pub async fn get_cached_active_mixnodes_detailed( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes_detailed().await?) + Ok(self.nym_api.get_active_mixnodes_detailed().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_gateways().await?) + Ok(self.nym_api.get_gateways().await?) } pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, ) -> Result { - Ok(self.validator_api.blind_sign(request_body).await?) + Ok(self.nym_api.blind_sign(request_body).await?) } } @@ -827,42 +827,40 @@ impl CoconutApiClient { #[derive(Clone)] pub struct ApiClient { - pub validator_api: validator_api::Client, + pub nym_api_client: nym_api::Client, // TODO: perhaps if we really need it at some (currently I don't see any reasons for it) // we could re-implement the communication with the REST API on port 1317 } impl ApiClient { pub fn new(api_url: Url) -> Self { - let validator_api_client = validator_api::Client::new(api_url); + let nym_api_client = nym_api::Client::new(api_url); - ApiClient { - validator_api: validator_api_client, - } + ApiClient { nym_api_client } } - pub fn change_validator_api(&mut self, new_endpoint: Url) { - self.validator_api.change_url(new_endpoint); + pub fn change_nym_api(&mut self, new_endpoint: Url) { + self.nym_api_client.change_url(new_endpoint); } pub async fn get_cached_active_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_active_mixnodes().await?) + Ok(self.nym_api_client.get_active_mixnodes().await?) } pub async fn get_cached_rewarded_mixnodes( &self, ) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_rewarded_mixnodes().await?) + Ok(self.nym_api_client.get_rewarded_mixnodes().await?) } pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_mixnodes().await?) + Ok(self.nym_api_client.get_mixnodes().await?) } pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { - Ok(self.validator_api.get_gateways().await?) + Ok(self.nym_api_client.get_gateways().await?) } pub async fn get_gateway_core_status_count( @@ -871,7 +869,7 @@ impl ApiClient { since: Option, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_gateway_core_status_count(identity, since) .await?) } @@ -882,7 +880,7 @@ impl ApiClient { since: Option, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_core_status_count(mix_id, since) .await?) } @@ -891,7 +889,7 @@ impl ApiClient { &self, mix_id: MixId, ) -> Result { - Ok(self.validator_api.get_mixnode_status(mix_id).await?) + Ok(self.nym_api_client.get_mixnode_status(mix_id).await?) } pub async fn get_mixnode_reward_estimation( @@ -899,7 +897,7 @@ impl ApiClient { mix_id: MixId, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_reward_estimation(mix_id) .await?) } @@ -909,7 +907,7 @@ impl ApiClient { mix_id: MixId, ) -> Result { Ok(self - .validator_api + .nym_api_client .get_mixnode_stake_saturation(mix_id) .await?) } @@ -918,7 +916,7 @@ impl ApiClient { &self, request_body: &BlindSignRequestBody, ) -> Result { - Ok(self.validator_api.blind_sign(request_body).await?) + Ok(self.nym_api_client.blind_sign(request_body).await?) } pub async fn partial_bandwidth_credential( @@ -926,7 +924,7 @@ impl ApiClient { request_body: &str, ) -> Result { Ok(self - .validator_api + .nym_api_client .partial_bandwidth_credential(request_body) .await?) } @@ -936,7 +934,7 @@ impl ApiClient { request_body: &VerifyCredentialBody, ) -> Result { Ok(self - .validator_api + .nym_api_client .verify_bandwidth_credential(request_body) .await?) } diff --git a/common/client-libs/validator-client/src/error.rs b/common/client-libs/validator-client/src/error.rs index e832b12618..70ac98b30d 100644 --- a/common/client-libs/validator-client/src/error.rs +++ b/common/client-libs/validator-client/src/error.rs @@ -1,15 +1,15 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::validator_api; +use crate::nym_api; use thiserror::Error; #[derive(Error, Debug)] pub enum ValidatorClientError { #[error("There was an issue with the validator api request - {source}")] - ValidatorAPIError { + NymAPIError { #[from] - source: validator_api::error::ValidatorAPIError, + source: nym_api::error::NymAPIError, }, #[error("One of the provided URLs was malformed - {0}")] diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 2a5e010338..5cf81f09d7 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -5,14 +5,14 @@ pub mod client; #[cfg(feature = "nymd-client")] pub mod connection_tester; mod error; +pub mod nym_api; #[cfg(feature = "nymd-client")] pub mod nymd; -pub mod validator_api; #[cfg(feature = "nymd-client")] pub use crate::client::{ApiClient, CoconutApiClient}; pub use crate::error::ValidatorClientError; -pub use validator_api_requests::*; +pub use nym_api_requests::*; #[cfg(feature = "nymd-client")] pub use client::{Client, Config}; diff --git a/common/client-libs/validator-client/src/validator_api/error.rs b/common/client-libs/validator-client/src/nym_api/error.rs similarity index 60% rename from common/client-libs/validator-client/src/validator_api/error.rs rename to common/client-libs/validator-client/src/nym_api/error.rs index 9d7cd1d50d..1c3f7b11a4 100644 --- a/common/client-libs/validator-client/src/validator_api/error.rs +++ b/common/client-libs/validator-client/src/nym_api/error.rs @@ -1,8 +1,8 @@ +use nym_api_requests::models::RequestError; use thiserror::Error; -use validator_api_requests::models::RequestError; #[derive(Error, Debug)] -pub enum ValidatorAPIError { +pub enum NymAPIError { #[error("There was an issue with the REST request - {source}")] ReqwestClientError { #[from] @@ -12,6 +12,6 @@ pub enum ValidatorAPIError { #[error("Request failed with error message - {0}")] GenericRequestFailure(String), - #[error("The validator API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())] + #[error("The nym API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())] ApiRequestFailure { status: u16, error: RequestError }, } diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs similarity index 82% rename from common/client-libs/validator-client/src/validator_api/mod.rs rename to common/client-libs/validator-client/src/nym_api/mod.rs index c0775b9971..e1d65959c6 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -1,23 +1,23 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::validator_api::error::ValidatorAPIError; -use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; +use crate::nym_api::error::NymAPIError; +use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; -use reqwest::Response; -use serde::{Deserialize, Serialize}; -use url::Url; -use validator_api_requests::coconut::{ +use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; -use validator_api_requests::models::{ +use nym_api_requests::models::{ ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; +use reqwest::Response; +use serde::{Deserialize, Serialize}; +use url::Url; pub mod error; pub mod routes; @@ -54,7 +54,7 @@ impl Client { &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where K: AsRef, V: AsRef, @@ -63,11 +63,11 @@ impl Client { Ok(self.reqwest_client.get(url).send().await?) } - async fn query_validator_api( + async fn query_nym_api( &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where for<'a> T: Deserialize<'a>, K: AsRef, @@ -77,16 +77,16 @@ impl Client { if res.status().is_success() { Ok(res.json().await?) } else { - Err(ValidatorAPIError::GenericRequestFailure(res.text().await?)) + Err(NymAPIError::GenericRequestFailure(res.text().await?)) } } // This works for endpoints returning Result, ErrorResponse> - async fn query_validator_api_fallible( + async fn query_nym_api_fallible( &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where for<'a> T: Deserialize<'a>, K: AsRef, @@ -98,19 +98,19 @@ impl Client { Ok(res.json().await?) } else { let request_error: RequestError = res.json().await?; - Err(ValidatorAPIError::ApiRequestFailure { + Err(NymAPIError::ApiRequestFailure { status: status.as_u16(), error: request_error, }) } } - async fn post_validator_api( + async fn post_nym_api( &self, path: PathSegments<'_>, params: Params<'_, K, V>, json_body: &B, - ) -> Result + ) -> Result where B: Serialize + ?Sized, for<'a> T: Deserialize<'a>, @@ -122,21 +122,17 @@ impl Client { if response.status().is_success() { Ok(response.json().await?) } else { - Err(ValidatorAPIError::GenericRequestFailure( - response.text().await?, - )) + Err(NymAPIError::GenericRequestFailure(response.text().await?)) } } - pub async fn get_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS) + pub async fn get_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS) .await } - pub async fn get_mixnodes_detailed( - &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_mixnodes_detailed(&self) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -148,13 +144,13 @@ impl Client { .await } - pub async fn get_gateways(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) + pub async fn get_gateways(&self) -> Result, NymAPIError> { + self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await } - pub async fn get_active_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_active_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api( &[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE], NO_PARAMS, ) @@ -163,8 +159,8 @@ impl Client { pub async fn get_active_mixnodes_detailed( &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + ) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -177,8 +173,8 @@ impl Client { .await } - pub async fn get_rewarded_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api( + pub async fn get_rewarded_mixnodes(&self) -> Result, NymAPIError> { + self.query_nym_api( &[routes::API_VERSION, routes::MIXNODES, routes::REWARDED], NO_PARAMS, ) @@ -188,8 +184,8 @@ impl Client { pub async fn get_mixnode_report( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -205,8 +201,8 @@ impl Client { pub async fn get_gateway_report( &self, identity: IdentityKeyRef<'_>, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -222,8 +218,8 @@ impl Client { pub async fn get_mixnode_history( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -239,8 +235,8 @@ impl Client { pub async fn get_gateway_history( &self, identity: IdentityKeyRef<'_>, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -255,8 +251,8 @@ impl Client { pub async fn get_rewarded_mixnodes_detailed( &self, - ) -> Result, ValidatorAPIError> { - self.query_validator_api( + ) -> Result, NymAPIError> { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS, @@ -273,9 +269,9 @@ impl Client { &self, identity: IdentityKeyRef<'_>, since: Option, - ) -> Result { + ) -> Result { if let Some(since) = since { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -287,7 +283,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -304,9 +300,9 @@ impl Client { &self, mix_id: MixId, since: Option, - ) -> Result { + ) -> Result { if let Some(since) = since { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -318,7 +314,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -335,8 +331,8 @@ impl Client { pub async fn get_mixnode_status( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api( + ) -> Result { + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -352,8 +348,8 @@ impl Client { pub async fn get_mixnode_reward_estimation( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -370,8 +366,8 @@ impl Client { &self, mix_id: MixId, request_body: &ComputeRewardEstParam, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -388,8 +384,8 @@ impl Client { pub async fn get_mixnode_stake_saturation( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -405,8 +401,8 @@ impl Client { pub async fn get_mixnode_inclusion_probability( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -422,8 +418,8 @@ impl Client { pub async fn get_mixnode_avg_uptime( &self, mix_id: MixId, - ) -> Result { - self.query_validator_api_fallible( + ) -> Result { + self.query_nym_api_fallible( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -439,8 +435,8 @@ impl Client { pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::COCONUT_ROUTES, @@ -456,8 +452,8 @@ impl Client { pub async fn partial_bandwidth_credential( &self, request_body: &str, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::COCONUT_ROUTES, @@ -473,8 +469,8 @@ impl Client { pub async fn verify_bandwidth_credential( &self, request_body: &VerifyCredentialBody, - ) -> Result { - self.post_validator_api( + ) -> Result { + self.post_nym_api( &[ routes::API_VERSION, routes::COCONUT_ROUTES, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs similarity index 92% rename from common/client-libs/validator-client/src/validator_api/routes.rs rename to common/client-libs/validator-client/src/nym_api/routes.rs index e98f2d295a..cd8a16047a 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -1,9 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use network_defaults::VALIDATOR_API_VERSION; +use network_defaults::NYM_API_VERSION; -pub const API_VERSION: &str = VALIDATOR_API_VERSION; +pub const API_VERSION: &str = NYM_API_VERSION; pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs index b67627d316..ce69556137 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_query_client.rs @@ -8,13 +8,13 @@ use coconut_dkg_common::dealer::{ DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse, }; use coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use coconut_dkg_common::verification_key::PagedVKSharesResponse; use cosmrs::AccountId; #[async_trait] pub trait DkgQueryClient { - async fn get_current_epoch_state(&self) -> Result; + async fn get_current_epoch(&self) -> Result; async fn get_current_epoch_threshold(&self) -> Result, NymdError>; async fn get_dealer_details( &self, @@ -49,7 +49,7 @@ impl DkgQueryClient for NymdClient where C: CosmWasmClient + Send + Sync, { - async fn get_current_epoch_state(&self) -> Result { + async fn get_current_epoch(&self) -> Result { let request = DkgQueryMsg::GetCurrentEpochState {}; self.client .query_contract_smart(self.coconut_dkg_contract_address(), &request) diff --git a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs index b0d1dfaf08..6fee83ac5d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/dkg_signing_client.rs @@ -12,6 +12,7 @@ use contracts_common::dealings::ContractSafeBytes; #[async_trait] pub trait DkgSigningClient { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, @@ -37,6 +38,21 @@ impl DkgSigningClient for NymdClient where C: SigningCosmWasmClient + Send + Sync, { + async fn advance_dkg_epoch_state(&self, fee: Option) -> Result { + let req = DkgExecuteMsg::AdvanceEpochState {}; + + self.client + .execute( + self.address(), + self.coconut_dkg_contract_address(), + &req, + fee.unwrap_or_default(), + "advancing DKG state", + vec![], + ) + .await + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/common/commands/src/context/errors.rs b/common/commands/src/context/errors.rs index 44a99f49ed..7be8f6d8ef 100644 --- a/common/commands/src/context/errors.rs +++ b/common/commands/src/context/errors.rs @@ -16,9 +16,9 @@ pub enum ContextError { #[error("failed to create client - {0}")] NymdError(String), - #[error("{0}")] + #[error(transparent)] NymdErrorPassthrough(#[from] validator_client::nymd::error::NymdError), - #[error("{0}")] + #[error(transparent)] ValidatorClientError(#[from] validator_client::ValidatorClientError), } diff --git a/common/commands/src/context/mod.rs b/common/commands/src/context/mod.rs index 4f55aa1847..3a21634afb 100644 --- a/common/commands/src/context/mod.rs +++ b/common/commands/src/context/mod.rs @@ -7,8 +7,8 @@ use network_defaults::{ NymNetworkDetails, }; use tap::prelude::*; +pub use validator_client::nym_api::Client as NymApiClient; use validator_client::nymd::{self, AccountId, NymdClient, QueryNymdClient, SigningNymdClient}; -pub use validator_client::validator_api::Client as ValidatorApiClient; use crate::context::errors::ContextError; @@ -16,14 +16,14 @@ pub mod errors; pub type SigningClient = validator_client::nymd::NymdClient; pub type QueryClient = validator_client::nymd::NymdClient; -pub type SigningClientWithValidatorAPI = validator_client::Client; -pub type QueryClientWithValidatorAPI = validator_client::Client; +pub type SigningClientWithNymd = validator_client::Client; +pub type QueryClientWithNymd = validator_client::Client; #[derive(Debug)] pub struct ClientArgs { pub config_env_file: Option, pub nymd_url: Option, - pub validator_api_url: Option, + pub nym_api_url: Option, pub mnemonic: Option, pub mixnet_contract_address: Option, pub vesting_contract_address: Option, @@ -38,8 +38,8 @@ pub fn get_network_details(args: &ClientArgs) -> Result Result { let client_config = nymd::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; // get mnemonic let mnemonic = match std::env::var("MNEMONIC") { @@ -88,7 +88,7 @@ pub fn create_query_client( network_details: &NymNetworkDetails, ) -> Result { let client_config = nymd::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; let nymd_url = network_details .endpoints @@ -103,12 +103,12 @@ pub fn create_query_client( } } -pub fn create_signing_client_with_validator_api( +pub fn create_signing_client_with_nym_api( args: ClientArgs, network_details: &NymNetworkDetails, -) -> Result { +) -> Result { let client_config = validator_client::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; // get mnemonic let mnemonic = match std::env::var("MNEMONIC") { @@ -126,11 +126,11 @@ pub fn create_signing_client_with_validator_api( } } -pub fn create_query_client_with_validator_api( +pub fn create_query_client_with_nym_api( network_details: &NymNetworkDetails, -) -> Result { +) -> Result { let client_config = validator_client::Config::try_from_nym_network_details(network_details) - .tap_err(|e| log::error!("Failed to get client config - {:?}", e))?; + .tap_err(|err| log::error!("Failed to get client config - {err}"))?; match validator_client::client::Client::new_query(client_config) { Ok(client) => Ok(client), diff --git a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs index d816e65a0c..6867f51e81 100644 --- a/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs +++ b/common/commands/src/validator/mixnet/delegators/query_for_delegations.rs @@ -4,7 +4,7 @@ use clap::Parser; use log::info; -use crate::context::SigningClientWithValidatorAPI; +use crate::context::SigningClientWithNymd; use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough}; use comfy_table::Table; @@ -14,7 +14,7 @@ use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventKin #[derive(Debug, Parser)] pub struct Args {} -pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) { +pub async fn execute(_args: Args, client: SigningClientWithNymd) { info!( "Getting delegations for account {}...", client.nymd.address() @@ -48,14 +48,14 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) { } } -async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String { +async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithNymd) -> String { match client.nymd.get_block_timestamp(Some(block_height)).await { Ok(res) => res.to_rfc3339(), Err(_e) => "-".to_string(), } } -async fn print_delegations(delegations: Vec, client: &SigningClientWithValidatorAPI) { +async fn print_delegations(delegations: Vec, client: &SigningClientWithNymd) { let mut table = Table::new(); table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]); @@ -75,10 +75,7 @@ async fn print_delegations(delegations: Vec, client: &SigningClientW println!("{table}"); } -async fn print_delegation_events( - events: Vec, - client: &SigningClientWithValidatorAPI, -) { +async fn print_delegation_events(events: Vec, client: &SigningClientWithNymd) { let mut table = Table::new(); table.set_header(vec![ diff --git a/common/commands/src/validator/mixnet/query/query_all_gateways.rs b/common/commands/src/validator/mixnet/query/query_all_gateways.rs index 679ac08bcd..7904af2c4d 100644 --- a/common/commands/src/validator/mixnet/query/query_all_gateways.rs +++ b/common/commands/src/validator/mixnet/query/query_all_gateways.rs @@ -4,7 +4,7 @@ use clap::Parser; use comfy_table::Table; -use crate::context::QueryClientWithValidatorAPI; +use crate::context::QueryClientWithNymd; use crate::utils::{pretty_cosmwasm_coin, show_error}; #[derive(Debug, Parser)] @@ -14,8 +14,8 @@ pub struct Args { pub identity_key: Option, } -pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) { - match client.validator_api.get_gateways().await { +pub async fn query(args: Args, client: &QueryClientWithNymd) { + match client.nym_api.get_gateways().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs index 4ec6b25b7b..cdcd70672f 100644 --- a/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs +++ b/common/commands/src/validator/mixnet/query/query_all_mixnodes.rs @@ -4,7 +4,7 @@ use clap::Parser; use comfy_table::Table; -use crate::context::QueryClientWithValidatorAPI; +use crate::context::QueryClientWithNymd; use crate::utils::{pretty_decimal_with_denom, show_error}; #[derive(Debug, Parser)] @@ -14,8 +14,8 @@ pub struct Args { pub identity_key: Option, } -pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) { - match client.validator_api.get_mixnodes().await { +pub async fn query(args: Args, client: &QueryClientWithNymd) { + match client.nym_api.get_mixnodes().await { Ok(res) => match args.identity_key { Some(identity_key) => { let node = res.iter().find(|node| { diff --git a/common/commands/src/validator/signature/errors.rs b/common/commands/src/validator/signature/errors.rs index 2369e06d66..d67c3d387b 100644 --- a/common/commands/src/validator/signature/errors.rs +++ b/common/commands/src/validator/signature/errors.rs @@ -11,6 +11,6 @@ pub enum Errors { #[error("signature error - {0}")] SignatureError(#[from] k256::ecdsa::signature::Error), - #[error("{0}")] + #[error(transparent)] CosmrsError(#[from] cosmrs::ErrorReport), } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index b2d0f1ae20..7a85486eb0 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize}; pub struct InstantiateMsg { pub group_addr: String, pub multisig_addr: String, - pub admin: String, pub mix_denom: String, } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index ffb1653919..4de725cebf 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -6,15 +6,48 @@ use std::fmt::{Display, Formatter}; pub use crate::dealer::{DealerDetails, PagedDealerResponse}; pub use contracts_common::dealings::ContractSafeBytes; -pub use cosmwasm_std::{Addr, Coin}; +pub use cosmwasm_std::{Addr, Coin, Timestamp}; pub type EncodedBTEPublicKeyWithProof = String; pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str; pub type NodeIndex = u64; +// The time sign-up is open for dealers to join (2 minutes) +pub const PUBLIC_KEY_SUBMISSION_TIME_SECS: u64 = 60 * 2; +pub const DEALING_EXCHANGE_TIME_SECS: u64 = 60 * 5; +pub const VERIFICATION_KEY_SUBMISSION_TIME_SECS: u64 = 60 * 5; +pub const VERIFICATION_KEY_VALIDATION_TIME_SECS: u64 = 60; +pub const VERIFICATION_KEY_FINALIZATION_TIME_SECS: u64 = 60; +// The time an epoch lasts (2 weeks) +pub const IN_PROGRESS_TIME_SECS: u64 = 60 * 60 * 24 * 14; + // 2 public attributes, 2 private attributes, 1 fixed for coconut credential pub const TOTAL_DEALINGS: usize = 2 + 2 + 1; +#[derive(Serialize, Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +#[serde(rename_all = "snake_case")] +pub struct Epoch { + pub state: EpochState, + pub finish_timestamp: Timestamp, +} + +impl Epoch { + pub fn new(state: EpochState, current_timestamp: Timestamp) -> Self { + let duration = match state { + EpochState::PublicKeySubmission => PUBLIC_KEY_SUBMISSION_TIME_SECS, + EpochState::DealingExchange => DEALING_EXCHANGE_TIME_SECS, + EpochState::VerificationKeySubmission => VERIFICATION_KEY_SUBMISSION_TIME_SECS, + EpochState::VerificationKeyValidation => VERIFICATION_KEY_VALIDATION_TIME_SECS, + EpochState::VerificationKeyFinalization => VERIFICATION_KEY_FINALIZATION_TIME_SECS, + EpochState::InProgress => IN_PROGRESS_TIME_SECS, + }; + Epoch { + state, + finish_timestamp: current_timestamp.plus_seconds(duration), + } + } +} + // currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints), // the epoch can be in the following states (in order): // 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs index b552da1931..0ab73f6085 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs @@ -56,7 +56,6 @@ impl Family { self.proxy.as_ref() } - #[allow(dead_code)] pub fn label(&self) -> &str { &self.label } diff --git a/common/credential-storage/src/lib.rs b/common/credential-storage/src/lib.rs index 3b683026ce..d0a0992894 100644 --- a/common/credential-storage/src/lib.rs +++ b/common/credential-storage/src/lib.rs @@ -45,13 +45,13 @@ impl PersistentStorage { let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { Ok(db) => db, Err(err) => { - error!("Failed to connect to SQLx database: {}", err); + error!("Failed to connect to SQLx database: {err}"); return Err(err.into()); } }; if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { - error!("Failed to perform migration on the SQLx database: {}", err); + error!("Failed to perform migration on the SQLx database: {err}"); return Err(err.into()); } @@ -104,7 +104,7 @@ impl Storage for PersistentStorage { pub async fn initialise_storage(path: PathBuf) -> PersistentStorage { match PersistentStorage::init(path).await { - Err(err) => panic!("failed to initialise credential storage - {}", err), + Err(err) => panic!("failed to initialise credential storage - {err}"), Ok(storage) => storage, } } diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index 58d43af03f..2f754253cc 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -13,7 +13,7 @@ thiserror = "1.0" # I guess temporarily until we get serde support in coconut up and running coconut-interface = { path = "../coconut-interface" } crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] } -validator-api-requests = { path = "../../validator-api/validator-api-requests" } +nym-api-requests = { path = "../../nym-api/nym-api-requests" } validator-client = { path = "../client-libs/validator-client" } [dev-dependencies] diff --git a/common/credentials/src/coconut/params.rs b/common/credentials/src/coconut/params.rs index 42b9205ebb..7e2260bc10 100644 --- a/common/credentials/src/coconut/params.rs +++ b/common/credentials/src/coconut/params.rs @@ -9,7 +9,7 @@ type Aes128Ctr = ctr::Ctr64LE; /// Hashing algorithm used during hkdf for ephemeral shared key generation per blinded signature /// response encryption. -pub type ValidatorApiCredentialHkdfAlgorithm = blake3::Hasher; +pub type NymApiCredentialHkdfAlgorithm = blake3::Hasher; /// Encryption algorithm used for end-to-end encryption of blinded signature response -pub type ValidatorApiCredentialEncryptionAlgorithm = Aes128Ctr; +pub type NymApiCredentialEncryptionAlgorithm = Aes128Ctr; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index f701938d59..0305bb845b 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -8,13 +8,11 @@ use coconut_interface::{ use crypto::asymmetric::encryption::PublicKey; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; -use validator_api_requests::coconut::BlindSignRequestBody; +use nym_api_requests::coconut::BlindSignRequestBody; use validator_client::client::CoconutApiClient; use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; -use crate::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, -}; +use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm}; use crate::error::Error; pub async fn obtain_aggregate_verification_key( @@ -66,11 +64,11 @@ async fn obtain_partial_credential( let remote_key = PublicKey::from_bytes(&response.remote_key)?; let encryption_key = recompute_shared_key::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, >(&remote_key, attributes.encryption_key()); - let zero_iv = stream_cipher::zero_iv::(); - let blinded_signature_bytes = stream_cipher::decrypt::( + let zero_iv = stream_cipher::zero_iv::(); + let blinded_signature_bytes = stream_cipher::decrypt::( &encryption_key, &zero_iv, &encrypted_signature, diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index a8e749f2e0..94d3d2820b 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -22,6 +22,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true } serde_bytes = { version = "0.11.6", optional = true } serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" } subtle-encoding = { version = "0.5", features = ["bech32-preview"]} +thiserror = "1.0.37" # internal nymsphinx-types = { path = "../nymsphinx/types" } diff --git a/common/crypto/dkg/src/dealing.rs b/common/crypto/dkg/src/dealing.rs index b3633ec82d..d92a9ba6b4 100644 --- a/common/crypto/dkg/src/dealing.rs +++ b/common/crypto/dkg/src/dealing.rs @@ -326,11 +326,6 @@ pub fn try_recover_verification_keys( return Err(DkgError::MismatchedDealings); } - // currently we expect every dealer to also be a receiver. This restriction might be relaxed in the future - if dealings.len() != receivers.len() { - return Err(DkgError::MismatchedDealings); - } - let indices = receivers.keys().collect::>(); // Compute A0, ..., A_{t-1} diff --git a/common/crypto/dkg/tests/integration.rs b/common/crypto/dkg/tests/integration.rs index 2d25c31d40..4ac55df1c7 100644 --- a/common/crypto/dkg/tests/integration.rs +++ b/common/crypto/dkg/tests/integration.rs @@ -254,3 +254,125 @@ fn full_threshold_secret_resharing() { assert_ne!(derived_secrets, reshared_secrets); assert_ne!(recovered_partials, reshared_partials); } + +#[test] +#[ignore] // expensive test +fn full_threshold_secret_resharing_left_party() { + let dummy_seed = [42u8; 32]; + let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed); + let params = setup(); + + // the simplest possible case + let threshold = 2; + + // the indices are going to get assigned externally, so for test sake, use non-consecutive ones + let mut node_indices = vec![15u64, 248, 33521]; + + let mut receivers = BTreeMap::new(); + let mut full_keys = Vec::new(); + for index in &node_indices { + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(*index, *pk.public_key()); + full_keys.push((dk, pk)) + } + + let first_dealings = node_indices + .iter() + .map(|&dealer_index| { + Dealing::create(&mut rng, ¶ms, dealer_index, threshold, &receivers, None).0 + }) + .collect::>(); + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_original_master, + recovered_partials, + } = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap(); + + let mut derived_secrets = Vec::new(); + for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + let shares = first_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = + combine_shares(shares, &receivers.keys().copied().collect::>()).unwrap(); + + derived_secrets.push(recovered_secret) + } + + let original_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), derived_secrets[0]), + (Scalar::from(node_indices[1]), derived_secrets[1]), + ]) + .unwrap(); + + // one party leaves the process + let left_party_index = node_indices.pop().unwrap(); + receivers.remove(&left_party_index); + full_keys.pop(); + // and another one joins, but we're still over the threshold value of initial parties + let join_party_index = 100000; + let (dk, pk) = keygen(¶ms, &mut rng); + receivers.insert(join_party_index, *pk.public_key()); + full_keys.push((dk, pk)); + + // only initial parties attempt to create resharing dealings! + let resharing_dealings = node_indices + .iter() + .zip(derived_secrets.iter().take(2)) + .map(|(&dealer_index, prior_secret)| { + Dealing::create( + &mut rng, + ¶ms, + dealer_index, + threshold, + &receivers, + Some(*prior_secret), + ) + .0 + }) + .collect::>(); + + for (reshared_dealing, prior_vk) in resharing_dealings + .iter() + .zip(recovered_partials.iter().take(2)) + { + reshared_dealing + .verify(¶ms, threshold, &receivers, Some(*prior_vk)) + .unwrap(); + } + + // recover verification keys + let RecoveredVerificationKeys { + recovered_master: public_reshared_master, + recovered_partials: reshared_partials, + } = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap(); + + let mut reshared_secrets = Vec::new(); + for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() { + let shares = resharing_dealings + .iter() + .map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap()) + .collect(); + + let recovered_secret = combine_shares(shares, &node_indices).unwrap(); + + reshared_secrets.push(recovered_secret) + } + + let reshared_master = perform_lagrangian_interpolation_at_origin(&[ + (Scalar::from(node_indices[0]), reshared_secrets[0]), + (Scalar::from(join_party_index), reshared_secrets[2]), + ]) + .unwrap(); + + // the master secret and public values didn't change + assert_eq!(original_master, reshared_master); + assert_eq!(public_original_master, public_reshared_master); + + // but partials did + assert_ne!(derived_secrets, reshared_secrets); + assert_ne!(recovered_partials, reshared_partials); +} diff --git a/common/crypto/src/asymmetric/encryption/mod.rs b/common/crypto/src/asymmetric/encryption/mod.rs index 5c7b5398ad..28ff92c07e 100644 --- a/common/crypto/src/asymmetric/encryption/mod.rs +++ b/common/crypto/src/asymmetric/encryption/mod.rs @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use std::fmt::{self, Display, Formatter}; +use thiserror::Error; + #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{self, Display, Formatter}; /// Size of a X25519 private key pub const PRIVATE_KEY_SIZE: usize = 32; @@ -17,31 +19,26 @@ pub const PUBLIC_KEY_SIZE: usize = 32; /// Size of a X25519 shared secret pub const SHARED_SECRET_SIZE: usize = 32; -#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug, Error)] pub enum KeyRecoveryError { - InvalidPublicKeyBytes, - InvalidPrivateKeyBytes, - MalformedString(bs58::decode::Error), -} + #[error("received public key of invalid size. Got: {received}, expected: {expected}")] + InvalidSizePublicKey { received: usize, expected: usize }, -impl From for KeyRecoveryError { - fn from(err: bs58::decode::Error) -> Self { - KeyRecoveryError::MalformedString(err) - } -} + #[error("received private key of invalid size. Got: {received}, expected: {expected}")] + InvalidSizePrivateKey { received: usize, expected: usize }, -// required for std::error::Error -impl Display for KeyRecoveryError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - KeyRecoveryError::InvalidPrivateKeyBytes => write!(f, "Invalid private key bytes"), - KeyRecoveryError::InvalidPublicKeyBytes => write!(f, "Invalid public key bytes"), - KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), - } - } -} + #[error("the base58 representation of the public key was malformed - {source}")] + MalformedPublicKeyString { + #[source] + source: bs58::decode::Error, + }, -impl std::error::Error for KeyRecoveryError {} + #[error("the base58 representation of the private key was malformed - {source}")] + MalformedPrivateKeyString { + #[source] + source: bs58::decode::Error, + }, +} pub struct KeyPair { pub(crate) private_key: PrivateKey, @@ -112,7 +109,10 @@ impl PublicKey { pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PUBLIC_KEY_SIZE { - return Err(KeyRecoveryError::InvalidPublicKeyBytes); + return Err(KeyRecoveryError::InvalidSizePublicKey { + received: b.len(), + expected: PUBLIC_KEY_SIZE, + }); } let mut bytes = [0; PUBLIC_KEY_SIZE]; bytes.copy_from_slice(&b[..PUBLIC_KEY_SIZE]); @@ -124,7 +124,9 @@ impl PublicKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| KeyRecoveryError::MalformedPublicKeyString { source })?; Self::from_bytes(&bytes) } } @@ -188,7 +190,10 @@ impl PrivateKey { pub fn from_bytes(b: &[u8]) -> Result { if b.len() != PRIVATE_KEY_SIZE { - return Err(KeyRecoveryError::InvalidPrivateKeyBytes); + return Err(KeyRecoveryError::InvalidSizePrivateKey { + received: b.len(), + expected: PRIVATE_KEY_SIZE, + }); } let mut bytes = [0; 32]; bytes.copy_from_slice(&b[..PRIVATE_KEY_SIZE]); @@ -200,7 +205,9 @@ impl PrivateKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| KeyRecoveryError::MalformedPrivateKeyString { source })?; Self::from_bytes(&bytes) } diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index 8ba1de8ec6..836f6c70e0 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -6,10 +6,11 @@ pub use ed25519_dalek::SignatureError; pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use pemstore::traits::{PemStorableKey, PemStorableKeyPair}; +use std::fmt::{self, Display, Formatter}; +use thiserror::Error; + #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; -use std::fmt::{self, Display, Formatter}; - #[cfg(feature = "serde")] use serde::de::Error as SerdeError; #[cfg(feature = "serde")] @@ -17,35 +18,30 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes}; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum Ed25519RecoveryError { - MalformedBytes(SignatureError), - MalformedString(bs58::decode::Error), -} + #[error(transparent)] + MalformedBytes(#[from] SignatureError), -impl From for Ed25519RecoveryError { - fn from(err: SignatureError) -> Self { - Ed25519RecoveryError::MalformedBytes(err) - } -} + #[error("the base58 representation of the public key was malformed - {source}")] + MalformedPublicKeyString { + #[source] + source: bs58::decode::Error, + }, -impl From for Ed25519RecoveryError { - fn from(err: bs58::decode::Error) -> Self { - Ed25519RecoveryError::MalformedString(err) - } -} + #[error("the base58 representation of the private key was malformed - {source}")] + MalformedPrivateKeyString { + #[source] + source: bs58::decode::Error, + }, -impl fmt::Display for Ed25519RecoveryError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err), - Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err), - } - } + #[error("the base58 representation of the signature was malformed - {source}")] + MalformedSignatureString { + #[source] + source: bs58::decode::Error, + }, } -impl std::error::Error for Ed25519RecoveryError {} - /// Keypair for usage in ed25519 EdDSA. #[derive(Debug)] pub struct KeyPair { @@ -134,7 +130,9 @@ impl PublicKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedPublicKeyString { source })?; Self::from_bytes(&bytes) } @@ -211,7 +209,9 @@ impl PrivateKey { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedPrivateKeyString { source })?; Self::from_bytes(&bytes) } @@ -277,7 +277,9 @@ impl Signature { } pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; + let bytes = bs58::decode(val) + .into_vec() + .map_err(|source| Ed25519RecoveryError::MalformedSignatureString { source })?; Self::from_bytes(&bytes) } diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 53676023af..e4d68165b9 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -16,6 +16,7 @@ serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.21.2", features = ["time", "macros", "rt", "net", "io-util"] } tokio-util = { version = "0.7.3", features = ["codec"] } url = "2.2" +thiserror = "1.0.37" crypto = { path = "../crypto" } nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } diff --git a/common/mixnode-common/src/packet_processor/error.rs b/common/mixnode-common/src/packet_processor/error.rs index 38be92079d..084ce708a8 100644 --- a/common/mixnode-common/src/packet_processor/error.rs +++ b/common/mixnode-common/src/packet_processor/error.rs @@ -4,63 +4,22 @@ use nymsphinx_acknowledgements::surb_ack::SurbAckRecoveryError; use nymsphinx_addressing::nodes::NymNodeRoutingAddressError; use nymsphinx_types::Error as SphinxError; -use std::fmt::{self, Display, Formatter}; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum MixProcessingError { - SphinxProcessingError(SphinxError), - InvalidHopAddress(NymNodeRoutingAddressError), - NoSurbAckInFinalHop, - MalformedSurbAck(SurbAckRecoveryError), + #[error("failed to process received packet: {0}")] + SphinxProcessingError(#[from] SphinxError), + #[error("the forward hop address was malformed: {0}")] + InvalidForwardHopAddress(#[from] NymNodeRoutingAddressError), + + #[error("the final hop did not contain a SURB-Ack")] + NoSurbAckInFinalHop, + + #[error("failed to recover the expected SURB-Ack packet: {0}")] + MalformedSurbAck(#[from] SurbAckRecoveryError), + + #[error("the received packet was set to use the very old and very much deprecated 'VPN' mode")] ReceivedOldTypeVpnPacket, } - -impl From for MixProcessingError { - // for the time being just have a single error instance for all possible results of SphinxError - fn from(err: SphinxError) -> Self { - use MixProcessingError::*; - - SphinxProcessingError(err) - } -} - -impl From for MixProcessingError { - fn from(err: NymNodeRoutingAddressError) -> Self { - use MixProcessingError::*; - - InvalidHopAddress(err) - } -} - -impl From for MixProcessingError { - fn from(err: SurbAckRecoveryError) -> Self { - use MixProcessingError::*; - - MalformedSurbAck(err) - } -} - -impl Display for MixProcessingError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - MixProcessingError::SphinxProcessingError(sphinx_err) => { - write!(f, "Sphinx Processing Error - {}", sphinx_err) - } - MixProcessingError::InvalidHopAddress(address_err) => { - write!(f, "Invalid Hop Address - {:?}", address_err) - } - MixProcessingError::NoSurbAckInFinalHop => { - write!(f, "No SURBAck present in the final hop data") - } - MixProcessingError::MalformedSurbAck(surb_ack_err) => { - write!(f, "Malformed SURBAck - {:?}", surb_ack_err) - } - MixProcessingError::ReceivedOldTypeVpnPacket => { - write!(f, "Received an old-type unsafe 'VPN' mode packet") - } - } - } -} - -impl std::error::Error for MixProcessingError {} diff --git a/common/mixnode-common/src/packet_processor/processor.rs b/common/mixnode-common/src/packet_processor/processor.rs index aaadfe0171..84e543d8bd 100644 --- a/common/mixnode-common/src/packet_processor/processor.rs +++ b/common/mixnode-common/src/packet_processor/processor.rs @@ -51,7 +51,7 @@ impl SphinxPacketProcessor { packet: SphinxPacket, ) -> Result { packet.process(&self.sphinx_key).map_err(|err| { - debug!("Failed to unwrap Sphinx packet: {:?}", err); + debug!("Failed to unwrap Sphinx packet: {err}"); MixProcessingError::SphinxProcessingError(err) }) } diff --git a/common/mixnode-common/src/verloc/error.rs b/common/mixnode-common/src/verloc/error.rs index 44a408c77a..0268059d7c 100644 --- a/common/mixnode-common/src/verloc/error.rs +++ b/common/mixnode-common/src/verloc/error.rs @@ -53,13 +53,13 @@ impl Display for RttError { write!(f, "The received reply packet had invalid signature") } RttError::UnreachableNode(id, err) => { - write!(f, "Could not establish connection to {} - {}", id, err) + write!(f, "Could not establish connection to {} - {err}", id) } RttError::UnexpectedConnectionFailureWrite(id, err) => { - write!(f, "Failed to write echo packet to {} - {}", id, err) + write!(f, "Failed to write echo packet to {} - {err}", id) } RttError::UnexpectedConnectionFailureRead(id, err) => { - write!(f, "Failed to read reply packet from {} - {}", id, err) + write!(f, "Failed to read reply packet from {} - {err}", id) } RttError::ConnectionReadTimeout(id) => { write!(f, "Timed out while trying to read reply packet from {}", id) diff --git a/common/mixnode-common/src/verloc/listener.rs b/common/mixnode-common/src/verloc/listener.rs index 8f49779130..812358e330 100644 --- a/common/mixnode-common/src/verloc/listener.rs +++ b/common/mixnode-common/src/verloc/listener.rs @@ -11,7 +11,7 @@ use std::fmt::{Display, Formatter}; use std::net::SocketAddr; use std::sync::Arc; use std::{fmt, io, process}; -use task::ShutdownListener; +use task::TaskClient; use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; use tokio_util::codec::{Decoder, Encoder, Framed}; @@ -19,14 +19,14 @@ use tokio_util::codec::{Decoder, Encoder, Framed}; pub(crate) struct PacketListener { address: SocketAddr, connection_handler: Arc, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketListener { pub(crate) fn new( address: SocketAddr, identity: Arc, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { PacketListener { address, @@ -66,7 +66,7 @@ impl PacketListener { tokio::spawn(connection_handler.handle_connection(socket, remote_addr, handler_shutdown_listener)); } - Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + Err(err) => warn!("Failed to accept incoming connection - {err}"), } }, _ = shutdown_listener.recv() => { @@ -91,7 +91,7 @@ impl ConnectionHandler { self: Arc, conn: TcpStream, remote: SocketAddr, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); @@ -141,9 +141,9 @@ enum EchoPacketCodecError { impl Display for EchoPacketCodecError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {}", err), + EchoPacketCodecError::IoError(err) => write!(f, "encountered io error - {err}"), EchoPacketCodecError::PacketRecoveryError(err) => { - write!(f, "failed to correctly decode an echo packet - {}", err) + write!(f, "failed to correctly decode an echo packet - {err}") } } } diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 48e9a7b483..21053dd7cc 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -12,7 +12,7 @@ use rand::thread_rng; use std::net::{SocketAddr, ToSocketAddrs}; use std::sync::Arc; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use tokio::task::JoinHandle; use tokio::time::sleep; use url::Url; @@ -69,8 +69,8 @@ pub struct Config { /// due to being unable to get the list of nodes. retry_timeout: Duration, - /// URLs to the validator apis for obtaining network topology. - validator_api_urls: Vec, + /// URLs to the nym apis for obtaining network topology. + nym_api_urls: Vec, } impl Config { @@ -132,15 +132,15 @@ impl ConfigBuilder { self } - pub fn validator_api_urls(mut self, validator_api_urls: Vec) -> Self { - self.0.validator_api_urls = validator_api_urls; + pub fn nym_api_urls(mut self, nym_api_urls: Vec) -> Self { + self.0.nym_api_urls = nym_api_urls; self } pub fn build(self) -> Config { // panics here are fine as those are only ever constructed at the initial setup assert!( - !self.0.validator_api_urls.is_empty(), + !self.0.nym_api_urls.is_empty(), "at least one validator endpoint must be provided", ); self.0 @@ -159,7 +159,7 @@ impl Default for ConfigBuilder { tested_nodes_batch_size: DEFAULT_BATCH_SIZE, testing_interval: DEFAULT_TESTING_INTERVAL, retry_timeout: DEFAULT_RETRY_TIMEOUT, - validator_api_urls: vec![], + nym_api_urls: vec![], }) } } @@ -168,7 +168,7 @@ pub struct VerlocMeasurer { config: Config, packet_sender: Arc, packet_listener: Arc, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, currently_used_api: usize, @@ -184,9 +184,9 @@ impl VerlocMeasurer { pub fn new( mut config: Config, identity: Arc, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { - config.validator_api_urls.shuffle(&mut thread_rng()); + config.nym_api_urls.shuffle(&mut thread_rng()); VerlocMeasurer { packet_sender: Arc::new(PacketSender::new( @@ -204,24 +204,21 @@ impl VerlocMeasurer { )), shutdown_listener, currently_used_api: 0, - validator_client: validator_client::ApiClient::new( - config.validator_api_urls[0].clone(), - ), + validator_client: validator_client::ApiClient::new(config.nym_api_urls[0].clone()), config, results: AtomicVerlocResult::new(), } } - fn use_next_validator_api(&mut self) { - if self.config.validator_api_urls.len() == 1 { + fn use_next_nym_api(&mut self) { + if self.config.nym_api_urls.len() == 1 { warn!("There's only a single validator API available - it won't be possible to use a different one"); return; } - self.currently_used_api = - (self.currently_used_api + 1) % self.config.validator_api_urls.len(); + self.currently_used_api = (self.currently_used_api + 1) % self.config.nym_api_urls.len(); self.validator_client - .change_validator_api(self.config.validator_api_urls[self.currently_used_api].clone()) + .change_nym_api(self.config.nym_api_urls[self.currently_used_api].clone()) } pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult { @@ -308,7 +305,7 @@ impl VerlocMeasurer { "failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run", err ); - self.use_next_validator_api(); + self.use_next_nym_api(); sleep(self.config.retry_timeout).await; continue; } diff --git a/common/mixnode-common/src/verloc/sender.rs b/common/mixnode-common/src/verloc/sender.rs index bed34f4474..91f7ee9974 100644 --- a/common/mixnode-common/src/verloc/sender.rs +++ b/common/mixnode-common/src/verloc/sender.rs @@ -11,7 +11,7 @@ use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; use std::{fmt, io}; -use task::ShutdownListener; +use task::TaskClient; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::sleep; @@ -45,7 +45,7 @@ pub(crate) struct PacketSender { packet_timeout: Duration, connection_timeout: Duration, delay_between_packets: Duration, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, } impl PacketSender { @@ -55,7 +55,7 @@ impl PacketSender { packet_timeout: Duration, connection_timeout: Duration, delay_between_packets: Duration, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { PacketSender { identity, diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index d7b01622b1..3a2daeed42 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -355,14 +355,14 @@ pub const DEFAULT_WEBSOCKET_LISTENING_PORT: u16 = 1977; // 'SOCKS5' CLIENT pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080; -// VALIDATOR-API -pub const DEFAULT_VALIDATOR_API_PORT: u16 = 8080; +// NYM-API +pub const DEFAULT_NYM_API_PORT: u16 = 8080; -pub const VALIDATOR_API_VERSION: &str = "v1"; +pub const NYM_API_VERSION: &str = "v1"; // REWARDING -/// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms +/// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$ // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$ // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 9259; // 40$/1hr/6 at 1 Nym == 1$ diff --git a/common/nymsphinx/acknowledgements/Cargo.toml b/common/nymsphinx/acknowledgements/Cargo.toml index f105e0c544..c05976ade7 100644 --- a/common/nymsphinx/acknowledgements/Cargo.toml +++ b/common/nymsphinx/acknowledgements/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] rand = { version = "0.7.3", features = ["wasm-bindgen"] } +thiserror = "1.0.37" crypto = { path = "../../crypto", features = ["symmetric", "rand"] } nymsphinx-addressing = { path = "../addressing" } diff --git a/common/nymsphinx/acknowledgements/src/surb_ack.rs b/common/nymsphinx/acknowledgements/src/surb_ack.rs index 67e863d1f6..912ed4bc32 100644 --- a/common/nymsphinx/acknowledgements/src/surb_ack.rs +++ b/common/nymsphinx/acknowledgements/src/surb_ack.rs @@ -4,10 +4,13 @@ use crate::identifier::prepare_identifier; use crate::AckKey; use nymsphinx_addressing::clients::Recipient; -use nymsphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN}; +use nymsphinx_addressing::nodes::{ + NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN, +}; use nymsphinx_params::packet_sizes::PacketSize; use nymsphinx_params::DEFAULT_NUM_MIX_HOPS; use nymsphinx_types::builder::SphinxPacketBuilder; +use nymsphinx_types::Error as SphinxError; use nymsphinx_types::{ delays::{self, Delay}, SphinxPacket, @@ -15,6 +18,7 @@ use nymsphinx_types::{ use rand::{CryptoRng, RngCore}; use std::convert::TryFrom; use std::time; +use thiserror::Error; use topology::{NymTopology, NymTopologyError}; pub struct SurbAck { @@ -23,11 +27,16 @@ pub struct SurbAck { expected_total_delay: Delay, } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum SurbAckRecoveryError { - InvalidPacketSize, - InvalidAddress, - InvalidSphinxPacket, + #[error("received an invalid number of bytes to deserialize the SURB-Ack. Got {received}, expected {expected}")] + InvalidPacketSize { received: usize, expected: usize }, + + #[error("could not extract first hop address information - {0}")] + InvalidAddress(#[from] NymNodeRoutingAddressError), + + #[error("the contained sphinx packet was not correctly formed - {0}")] + InvalidSphinxPacket(#[from] SphinxError), } impl SurbAck { @@ -92,20 +101,17 @@ impl SurbAck { b: &[u8], ) -> Result<(NymNodeRoutingAddress, SphinxPacket), SurbAckRecoveryError> { if b.len() != Self::len() { - Err(SurbAckRecoveryError::InvalidPacketSize) + Err(SurbAckRecoveryError::InvalidPacketSize { + received: b.len(), + expected: Self::len(), + }) } else { - let address = match NymNodeRoutingAddress::try_from_bytes(b) { - Ok(address) => address, - Err(_) => return Err(SurbAckRecoveryError::InvalidAddress), - }; + let address = NymNodeRoutingAddress::try_from_bytes(b)?; // TODO: this will be variable once/if we decide to introduce optimization described // in common/nymsphinx/chunking/src/lib.rs:available_plaintext_size() let address_offset = MAX_NODE_ADDRESS_UNPADDED_LEN; - let packet = match SphinxPacket::from_bytes(&b[address_offset..]) { - Ok(packet) => packet, - Err(_) => return Err(SurbAckRecoveryError::InvalidSphinxPacket), - }; + let packet = SphinxPacket::from_bytes(&b[address_offset..])?; Ok((address, packet)) } diff --git a/common/nymsphinx/chunking/src/fragment.rs b/common/nymsphinx/chunking/src/fragment.rs index 3f102282dd..8d909982eb 100644 --- a/common/nymsphinx/chunking/src/fragment.rs +++ b/common/nymsphinx/chunking/src/fragment.rs @@ -208,6 +208,11 @@ impl Fragment { } } + /// Gets the size of payload contained in this `Fragment`. + pub fn payload_size(&self) -> usize { + self.payload.len() + } + /// Extracts id of this `Fragment`. pub fn id(&self) -> i32 { self.header.id diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index 6921a8cf69..fc46ece44a 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" [dependencies] bytes = "1.0" tokio-util = { version = "0.7.3", features = ["codec"] } +thiserror = "1.0.37" nymsphinx-types = { path = "../types" } nymsphinx-params = { path = "../params" } diff --git a/common/nymsphinx/framing/src/codec.rs b/common/nymsphinx/framing/src/codec.rs index 5794a88544..f3986d2ece 100644 --- a/common/nymsphinx/framing/src/codec.rs +++ b/common/nymsphinx/framing/src/codec.rs @@ -1,57 +1,48 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::packet::{FramedSphinxPacket, Header}; use bytes::{Buf, BufMut, BytesMut}; use nymsphinx_params::packet_modes::InvalidPacketMode; use nymsphinx_params::packet_sizes::{InvalidPacketSize, PacketSize}; +use nymsphinx_types::Error as SphinxError; use nymsphinx_types::SphinxPacket; use std::io; +use thiserror::Error; use tokio_util::codec::{Decoder, Encoder}; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum SphinxCodecError { - InvalidPacketSize, - InvalidPacketMode, - MalformedSphinxPacket, - IoError(io::Error), -} + #[error("the packet size information was malformed - {0}")] + InvalidPacketSize(#[from] InvalidPacketSize), -impl From for SphinxCodecError { - fn from(err: io::Error) -> Self { - SphinxCodecError::IoError(err) - } + #[error("the packet mode information was malformed - {0}")] + InvalidPacketMode(#[from] InvalidPacketMode), + + #[error("the actual sphinx packet was malformed - {0}")] + MalformedSphinxPacket(#[from] SphinxError), + + #[error("encountered an IO error - {0}")] + IoError(#[from] io::Error), } impl From for io::Error { fn from(err: SphinxCodecError) -> Self { match err { - SphinxCodecError::InvalidPacketSize => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet size") + SphinxCodecError::InvalidPacketSize(source) => { + io::Error::new(io::ErrorKind::InvalidInput, source) } - SphinxCodecError::InvalidPacketMode => { - io::Error::new(io::ErrorKind::InvalidInput, "invalid packet mode") + SphinxCodecError::InvalidPacketMode(source) => { + io::Error::new(io::ErrorKind::InvalidInput, source) } - SphinxCodecError::MalformedSphinxPacket => { - io::Error::new(io::ErrorKind::InvalidData, "malformed packet") + SphinxCodecError::MalformedSphinxPacket(source) => { + io::Error::new(io::ErrorKind::InvalidData, source) } SphinxCodecError::IoError(err) => err, } } } -impl From for SphinxCodecError { - fn from(_: InvalidPacketSize) -> Self { - SphinxCodecError::InvalidPacketSize - } -} - -impl From for SphinxCodecError { - fn from(_: InvalidPacketMode) -> Self { - SphinxCodecError::InvalidPacketMode - } -} - // TODO: in the future it could be extended to have state containing symmetric encryption key // so that all data could be encrypted easily (alternatively we could just slap TLS) pub struct SphinxCodec; @@ -97,17 +88,11 @@ impl Decoder for SphinxCodec { // advance buffer past the header - at this point we have enough bytes src.advance(header.size()); let sphinx_packet_bytes = src.split_to(sphinx_packet_size); - let sphinx_packet = match SphinxPacket::from_bytes(&sphinx_packet_bytes) { - Ok(sphinx_packet) => sphinx_packet, - // here it could be debatable whether stream is corrupt or not, - // but let's go with the safer approach and assume it is. - Err(_) => return Err(SphinxCodecError::MalformedSphinxPacket), - }; - let nymsphinx_packet = FramedSphinxPacket { - header, - packet: sphinx_packet, - }; + // here it could be debatable whether stream is corrupt or not, + // but let's go with the safer approach and assume it is. + let packet = SphinxPacket::from_bytes(&sphinx_packet_bytes)?; + let nymsphinx_packet = FramedSphinxPacket { header, packet }; // As per docs: // Before returning from the function, implementations should ensure that the buffer diff --git a/common/nymsphinx/params/Cargo.toml b/common/nymsphinx/params/Cargo.toml index a06f897f22..ebca914742 100644 --- a/common/nymsphinx/params/Cargo.toml +++ b/common/nymsphinx/params/Cargo.toml @@ -7,5 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +thiserror = "1.0.37" + crypto = { path = "../../crypto", features = ["hashing", "symmetric"] } nymsphinx-types = { path = "../types" } diff --git a/common/nymsphinx/params/src/packet_modes.rs b/common/nymsphinx/params/src/packet_modes.rs index 819b32b3af..3fb7ac6358 100644 --- a/common/nymsphinx/params/src/packet_modes.rs +++ b/common/nymsphinx/params/src/packet_modes.rs @@ -1,10 +1,14 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use std::convert::TryFrom; +use thiserror::Error; -#[derive(Debug)] -pub struct InvalidPacketMode; +#[derive(Error, Debug)] +#[error("{received} is not a valid packet mode tag")] +pub struct InvalidPacketMode { + received: u8, +} #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -31,11 +35,11 @@ impl PacketMode { impl TryFrom for PacketMode { type Error = InvalidPacketMode; - fn try_from(value: u8) -> std::result::Result { + fn try_from(value: u8) -> Result { match value { _ if value == (PacketMode::Mix as u8) => Ok(Self::Mix), _ if value == (PacketMode::Vpn as u8) => Ok(Self::Vpn), - _ => Err(InvalidPacketMode), + v => Err(InvalidPacketMode { received: v }), } } } diff --git a/common/nymsphinx/params/src/packet_sizes.rs b/common/nymsphinx/params/src/packet_sizes.rs index aa573ffe11..ef9976bd92 100644 --- a/common/nymsphinx/params/src/packet_sizes.rs +++ b/common/nymsphinx/params/src/packet_sizes.rs @@ -6,6 +6,7 @@ use nymsphinx_types::header::HEADER_SIZE; use nymsphinx_types::PAYLOAD_OVERHEAD_SIZE; use std::convert::TryFrom; use std::str::FromStr; +use thiserror::Error; // it's up to the smart people to figure those values out : ) const REGULAR_PACKET_SIZE: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 2 * 1024; @@ -20,11 +21,17 @@ const EXTENDED_PACKET_SIZE_8: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 8 * const EXTENDED_PACKET_SIZE_16: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 16 * 1024; const EXTENDED_PACKET_SIZE_32: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 32 * 1024; -#[derive(Debug)] -pub struct InvalidPacketSize; +#[derive(Debug, Error)] +pub enum InvalidPacketSize { + #[error("{received} is not a valid packet size tag")] + UnknownPacketTag { received: u8 }, -#[derive(Debug)] -pub struct InvalidExtendedPacketSize; + #[error("{received} is not a valid extended packet size variant")] + UnknownExtendedPacketVariant { received: String }, + + #[error("{received} does not correspond with any known packet size")] + UnknownPacketSize { received: usize }, +} #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -55,7 +62,9 @@ impl FromStr for PacketSize { "extended8" => Ok(Self::ExtendedPacket8), "extended16" => Ok(Self::ExtendedPacket16), "extended32" => Ok(Self::ExtendedPacket32), - _ => Err(InvalidPacketSize), + s => Err(InvalidPacketSize::UnknownExtendedPacketVariant { + received: s.to_string(), + }), } } } @@ -63,14 +72,14 @@ impl FromStr for PacketSize { impl TryFrom for PacketSize { type Error = InvalidPacketSize; - fn try_from(value: u8) -> std::result::Result { + fn try_from(value: u8) -> Result { match value { _ if value == (PacketSize::RegularPacket as u8) => Ok(Self::RegularPacket), _ if value == (PacketSize::AckPacket as u8) => Ok(Self::AckPacket), _ if value == (PacketSize::ExtendedPacket8 as u8) => Ok(Self::ExtendedPacket8), _ if value == (PacketSize::ExtendedPacket16 as u8) => Ok(Self::ExtendedPacket16), _ if value == (PacketSize::ExtendedPacket32 as u8) => Ok(Self::ExtendedPacket32), - _ => Err(InvalidPacketSize), + v => Err(InvalidPacketSize::UnknownPacketTag { received: v }), } } } @@ -94,7 +103,7 @@ impl PacketSize { self.size() - HEADER_SIZE } - pub fn get_type(size: usize) -> std::result::Result { + pub fn get_type(size: usize) -> Result { if PacketSize::RegularPacket.size() == size { Ok(PacketSize::RegularPacket) } else if PacketSize::AckPacket.size() == size { @@ -106,7 +115,7 @@ impl PacketSize { } else if PacketSize::ExtendedPacket32.size() == size { Ok(PacketSize::ExtendedPacket32) } else { - Err(InvalidPacketSize) + Err(InvalidPacketSize::UnknownPacketSize { received: size }) } } diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index f5ea5545d8..58dd09b263 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] log = "0.4" +thiserror = "1.0.37" \ No newline at end of file diff --git a/common/socks5/ordered-buffer/src/message.rs b/common/socks5/ordered-buffer/src/message.rs index f5f28f53b1..15627d7fe0 100644 --- a/common/socks5/ordered-buffer/src/message.rs +++ b/common/socks5/ordered-buffer/src/message.rs @@ -1,9 +1,16 @@ -use std::cmp::Ordering; +// Copyright 2020-2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -#[derive(Debug, PartialEq, Eq)] +use std::cmp::Ordering; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] pub enum MessageError { + #[error("the received message was empty")] NoData, - IndexTooShort, + + #[error("could not extract message index. Received {received} bytes, but expected {expected}")] + IndexTooShort { received: usize, expected: usize }, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -32,7 +39,10 @@ impl OrderedMessage { } if data.len() < 8 { - return Err(MessageError::IndexTooShort); + return Err(MessageError::IndexTooShort { + received: data.len(), + expected: 8, + }); } let index = u64::from_be_bytes([ data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], @@ -81,7 +91,13 @@ mod ordered_message_from_bytes { #[test] fn fails_when_data_is_too_short() { let result = OrderedMessage::try_from_bytes(vec![1, 2, 3]); - assert_eq!(Err(MessageError::IndexTooShort), result); + assert_eq!( + Err(MessageError::IndexTooShort { + received: 3, + expected: 8 + }), + result + ); } #[test] diff --git a/common/socks5/proxy-helpers/src/connection_controller.rs b/common/socks5/proxy-helpers/src/connection_controller.rs index 97c4487af6..66c8b94a7a 100644 --- a/common/socks5/proxy-helpers/src/connection_controller.rs +++ b/common/socks5/proxy-helpers/src/connection_controller.rs @@ -11,7 +11,7 @@ use std::{ collections::{HashMap, HashSet}, time::Duration, }; -use task::ShutdownListener; +use task::TaskClient; use tokio::time; /// A generic message produced after reading from a socket/connection. It includes data that was @@ -53,7 +53,7 @@ impl ActiveConnection { let ordered_message = match OrderedMessage::try_from_bytes(payload) { Ok(msg) => msg, Err(err) => { - error!("Malformed ordered message - {:?}", err); + error!("Malformed ordered message - {err}"); return; } }; @@ -98,14 +98,14 @@ pub struct Controller { // un-order messages. Note we don't ever expect to have more than 1-2 messages per connection here pending_messages: HashMap, bool)>>, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl Controller { pub fn new( client_connection_tx: ConnectionCommandSender, broadcast_connections: BroadcastActiveConnections, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> (Self, ControllerSender) { let (sender, receiver) = mpsc::unbounded(); ( @@ -158,9 +158,9 @@ impl Controller { .unbounded_send(ConnectionCommand::Close(conn_id)) { if self.shutdown.is_shutdown_poll() { - log::debug!("Failed to send: {}", err); + log::debug!("Failed to send: {err}"); } else { - log::error!("Failed to send: {}", err); + log::error!("Failed to send: {err}"); } } } @@ -197,7 +197,7 @@ impl Controller { socket_closed: active_connection.is_closed, }) { - error!("WTF IS THIS: {:?}", err); + error!("WTF IS THIS: {err}"); } // TODO: ABOVE UNWRAP CAUSED A CRASH IN A NORMAL USE!!!! diff --git a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs index 37403c33ad..2065f7770b 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs @@ -15,7 +15,7 @@ use socks5_requests::ConnectionId; use std::fmt::Debug; use std::time::Duration; use std::{io, sync::Arc}; -use task::ShutdownListener; +use task::TaskClient; use tokio::select; use tokio::{net::tcp::OwnedReadHalf, sync::Notify, time::sleep}; @@ -54,7 +54,7 @@ where Some(data) => match data { Ok(data) => (data, false), Err(err) => { - error!(target: &*format!("({}) socks5 inbound", connection_id), "failed to read request from the socket - {}", err); + error!(target: &*format!("({}) socks5 inbound", connection_id), "failed to read request from the socket - {err}"); (Default::default(), true) } }, @@ -170,7 +170,7 @@ pub(super) async fn run_inbound( adapter_fn: F, shutdown_notify: Arc, lane_queue_lengths: Option, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) -> OwnedReadHalf where F: Fn(ConnectionId, Vec, bool) -> S + Send + 'static, diff --git a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs index defcbf4f5f..a87ada287e 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs @@ -6,7 +6,7 @@ use client_connections::LaneQueueLengths; use socks5_requests::ConnectionId; use std::fmt::Debug; use std::{sync::Arc, time::Duration}; -use task::ShutdownListener; +use task::TaskClient; use tokio::{net::TcpStream, sync::Notify}; mod inbound; @@ -50,7 +50,7 @@ pub struct ProxyRunner { lane_queue_lengths: Option, // Listens to shutdown commands from higher up - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, } impl ProxyRunner @@ -66,7 +66,7 @@ where mix_sender: MixProxySender, connection_id: ConnectionId, lane_queue_lengths: Option, - shutdown_listener: ShutdownListener, + shutdown_listener: TaskClient, ) -> Self { ProxyRunner { mix_receiver: Some(mix_receiver), diff --git a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs index 78db73afb6..ed7f8d74c6 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/outbound.rs @@ -8,7 +8,7 @@ use futures::StreamExt; use log::*; use socks5_requests::ConnectionId; use std::{sync::Arc, time::Duration}; -use task::ShutdownListener; +use task::TaskClient; use tokio::io::AsyncWriteExt; use tokio::select; use tokio::{net::tcp::OwnedWriteHalf, sync::Notify, time::sleep, time::Instant}; @@ -33,7 +33,7 @@ async fn deal_with_message( if let Err(err) = writer.write_all(&connection_message.payload).await { // the other half is probably going to blow up too (if not, this task also needs to notify the other one!!) - error!(target: &*format!("({}) socks5 outbound", connection_id), "failed to write response back to the socket - {}", err); + error!(target: &*format!("({}) socks5 outbound", connection_id), "failed to write response back to the socket - {err}"); return true; } if connection_message.socket_closed { @@ -51,7 +51,7 @@ pub(super) async fn run_outbound( mut mix_receiver: ConnectionReceiver, connection_id: ConnectionId, shutdown_notify: Arc, - mut shutdown_listener: ShutdownListener, + mut shutdown_listener: TaskClient, ) -> (OwnedWriteHalf, ConnectionReceiver) { let shutdown_future = shutdown_notify.notified().then(|_| sleep(SHUTDOWN_TIMEOUT)); tokio::pin!(shutdown_future); diff --git a/common/socks5/requests/src/msg.rs b/common/socks5/requests/src/msg.rs index 2b688d8214..906c643524 100644 --- a/common/socks5/requests/src/msg.rs +++ b/common/socks5/requests/src/msg.rs @@ -9,13 +9,13 @@ use crate::response::{Response, ResponseError}; #[derive(Debug, Error)] pub enum MessageError { - #[error("{0}")] + #[error(transparent)] Request(RequestError), #[error("{0:?}")] Response(ResponseError), - #[error("{0}")] + #[error(transparent)] NetworkRequesterResponseError(NrError), #[error("no data")] diff --git a/common/socks5/requests/src/network_requester_response.rs b/common/socks5/requests/src/network_requester_response.rs index 64e459dfb0..eba3f88dd9 100644 --- a/common/socks5/requests/src/network_requester_response.rs +++ b/common/socks5/requests/src/network_requester_response.rs @@ -63,7 +63,7 @@ impl NetworkRequesterResponse { self.connection_id .to_be_bytes() .iter() - .cloned() + .copied() .chain(self.network_requester_error.into_bytes().into_iter()) .collect() } diff --git a/common/task/src/lib.rs b/common/task/src/lib.rs index 5d3d0ea2f5..7ac5ab8431 100644 --- a/common/task/src/lib.rs +++ b/common/task/src/lib.rs @@ -1,13 +1,12 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub mod shutdown; +pub mod manager; #[cfg(not(target_arch = "wasm32"))] pub mod signal; pub mod spawn; -// WIP(JON): those both need to be public? -pub use shutdown::{ShutdownListener, ShutdownNotifier, StatusReceiver, StatusSender}; +pub use manager::{StatusReceiver, StatusSender, TaskClient, TaskManager}; #[cfg(not(target_arch = "wasm32"))] pub use signal::{wait_for_signal, wait_for_signal_and_error}; diff --git a/common/task/src/shutdown.rs b/common/task/src/manager.rs similarity index 84% rename from common/task/src/shutdown.rs rename to common/task/src/manager.rs index 09e8abcb50..f40a525527 100644 --- a/common/task/src/shutdown.rs +++ b/common/task/src/manager.rs @@ -14,7 +14,6 @@ use tokio::{ const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5; -// WIP(JON): can these be from futures channel too? pub(crate) type SentError = Box; type ErrorSender = mpsc::UnboundedSender; type ErrorReceiver = mpsc::UnboundedReceiver; @@ -29,9 +28,17 @@ enum TaskError { UnexpectedHalt, } -/// Used to notify other tasks to gracefully shutdown +// TODO: possibly we should create a `Status` trait instead of reusing `Error` +#[derive(thiserror::Error, Debug)] +pub enum TaskStatus { + #[error("Ready")] + Ready, +} + +/// Listens to status and error messages from tasks, as well as notifying them to gracefully +/// shutdown. Keeps track of if task stop unexpectedly, such as in a panic. #[derive(Debug)] -pub struct ShutdownNotifier { +pub struct TaskManager { // These channels have the dual purpose of signalling it's time to shutdown, but also to keep // track of which tasks we are still waiting for. notify_tx: watch::Sender<()>, @@ -55,7 +62,7 @@ pub struct ShutdownNotifier { task_status_rx: Option, } -impl Default for ShutdownNotifier { +impl Default for TaskManager { fn default() -> Self { let (notify_tx, notify_rx) = watch::channel(()); let (task_halt_tx, task_halt_rx) = mpsc::unbounded_channel(); @@ -77,11 +84,7 @@ impl Default for ShutdownNotifier { } } -impl ShutdownNotifier { - pub fn take_task_status_rx(&mut self) -> Option { - self.task_status_rx.take() - } - +impl TaskManager { pub fn new(shutdown_timer_secs: u64) -> Self { Self { shutdown_timer_secs, @@ -89,8 +92,8 @@ impl ShutdownNotifier { } } - pub fn subscribe(&self) -> ShutdownListener { - ShutdownListener::new( + pub fn subscribe(&self) -> TaskClient { + TaskClient::new( self.notify_rx .as_ref() .expect("Unable to subscribe to shutdown notifier that is already shutdown") @@ -105,15 +108,21 @@ impl ShutdownNotifier { self.notify_tx.send(()) } - pub fn start_status_listener(&mut self, mut sender: StatusSender) { + pub async fn start_status_listener(&mut self, mut sender: StatusSender) { + // Announce that we are operational. This means that in the application where this is used, + // everything is up and running and ready to go. + if let Err(msg) = sender.send(Box::new(TaskStatus::Ready)).await { + log::error!("Error sending status message: {}", msg); + }; + if let Some(mut task_status_rx) = self.task_status_rx.take() { log::info!("Starting status message listener"); crate::spawn::spawn(async move { loop { if let Some(msg) = task_status_rx.next().await { log::trace!("Got msg: {}", msg); - if sender.send(msg).await.is_err() { - log::error!("Error sending status message"); + if let Err(msg) = sender.send(msg).await { + log::error!("Error sending status message: {}", msg); } } else { log::trace!("Stopping since channel closed"); @@ -149,7 +158,7 @@ impl ShutdownNotifier { } pub async fn wait_for_shutdown(&mut self) { - log::info!("Waiting for shutdown"); + log::debug!("Waiting for shutdown"); if let Some(notify_rx) = self.notify_rx.take() { drop(notify_rx); } @@ -173,9 +182,10 @@ impl ShutdownNotifier { } } -/// Listen for shutdown notifications +/// Listen for shutdown notifications, and can send error and status messages back to the +/// `TaskManager` #[derive(Clone, Debug)] -pub struct ShutdownListener { +pub struct TaskClient { // If a shutdown notification has been registered shutdown: bool, @@ -193,10 +203,10 @@ pub struct ShutdownListener { status_msg: StatusSender, // The current operating mode - mode: ShutdownListenerMode, + mode: ClientOperatingMode, } -impl ShutdownListener { +impl TaskClient { #[cfg(not(target_arch = "wasm32"))] const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); @@ -205,31 +215,31 @@ impl ShutdownListener { return_error: ErrorSender, drop_error: ErrorSender, status_msg: StatusSender, - ) -> ShutdownListener { - ShutdownListener { + ) -> TaskClient { + TaskClient { shutdown: false, notify, return_error, drop_error, status_msg, - mode: ShutdownListenerMode::Listening, + mode: ClientOperatingMode::Listening, } } // Create a dummy that will never report that we should shutdown. - pub fn dummy() -> ShutdownListener { + pub fn dummy() -> TaskClient { let (_notify_tx, notify_rx) = watch::channel(()); let (task_halt_tx, _task_halt_rx) = mpsc::unbounded_channel(); let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel(); //let (task_status_tx, _task_status_rx) = futures::channel::mpsc::unbounded(); let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128); - ShutdownListener { + TaskClient { shutdown: false, notify: notify_rx, return_error: task_halt_tx, drop_error: task_drop_tx, status_msg: task_status_tx, - mode: ShutdownListenerMode::Dummy, + mode: ClientOperatingMode::Dummy, } } @@ -256,6 +266,15 @@ impl ShutdownListener { self.shutdown = true; } + pub async fn recv_with_delay(&mut self) { + self.recv() + .then(|msg| async move { + sleep(Duration::from_secs(2)).await; + msg + }) + .await + } + pub async fn recv_timeout(&mut self) { if self.mode.is_dummy() { return pending().await; @@ -299,7 +318,7 @@ impl ShutdownListener { if self.mode.is_dummy() { return; } - log::trace!("Notifying we stopped: {:?}", err); + log::trace!("Notifying we stopped: {err}"); if self.return_error.send(err).is_err() { log::error!("Failed to send back error message"); } @@ -315,7 +334,7 @@ impl ShutdownListener { } } -impl Drop for ShutdownListener { +impl Drop for TaskClient { fn drop(&mut self) { if !self.mode.should_signal_on_drop() { return; @@ -331,7 +350,7 @@ impl Drop for ShutdownListener { } #[derive(Clone, Debug, PartialEq, Eq)] -enum ShutdownListenerMode { +enum ClientOperatingMode { // Normal operations Listening, // Normal operations, but we don't report back if the we stop by getting dropped. @@ -340,20 +359,20 @@ enum ShutdownListenerMode { Dummy, } -impl ShutdownListenerMode { +impl ClientOperatingMode { fn is_dummy(&self) -> bool { - self == &ShutdownListenerMode::Dummy + self == &ClientOperatingMode::Dummy } fn should_signal_on_drop(&self) -> bool { match self { - ShutdownListenerMode::Listening => true, - ShutdownListenerMode::ListeningButDontReportHalt | ShutdownListenerMode::Dummy => false, + ClientOperatingMode::Listening => true, + ClientOperatingMode::ListeningButDontReportHalt | ClientOperatingMode::Dummy => false, } } fn set_should_not_signal_on_drop(&mut self) { - use ShutdownListenerMode::{Dummy, Listening, ListeningButDontReportHalt}; + use ClientOperatingMode::{Dummy, Listening, ListeningButDontReportHalt}; *self = match &self { ListeningButDontReportHalt | Listening => ListeningButDontReportHalt, Dummy => Dummy, @@ -367,7 +386,7 @@ mod tests { #[tokio::test] async fn signal_shutdown() { - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let mut listener = shutdown.subscribe(); let task = tokio::spawn(async move { diff --git a/common/task/src/signal.rs b/common/task/src/signal.rs index a04a0f5601..483c7c630b 100644 --- a/common/task/src/signal.rs +++ b/common/task/src/signal.rs @@ -1,4 +1,4 @@ -use crate::{shutdown::SentError, ShutdownNotifier}; +use crate::{manager::SentError, TaskManager}; #[cfg(unix)] pub async fn wait_for_signal() { @@ -29,7 +29,7 @@ pub async fn wait_for_signal() { } #[cfg(unix)] -pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> { +pub async fn wait_for_signal_and_error(shutdown: &mut TaskManager) -> Result<(), SentError> { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); @@ -56,7 +56,7 @@ pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Resul } #[cfg(not(unix))] -pub async fn wait_for_signal_and_error(shutdown: &mut ShutdownNotifier) -> Result<(), SentError> { +pub async fn wait_for_signal_and_error(shutdown: &mut TaskManager) -> Result<(), SentError> { tokio::select! { _ = tokio::signal::ctrl_c() => { log::info!("Received SIGINT"); diff --git a/common/task/src/spawn.rs b/common/task/src/spawn.rs index ba66ea7f16..d30c9ac850 100644 --- a/common/task/src/spawn.rs +++ b/common/task/src/spawn.rs @@ -1,4 +1,4 @@ -use crate::ShutdownListener; +use crate::TaskClient; use std::future::Future; #[cfg(target_arch = "wasm32")] @@ -18,7 +18,7 @@ where tokio::spawn(future); } -pub fn spawn_with_report_error(future: F, mut shutdown: ShutdownListener) +pub fn spawn_with_report_error(future: F, mut shutdown: TaskClient) where F: Future> + Send + 'static, T: 'static, diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index 57b34146e6..9df2156b32 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -7,63 +7,25 @@ use mixnet_contract_common::GatewayBond; use nymsphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress}; use nymsphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; -use std::fmt::{self, Display, Formatter}; +use std::fmt; use std::io; use std::net::SocketAddr; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum GatewayConversionError { - InvalidIdentityKey(identity::Ed25519RecoveryError), - InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(String, io::Error), - InvalidStake, - Other(Box), -} + #[error("gateway identity key was malformed - {0}")] + InvalidIdentityKey(#[from] identity::Ed25519RecoveryError), -impl From for GatewayConversionError { - fn from(err: encryption::KeyRecoveryError) -> Self { - GatewayConversionError::InvalidSphinxKey(err) - } -} + #[error("gateway sphinx key was malformed - {0}")] + InvalidSphinxKey(#[from] encryption::KeyRecoveryError), -impl From for GatewayConversionError { - fn from(err: identity::Ed25519RecoveryError) -> Self { - GatewayConversionError::InvalidIdentityKey(err) - } -} - -impl Display for GatewayConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - GatewayConversionError::InvalidIdentityKey(err) => write!( - f, - "failed to convert gateway due to invalid identity key - {}", - err - ), - GatewayConversionError::InvalidSphinxKey(err) => write!( - f, - "failed to convert gateway due to invalid sphinx key - {}", - err - ), - GatewayConversionError::InvalidAddress(address, err) => { - write!( - f, - "failed to convert gateway due to invalid address {} - {}", - address, err - ) - } - GatewayConversionError::InvalidStake => { - write!(f, "failed to convert gateway due to invalid stake") - } - GatewayConversionError::Other(err) => { - write!( - f, - "failed to convert gateway due to another error - {}", - err - ) - } - } - } + #[error("'{value}' is not a valid gateway address - {source}")] + InvalidAddress { + value: String, + #[source] + source: io::Error, + }, } #[derive(Debug, Clone)] @@ -123,14 +85,22 @@ impl<'a> TryFrom<&'a GatewayBond> for Node { type Error = GatewayConversionError; fn try_from(bond: &'a GatewayBond) -> Result { - let host: NetworkAddress = bond.gateway.host.parse().map_err(|err| { - GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) - })?; + let host: NetworkAddress = + bond.gateway + .host + .parse() + .map_err(|err| GatewayConversionError::InvalidAddress { + value: bond.gateway.host.clone(), + source: err, + })?; // try to completely resolve the host in the mix situation to avoid doing it every // single time we want to construct a path let mix_host = host.to_socket_addrs(bond.gateway.mix_port).map_err(|err| { - GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) + GatewayConversionError::InvalidAddress { + value: bond.gateway.host.clone(), + source: err, + } })?[0]; Ok(Node { diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index ff3ba55c83..527176339d 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -276,7 +276,7 @@ pub fn nym_topology_from_detailed( match bond.try_into() { Ok(mix) => layer_entry.push(mix), Err(err) => { - warn!("Mix {} / {} is malformed - {}", mix_id, mix_identity, err); + warn!("Mix {} / {} is malformed - {err}", mix_id, mix_identity); continue; } } @@ -288,7 +288,7 @@ pub fn nym_topology_from_detailed( match bond.try_into() { Ok(gate) => gateways.push(gate), Err(err) => { - warn!("Gateway {} is malformed - {}", gate_id, err); + warn!("Gateway {} is malformed - {err}", gate_id); continue; } } diff --git a/common/topology/src/mix.rs b/common/topology/src/mix.rs index 81e0308b2f..08d7f5c0b7 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -7,63 +7,24 @@ use mixnet_contract_common::{Layer, MixId, MixNodeBond}; use nymsphinx_addressing::nodes::NymNodeRoutingAddress; use nymsphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; -use std::fmt::{self, Display, Formatter}; use std::io; use std::net::SocketAddr; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum MixnodeConversionError { - InvalidIdentityKey(identity::Ed25519RecoveryError), - InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(String, io::Error), - InvalidStake, - Other(Box), -} + #[error("mixnode identity key was malformed - {0}")] + InvalidIdentityKey(#[from] identity::Ed25519RecoveryError), -impl From for MixnodeConversionError { - fn from(err: encryption::KeyRecoveryError) -> Self { - MixnodeConversionError::InvalidSphinxKey(err) - } -} + #[error("mixnode sphinx key was malformed - {0}")] + InvalidSphinxKey(#[from] encryption::KeyRecoveryError), -impl From for MixnodeConversionError { - fn from(err: identity::Ed25519RecoveryError) -> Self { - MixnodeConversionError::InvalidIdentityKey(err) - } -} - -impl Display for MixnodeConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - MixnodeConversionError::InvalidIdentityKey(err) => write!( - f, - "failed to convert mixnode due to invalid identity key - {}", - err - ), - MixnodeConversionError::InvalidSphinxKey(err) => write!( - f, - "failed to convert mixnode due to invalid sphinx key - {}", - err - ), - MixnodeConversionError::InvalidAddress(address, err) => { - write!( - f, - "failed to convert mixnode due to invalid address {} - {}", - address, err - ) - } - MixnodeConversionError::InvalidStake => { - write!(f, "failed to convert mixnode due to invalid stake") - } - MixnodeConversionError::Other(err) => { - write!( - f, - "failed to convert mixnode due to another error - {}", - err - ) - } - } - } + #[error("'{value}' is not a valid mixnode address - {source}")] + InvalidAddress { + value: String, + #[source] + source: io::Error, + }, } #[derive(Debug, Clone)] @@ -100,16 +61,22 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node { type Error = MixnodeConversionError; fn try_from(bond: &'a MixNodeBond) -> Result { - let host: NetworkAddress = bond.mix_node.host.parse().map_err(|err| { - MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) - })?; + let host: NetworkAddress = + bond.mix_node + .host + .parse() + .map_err(|err| MixnodeConversionError::InvalidAddress { + value: bond.mix_node.host.clone(), + source: err, + })?; // try to completely resolve the host in the mix situation to avoid doing it every // single time we want to construct a path let mix_host = host .to_socket_addrs(bond.mix_node.mix_port) - .map_err(|err| { - MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) + .map_err(|err| MixnodeConversionError::InvalidAddress { + value: bond.mix_node.host.clone(), + source: err, })?[0]; Ok(Node { diff --git a/common/types/src/error.rs b/common/types/src/error.rs index b0874ba7aa..864a6f6eb2 100644 --- a/common/types/src/error.rs +++ b/common/types/src/error.rs @@ -1,7 +1,7 @@ use serde::{Serialize, Serializer}; use std::io; use thiserror::Error; -use validator_client::validator_api::error::ValidatorAPIError; +use validator_client::nym_api::error::NymAPIError; use validator_client::{nymd::error::NymdError, ValidatorClientError}; // TODO: ask @MS why this even exists @@ -23,9 +23,9 @@ pub enum TypesError { source: eyre::Report, }, #[error("{source}")] - ValidatorApiError { + NymApiError { #[from] - source: ValidatorAPIError, + source: NymAPIError, }, #[error("{source}")] IOError { @@ -52,8 +52,8 @@ pub enum TypesError { #[from] source: cosmwasm_std::DecimalRangeExceeded, }, - #[error("No validator API URL configured")] - NoValidatorApiUrlConfigured, + #[error("No nym API URL configured")] + NoNymApiUrlConfigured, #[error("{0} is not a valid amount string")] InvalidAmount(String), #[error("{0} is not a valid denomination string")] @@ -88,10 +88,10 @@ impl Serialize for TypesError { impl From for TypesError { fn from(e: ValidatorClientError) -> Self { match e { - ValidatorClientError::ValidatorAPIError { source } => source.into(), + ValidatorClientError::NymAPIError { source } => source.into(), ValidatorClientError::MalformedUrlProvided(e) => e.into(), ValidatorClientError::NymdError(e) => e.into(), - ValidatorClientError::NoAPIUrlAvailable => TypesError::NoValidatorApiUrlConfigured, + ValidatorClientError::NoAPIUrlAvailable => TypesError::NoNymApiUrlConfigured, } } } diff --git a/contracts/coconut-bandwidth/src/error.rs b/contracts/coconut-bandwidth/src/error.rs index 7bc8b11d4a..9f4aed6224 100644 --- a/contracts/coconut-bandwidth/src/error.rs +++ b/contracts/coconut-bandwidth/src/error.rs @@ -11,7 +11,7 @@ use thiserror::Error; /// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), #[error("Received multiple coin types")] @@ -29,6 +29,6 @@ pub enum ContractError { #[error("Credential already spent or in process of spending")] DuplicateBlindedSerialNumber, - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), } diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 5394e845af..650cf312f0 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -7,16 +7,16 @@ use crate::dealers::queries::{ use crate::dealers::transactions::try_add_dealer; use crate::dealings::queries::query_dealings_paged; use crate::dealings::transactions::try_commit_dealings; -use crate::epoch_state::queries::{query_current_epoch_state, query_current_epoch_threshold}; -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; +use crate::epoch_state::queries::{query_current_epoch, query_current_epoch_threshold}; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::epoch_state::transactions::advance_epoch_state; use crate::error::ContractError; -use crate::state::{State, ADMIN, MULTISIG, STATE}; +use crate::state::{State, MULTISIG, STATE}; use crate::verification_key_shares::queries::query_vk_shares_paged; use crate::verification_key_shares::transactions::try_commit_verification_key_share; use crate::verification_key_shares::transactions::try_verify_verification_key_share; use coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::{Epoch, EpochState}; use cosmwasm_std::{ entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, }; @@ -30,13 +30,11 @@ use cw4::Cw4Contract; #[entry_point] pub fn instantiate( mut deps: DepsMut<'_>, - _env: Env, + env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - let admin_addr = deps.api.addr_validate(&msg.admin)?; let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?; - ADMIN.set(deps.branch(), Some(admin_addr))?; MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?; let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| { @@ -52,7 +50,10 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; - CURRENT_EPOCH_STATE.save(deps.storage, &EpochState::default())?; + CURRENT_EPOCH.save( + deps.storage, + &Epoch::new(EpochState::default(), env.block.time), + )?; Ok(Response::default()) } @@ -79,14 +80,14 @@ pub fn execute( ExecuteMsg::VerifyVerificationKeyShare { owner } => { try_verify_verification_key_share(deps, info, owner) } - ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, info), + ExecuteMsg::AdvanceEpochState {} => advance_epoch_state(deps, env), } } #[entry_point] pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result { let response = match msg { - QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch_state(deps.storage)?)?, + QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?, QueryMsg::GetCurrentEpochThreshold {} => { to_binary(&query_current_epoch_threshold(deps.storage)?)? } @@ -120,9 +121,8 @@ pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result, mapping: &IndexedDealersMap<'_>, size: usize) { for n in 0..size { @@ -95,8 +94,7 @@ pub(crate) mod tests { #[test] fn dealers_empty_on_init() { - let mut deps = init_contract(); - let env = mock_env(); + let deps = init_contract(); for mapping in [storage::current_dealers(), storage::past_dealers()] { let page1 = query_dealers(deps.as_ref(), None, None, &mapping).unwrap(); @@ -107,8 +105,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); - let owner = Addr::unchecked("owner"); let limit = 2; for mapping in [storage::current_dealers(), storage::past_dealers()] { @@ -124,7 +120,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for mapping in [storage::current_dealers(), storage::past_dealers()] { fill_dealers(deps.as_mut(), &mapping, 1000); @@ -141,7 +136,6 @@ pub(crate) mod tests { #[test] fn dealers_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); // query with a crazily high limit in an attempt to use too many resources let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; @@ -163,7 +157,6 @@ pub(crate) mod tests { #[test] fn dealers_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); let per_page = 2; diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index 6c5ccbb8db..6566581221 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -70,21 +70,21 @@ pub fn try_add_dealer( pub(crate) mod tests { use super::*; use crate::epoch_state::transactions::advance_epoch_state; - use crate::support::tests::fixtures::dealer_details_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; - use cosmwasm_std::testing::mock_info; + use coconut_dkg_common::types::PUBLIC_KEY_SUBMISSION_TIME_SECS; + use cosmwasm_std::testing::{mock_env, mock_info}; #[test] fn invalid_state() { let mut deps = helpers::init_contract(); let owner = Addr::unchecked("owner"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); - let dealer_details = dealer_details_fixture(1); let bte_key_with_proof = String::from("bte_key_with_proof"); let announce_address = String::from("localhost:8000"); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_add_dealer( deps.as_mut(), diff --git a/contracts/coconut-dkg/src/dealings/queries.rs b/contracts/coconut-dkg/src/dealings/queries.rs index 7fe476a458..0bf0452c14 100644 --- a/contracts/coconut-dkg/src/dealings/queries.rs +++ b/contracts/coconut-dkg/src/dealings/queries.rs @@ -50,7 +50,6 @@ pub(crate) mod tests { use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT}; use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::helpers::init_contract; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::{Addr, DepsMut}; fn fill_dealings(deps: DepsMut<'_>, size: usize) { @@ -68,7 +67,6 @@ pub(crate) mod tests { #[test] fn empty_on_bad_idx() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 { @@ -89,7 +87,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; fill_dealings(deps.as_mut(), 1000); @@ -103,7 +100,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); for idx in 0..TOTAL_DEALINGS as u64 { @@ -117,7 +113,6 @@ pub(crate) mod tests { #[test] fn dealings_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1000); // query with a crazily high limit in an attempt to use too many resources @@ -135,7 +130,6 @@ pub(crate) mod tests { #[test] fn dealings_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); fill_dealings(deps.as_mut(), 1); diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 7320768979..fe2e5544cb 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -42,16 +42,16 @@ pub(crate) mod tests { use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::fixtures::dealing_bytes_fixture; use crate::support::tests::helpers; - use crate::support::tests::helpers::ADMIN_ADDRESS; use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::TOTAL_DEALINGS; - use cosmwasm_std::testing::mock_info; + use coconut_dkg_common::types::PUBLIC_KEY_SUBMISSION_TIME_SECS; + use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Addr; #[test] fn invalid_commit_dealing() { let mut deps = helpers::init_contract(); let owner = Addr::unchecked("owner"); + let mut env = mock_env(); let info = mock_info(owner.as_str(), &[]); let dealing_bytes = dealing_bytes_fixture(); @@ -65,7 +65,8 @@ pub(crate) mod tests { } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone()).unwrap_err(); diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index a7027ca488..3a68e0117c 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,15 +1,13 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::error::ContractError; -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use cosmwasm_std::Storage; -pub(crate) fn query_current_epoch_state( - storage: &dyn Storage, -) -> Result { - CURRENT_EPOCH_STATE +pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { + CURRENT_EPOCH .load(storage) .map_err(|_| ContractError::EpochNotInitialised) } @@ -24,12 +22,21 @@ pub(crate) fn query_current_epoch_threshold( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::{EpochState, PUBLIC_KEY_SUBMISSION_TIME_SECS}; + use cosmwasm_std::testing::mock_env; #[test] fn query_state() { let mut deps = init_contract(); - let state = query_current_epoch_state(deps.as_mut().storage).unwrap(); - assert_eq!(state, EpochState::PublicKeySubmission); + let epoch = query_current_epoch(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); + assert_eq!( + epoch.finish_timestamp, + mock_env() + .block + .time + .plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) + ); } #[test] diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index b5c670d899..d5aac5b041 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,8 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::types::EpochState; +use coconut_dkg_common::types::Epoch; use cw_storage_plus::Item; -pub(crate) const CURRENT_EPOCH_STATE: Item<'_, EpochState> = Item::new("current_epoch_state"); +pub(crate) const CURRENT_EPOCH: Item<'_, Epoch> = Item::new("current_epoch"); pub const THRESHOLD: Item = Item::new("threshold"); diff --git a/contracts/coconut-dkg/src/epoch_state/transactions.rs b/contracts/coconut-dkg/src/epoch_state/transactions.rs index fbb597f668..e2e75a1678 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions.rs @@ -2,25 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 use crate::dealers::storage::current_dealers; -use crate::epoch_state::storage::{CURRENT_EPOCH_STATE, THRESHOLD}; +use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; use crate::error::ContractError; -use crate::state::ADMIN; -use coconut_dkg_common::types::EpochState; -use cosmwasm_std::{DepsMut, MessageInfo, Order, Response}; +use coconut_dkg_common::types::{Epoch, EpochState}; +use cosmwasm_std::{DepsMut, Env, Order, Response}; -pub(crate) fn advance_epoch_state( - deps: DepsMut<'_>, - info: MessageInfo, -) -> Result { - ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch_state = - CURRENT_EPOCH_STATE.update::<_, ContractError>(deps.storage, |mut epoch_state| { - // TODO: When defaulting to the first state, some action will probably need to be taken on the - // rest of the contract, as we're starting with a new set of signers - epoch_state = epoch_state.next().unwrap_or_default(); - Ok(epoch_state) - })?; - if current_epoch_state == EpochState::DealingExchange { +pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { + let epoch = CURRENT_EPOCH.load(deps.storage)?; + if epoch.finish_timestamp > env.block.time { + return Err(ContractError::EarlyEpochStateAdvancement( + epoch + .finish_timestamp + .minus_seconds(env.block.time.seconds()) + .seconds(), + )); + } + + let current_epoch = CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |mut epoch| { + // TODO: When defaulting to the first state, some action will probably need to be taken on the + // rest of the contract, as we're starting with a new set of signers + epoch = Epoch::new(epoch.state.next().unwrap_or_default(), env.block.time); + Ok(epoch) + })?; + if current_epoch.state == EpochState::DealingExchange { let current_dealer_count = current_dealers() .keys(deps.storage, None, None, Order::Ascending) .count(); @@ -34,64 +38,147 @@ pub(crate) fn advance_epoch_state( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; - use coconut_dkg_common::types::{DealerDetails, EpochState}; - use cosmwasm_std::testing::mock_info; + use crate::error::ContractError::EarlyEpochStateAdvancement; + use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::{ + DealerDetails, EpochState, DEALING_EXCHANGE_TIME_SECS, IN_PROGRESS_TIME_SECS, + PUBLIC_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_FINALIZATION_TIME_SECS, + VERIFICATION_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_VALIDATION_TIME_SECS, + }; + use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; - use cw_controllers::AdminError; #[test] fn advance_state() { let mut deps = init_contract(); - let info = mock_info("requester", &[]); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let mut env = mock_env(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); assert_eq!( - advance_epoch_state(deps.as_mut(), info).unwrap_err(), - ContractError::Admin(AdminError::NotAdmin {}) + epoch.finish_timestamp, + env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS - 1); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::DealingExchange + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::DealingExchange); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeySubmission + epoch.finish_timestamp, + env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS - 2); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyValidation + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeySubmission); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::VerificationKeyFinalization + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS - 2); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::InProgress + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(2) ); - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeyValidation); assert_eq!( - CURRENT_EPOCH_STATE.load(deps.as_mut().storage).unwrap(), - EpochState::PublicKeySubmission + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS) + ); + + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS - 3); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(3) + ); + + env.block.time = env.block.time.plus_seconds(3); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::VerificationKeyFinalization); + assert_eq!( + epoch.finish_timestamp, + env.block + .time + .plus_seconds(VERIFICATION_KEY_FINALIZATION_TIME_SECS) + ); + + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_FINALIZATION_TIME_SECS - 1); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(1) + ); + + env.block.time = env.block.time.plus_seconds(1); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::InProgress); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds(IN_PROGRESS_TIME_SECS) + ); + + env.block.time = env.block.time.plus_seconds(IN_PROGRESS_TIME_SECS - 100); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(100) + ); + + env.block.time = env.block.time.plus_seconds(50); + assert_eq!( + advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(), + EarlyEpochStateAdvancement(50) + ); + + env.block.time = env.block.time.plus_seconds(100); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + assert_eq!(epoch.state, EpochState::PublicKeySubmission); + assert_eq!( + epoch.finish_timestamp, + env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS) ); } #[test] fn verify_threshold() { let mut deps = init_contract(); - let admin_info = mock_info(ADMIN_ADDRESS, &[]); + let mut env = mock_env(); assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); @@ -111,7 +198,8 @@ pub(crate) mod tests { .unwrap(); } - advance_epoch_state(deps.as_mut(), admin_info.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); assert_eq!( THRESHOLD.may_load(deps.as_mut().storage).unwrap().unwrap(), 67 diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index b944ad361d..280436ee2e 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::CURRENT_EPOCH_STATE; +use crate::epoch_state::storage::CURRENT_EPOCH; use crate::error::ContractError; use coconut_dkg_common::types::EpochState; use cosmwasm_std::Storage; @@ -10,7 +10,7 @@ pub(crate) fn check_epoch_state( storage: &dyn Storage, against: EpochState, ) -> Result<(), ContractError> { - let epoch_state = CURRENT_EPOCH_STATE.load(storage)?; + let epoch_state = CURRENT_EPOCH.load(storage)?.state; if epoch_state != against { Err(ContractError::IncorrectEpochState { current_state: epoch_state.to_string(), @@ -25,14 +25,20 @@ pub(crate) fn check_epoch_state( pub(crate) mod test { use super::*; use crate::support::tests::helpers::init_contract; + use coconut_dkg_common::types::Epoch; + use cosmwasm_std::testing::mock_env; #[test] pub fn check_state() { let mut deps = init_contract(); + let env = mock_env(); for fixed_state in EpochState::default().all_until(EpochState::InProgress) { - CURRENT_EPOCH_STATE - .save(deps.as_mut().storage, &fixed_state) + CURRENT_EPOCH + .save( + deps.as_mut().storage, + &Epoch::new(fixed_state, env.block.time), + ) .unwrap(); for against_state in EpochState::default().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index 089a8b9f27..0baeae5edd 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -8,10 +8,10 @@ use thiserror::Error; /// Custom errors for contract failure conditions. #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), #[error("Group contract invalid address '{addr}'")] @@ -23,6 +23,9 @@ pub enum ContractError { #[error("This sender is already a dealer for the epoch")] AlreadyADealer, + #[error("Too soon to advance epoch state. {0} more seconds until it can be advanced")] + EarlyEpochStateAdvancement(u64), + #[error("Epoch hasn't been correctly initialised!")] EpochNotInitialised, diff --git a/contracts/coconut-dkg/src/state/mod.rs b/contracts/coconut-dkg/src/state/mod.rs index 729618ec60..533bd98a0a 100644 --- a/contracts/coconut-dkg/src/state/mod.rs +++ b/contracts/coconut-dkg/src/state/mod.rs @@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize}; // unique items pub const STATE: Item = Item::new("state"); -pub const ADMIN: Admin = Admin::new("admin"); pub const MULTISIG: Admin = Admin::new("multisig"); #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index e0bdee4e4d..19aad555d1 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use coconut_dkg_common::dealer::{ContractDealing, DealerDetails}; +use coconut_dkg_common::dealer::DealerDetails; use coconut_dkg_common::types::ContractSafeBytes; use coconut_dkg_common::verification_key::ContractVKShare; use cosmwasm_std::Addr; diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index ec46ff0215..b59ee40bcf 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -17,7 +17,6 @@ pub fn init_contract() -> OwnedDeps> let msg = InstantiateMsg { group_addr: String::from(GROUP_CONTRACT), multisig_addr: String::from(MULTISIG_CONTRACT), - admin: String::from(ADMIN_ADDRESS), mix_denom: TEST_MIX_DENOM.to_string(), }; let env = mock_env(); diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e9c4d00dca..331c42e58a 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -45,7 +45,6 @@ pub(crate) mod tests { use crate::verification_key_shares::storage::{ VERIFICATION_KEY_SHARES_PAGE_DEFAULT_LIMIT, VERIFICATION_KEY_SHARES_PAGE_MAX_LIMIT, }; - use cosmwasm_std::testing::mock_env; use cosmwasm_std::Addr; #[test] @@ -58,7 +57,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_obeys_limits() { let mut deps = init_contract(); - let env = mock_env(); let limit = 2; for n in 0..1000 { let vk_share = vk_share_fixture(n); @@ -75,7 +73,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_has_default_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { let vk_share = vk_share_fixture(n); let sender = Addr::unchecked(format!("owner{}", n)); @@ -96,7 +93,6 @@ pub(crate) mod tests { #[test] fn vk_shares_paged_retrieval_has_max_limit() { let mut deps = init_contract(); - let env = mock_env(); for n in 0..1000 { let vk_share = vk_share_fixture(n); let sender = Addr::unchecked(format!("owner{}", n)); @@ -117,7 +113,6 @@ pub(crate) mod tests { #[test] fn vk_shares_pagination_works() { let mut deps = init_contract(); - let env = mock_env(); let vk_share = vk_share_fixture(1); let sender = Addr::unchecked(format!("owner{}", 1)); diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index 0f76fc7811..69df1bad7b 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -73,20 +73,21 @@ pub fn try_verify_verification_key_share( #[cfg(test)] mod tests { use super::*; - use crate::epoch_state::storage::CURRENT_EPOCH_STATE; use crate::epoch_state::transactions::advance_epoch_state; use crate::support::tests::helpers; - use crate::support::tests::helpers::{ADMIN_ADDRESS, MULTISIG_CONTRACT}; + use crate::support::tests::helpers::MULTISIG_CONTRACT; use coconut_dkg_common::dealer::DealerDetails; - use coconut_dkg_common::types::EpochState; + use coconut_dkg_common::types::{ + EpochState, DEALING_EXCHANGE_TIME_SECS, PUBLIC_KEY_SUBMISSION_TIME_SECS, + VERIFICATION_KEY_SUBMISSION_TIME_SECS, VERIFICATION_KEY_VALIDATION_TIME_SECS, + }; use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::Storage; use cw_controllers::AdminError; #[test] fn commit_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let share = "share".to_string(); @@ -104,8 +105,10 @@ mod tests { expected_state: EpochState::VerificationKeySubmission.to_string() } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_commit_verification_key_share( deps.as_mut(), env.clone(), @@ -147,6 +150,7 @@ mod tests { #[test] fn invalid_verify_vk_share() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let info = mock_info("requester", &[]); let owner = Addr::unchecked("owner"); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); @@ -161,10 +165,20 @@ mod tests { } ); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone()).unwrap_err(); @@ -183,14 +197,16 @@ mod tests { #[test] fn verify_vk_share() { let mut deps = helpers::init_contract(); - let env = mock_env(); + let mut env = mock_env(); let owner = Addr::unchecked("owner"); let info = mock_info(owner.as_ref(), &[]); let share = "share".to_string(); let multisig_info = mock_info(MULTISIG_CONTRACT, &[]); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env.block.time.plus_seconds(PUBLIC_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env.block.time.plus_seconds(DEALING_EXCHANGE_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let dealer_details = DealerDetails { address: owner.clone(), @@ -204,8 +220,16 @@ mod tests { try_commit_verification_key_share(deps.as_mut(), env.clone(), info.clone(), share.clone()) .unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); - advance_epoch_state(deps.as_mut(), mock_info(ADMIN_ADDRESS, &[])).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_SUBMISSION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); + env.block.time = env + .block + .time + .plus_seconds(VERIFICATION_KEY_VALIDATION_TIME_SECS); + advance_epoch_state(deps.as_mut(), env).unwrap(); try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone()).unwrap(); } diff --git a/contracts/coconut-test/src/submit_vk_creates_proposal.rs b/contracts/coconut-test/src/submit_vk_creates_proposal.rs index 15bbd65341..1122115b6d 100644 --- a/contracts/coconut-test/src/submit_vk_creates_proposal.rs +++ b/contracts/coconut-test/src/submit_vk_creates_proposal.rs @@ -71,7 +71,6 @@ fn dkg_proposal() { let msg = DkgInstantiateMsg { group_addr: group_contract_addr.to_string(), multisig_addr: multisig_contract_addr.to_string(), - admin: Addr::unchecked(OWNER).to_string(), mix_denom: TEST_COIN_DENOM.to_string(), }; let coconut_dkg_contract_addr = app @@ -109,6 +108,7 @@ fn dkg_proposal() { .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), @@ -175,6 +175,7 @@ fn dkg_proposal() { .unwrap(); for _ in 0..2 { + app.update_block(|block| block.time = block.time.plus_seconds(1000)); app.execute_contract( Addr::unchecked(OWNER), coconut_dkg_contract_addr.clone(), diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index e25e4c1bd8..d1552bb50b 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -408,7 +408,7 @@ pub mod tests { let env = test.env(); // note: we don't have to test for cases where `is_unbonding` is set to true on a mixnode - // since before performing the validator-api should clear out the event queue + // since before performing the nym-api should clear out the event queue // manually adjust delegation info as to indicate the rewarding information shouldnt get removed let mut rewarding_details = storage::MIXNODE_REWARDING diff --git a/contracts/multisig/cw3-flex-multisig/src/error.rs b/contracts/multisig/cw3-flex-multisig/src/error.rs index f7addae37b..75a546ca0e 100644 --- a/contracts/multisig/cw3-flex-multisig/src/error.rs +++ b/contracts/multisig/cw3-flex-multisig/src/error.rs @@ -5,10 +5,10 @@ use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Threshold(#[from] ThresholdError), #[error("Group contract invalid address '{addr}'")] diff --git a/contracts/multisig/cw4-group/src/error.rs b/contracts/multisig/cw4-group/src/error.rs index 82a84fe833..8c88fb2255 100644 --- a/contracts/multisig/cw4-group/src/error.rs +++ b/contracts/multisig/cw4-group/src/error.rs @@ -5,13 +5,13 @@ use cw_controllers::{AdminError, HookError}; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("{0}")] + #[error(transparent)] Hook(#[from] HookError), - #[error("{0}")] + #[error(transparent)] Admin(#[from] AdminError), #[error("Unauthorized")] diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index cc665b1f83..514bfb4852 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -25,6 +25,9 @@ thiserror = "1.0.29" tokio = {version = "1.21.2", features = ["full"] } maxminddb = "0.23.0" dotenv = "0.15.0" +rand = "0.8.5" +rand_seeder = "0.2.3" +rand_pcg = "0.3.1" mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" } diff --git a/explorer-api/src/client.rs b/explorer-api/src/client.rs index 129a394d31..717a0800e5 100644 --- a/explorer-api/src/client.rs +++ b/explorer-api/src/client.rs @@ -21,7 +21,7 @@ impl ThreadsafeValidatorClient { } pub(crate) fn api_endpoint(&self) -> &Url { - self.0.validator_api.current_url() + self.0.nym_api.current_url() } } diff --git a/explorer-api/src/country_statistics/distribution.rs b/explorer-api/src/country_statistics/distribution.rs index f48e87eebb..93d12067b6 100644 --- a/explorer-api/src/country_statistics/distribution.rs +++ b/explorer-api/src/country_statistics/distribution.rs @@ -1,5 +1,5 @@ use log::info; -use task::ShutdownListener; +use task::TaskClient; use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution; use crate::COUNTRY_DATA_REFRESH_INTERVAL; @@ -8,11 +8,11 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct CountryStatisticsDistributionTask { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl CountryStatisticsDistributionTask { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { CountryStatisticsDistributionTask { state, shutdown } } diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index c77a61a9a1..0ae79985c7 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -4,15 +4,15 @@ use crate::mix_nodes::location::Location; use crate::state::ExplorerApiStateContext; use log::{info, warn}; -use task::ShutdownListener; +use task::TaskClient; pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl GeoLocateTask { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { GeoLocateTask { state, shutdown } } diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 47dddf9f54..e1a02c57ea 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -8,7 +8,7 @@ use dotenv::dotenv; use log::info; use logging::setup_logging; use network_defaults::setup_env; -use task::ShutdownNotifier; +use task::TaskManager; mod buy_terms; pub(crate) mod cache; @@ -54,10 +54,10 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); - let validator_api_url = self.state.inner.validator_client.api_endpoint(); - info!("Using validator API - {}", validator_api_url); + let nym_api_url = self.state.inner.validator_client.api_endpoint(); + info!("Using validator API - {}", nym_api_url); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); // spawn concurrent tasks crate::tasks::ExplorerApiTasks::new(self.state.clone(), shutdown.subscribe()).start(); @@ -78,7 +78,7 @@ impl ExplorerApi { self.wait_for_interrupt(shutdown).await } - async fn wait_for_interrupt(&self, mut shutdown: ShutdownNotifier) { + async fn wait_for_interrupt(&self, mut shutdown: TaskManager) { wait_for_signal().await; log::info!("Sending shutdown"); diff --git a/explorer-api/src/mix_node/econ_stats.rs b/explorer-api/src/mix_node/econ_stats.rs index cde29d3a86..30d89a29b2 100644 --- a/explorer-api/src/mix_node/econ_stats.rs +++ b/explorer-api/src/mix_node/econ_stats.rs @@ -13,31 +13,26 @@ pub(crate) async fn retrieve_mixnode_econ_stats( ) -> Option { let stake_saturation = client .0 - .validator_api + .nym_api .get_mixnode_stake_saturation(mix_id) .await .ok()?; let inclusion_probability = client .0 - .validator_api + .nym_api .get_mixnode_inclusion_probability(mix_id) .await .ok()?; let reward_estimation = client .0 - .validator_api + .nym_api .get_mixnode_reward_estimation(mix_id) .await .ok()?; - let uptime_response = client - .0 - .validator_api - .get_mixnode_avg_uptime(mix_id) - .await - .ok()?; + let uptime_response = client.0.nym_api.get_mixnode_avg_uptime(mix_id).await.ok()?; Some(EconomicDynamicsStats { stake_saturation: best_effort_small_dec_to_f64(stake_saturation.saturation) as f32, diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index ef894e4b40..92f4dbe8aa 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -40,6 +40,7 @@ pub(crate) struct PrettyDetailedMixNodeBond { pub estimated_delegators_apy: f64, pub operating_cost: Coin, pub profit_margin_percent: Percent, + pub family_id: Option, } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 82c2aedd1f..e2aced241a 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -6,6 +6,7 @@ use std::time::Duration; pub(crate) mod http; pub(crate) mod location; pub(crate) mod models; +pub(crate) mod utils; pub(crate) const CACHE_REFRESH_RATE: Duration = Duration::from_secs(30); pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(60); diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs index 8dc9c96ac3..fdbd99e467 100644 --- a/explorer-api/src/mix_nodes/models.rs +++ b/explorer-api/src/mix_nodes/models.rs @@ -13,6 +13,7 @@ use tokio::sync::{RwLock, RwLockReadGuard}; use crate::helpers::best_effort_small_dec_to_f64; use validator_client::models::MixNodeBondAnnotated; +use super::utils::family_numerical_id; use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; use crate::mix_nodes::CACHE_ENTRY_TTL; @@ -140,6 +141,8 @@ impl ThreadsafeMixNodesCache { let denom = &node.mixnode_details.original_pledge().denom; let rewarding_info = &node.mixnode_details.rewarding_details; + let family_id = node.family.as_ref().map(family_numerical_id); + PrettyDetailedMixNodeBond { mix_id, location: location.and_then(|l| l.location.clone()), @@ -157,6 +160,7 @@ impl ThreadsafeMixNodesCache { estimated_delegators_apy: best_effort_small_dec_to_f64(node.estimated_delegators_apy), operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(), profit_margin_percent: rewarding_info.cost_params.profit_margin_percent, + family_id, } } diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index a151ec318a..e1040dda62 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,9 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use mixnet_contract_common::families::FamilyHead; +use rand::Rng; +use rand_pcg::Pcg64; +use rand_seeder::Seeder; + use crate::mix_nodes::location::GeoLocation; use isocountry::CountryCode; +#[allow(dead_code)] pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { match CountryCode::for_alpha2(&geo.country_code) { Ok(three_letter_country_code) => three_letter_country_code.alpha3().to_string(), @@ -16,3 +22,10 @@ pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String } } } + +// We don't need numerical IDs anywhere, so to avoid modifying the contract storage again and +// since this is for explorer ergonomics, it will generate a deterministic random u16 based on the family Identity. +pub(crate) fn family_numerical_id(fh: &FamilyHead) -> u16 { + let mut rng: Pcg64 = Seeder::from(fh.identity()).make_rng(); + rng.gen() +} diff --git a/explorer-api/src/tasks.rs b/explorer-api/src/tasks.rs index 08d43fb805..b7e72f72d2 100644 --- a/explorer-api/src/tasks.rs +++ b/explorer-api/src/tasks.rs @@ -4,7 +4,7 @@ use std::future::Future; use mixnet_contract_common::GatewayBond; -use task::ShutdownListener; +use task::TaskClient; use validator_client::models::MixNodeBondAnnotated; use validator_client::nymd::error::NymdError; use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse}; @@ -15,11 +15,11 @@ use crate::state::ExplorerApiStateContext; pub(crate) struct ExplorerApiTasks { state: ExplorerApiStateContext, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl ExplorerApiTasks { - pub(crate) fn new(state: ExplorerApiStateContext, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(state: ExplorerApiStateContext, shutdown: TaskClient) -> Self { ExplorerApiTasks { state, shutdown } } @@ -31,8 +31,8 @@ impl ExplorerApiTasks { { let bonds = match f(&self.state.inner.validator_client.0).await { Ok(result) => result, - Err(e) => { - error!("Unable to retrieve mixnode bonds: {:?}", e); + Err(err) => { + error!("Unable to retrieve mixnode bonds: {err}"); vec![] } }; @@ -115,8 +115,8 @@ impl ExplorerApiTasks { async fn update_validators_cache(&self) { match self.retrieve_all_validators().await { Ok(response) => self.state.inner.validators.update_cache(response).await, - Err(e) => { - error!("Failed to get validators: {:?}", e) + Err(err) => { + error!("Failed to get validators: {err}") } } } @@ -124,8 +124,8 @@ impl ExplorerApiTasks { async fn update_gateways_cache(&self) { match self.retrieve_all_gateways().await { Ok(response) => self.state.inner.gateways.update_cache(response).await, - Err(e) => { - error!("Failed to get gateways: {:?}", e) + Err(err) => { + error!("Failed to get gateways: {err}") } } } diff --git a/explorer/.env.prod b/explorer/.env.prod index b1b1fe0d4c..70bd50c7ca 100644 --- a/explorer/.env.prod +++ b/explorer/.env.prod @@ -1,5 +1,5 @@ EXPLORER_API_URL=https://explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://validator.nymtech.net +NYM_API_URL=https://validator.nymtech.net VALIDATOR_URL=https://rpc.nymtech.net BIG_DIPPER_URL=https://blocks.nymtech.net CURRENCY_DENOM=unym diff --git a/explorer/.env.qa b/explorer/.env.qa index 60b30e1bd1..dd7052ce4d 100644 --- a/explorer/.env.qa +++ b/explorer/.env.qa @@ -1,5 +1,5 @@ EXPLORER_API_URL=https://qa-explorer.nymtech.net/api/v1 -VALIDATOR_API_URL=https://qa-validator-api.nymtech.net +NYM_API_URL=https://qa-validator-api.nymtech.net VALIDATOR_URL=https://qa-validator.nymtech.net BIG_DIPPER_URL=https://qa-blocks.nymtech.net CURRENCY_DENOM=unym diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index fb7216b64f..dc6124c53d 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -1,6 +1,6 @@ // master APIs export const API_BASE_URL = process.env.EXPLORER_API_URL; -export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL; +export const NYM_API_BASE_URL = process.env.NYM_API_URL; export const VALIDATOR_BASE_URL = process.env.VALIDATOR_URL; export const BIG_DIPPER = process.env.BIG_DIPPER_URL; @@ -9,12 +9,12 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`; export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; -export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`; +export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/gateways`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; -export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`; +export const BLOCK_API = `${NYM_API_BASE_URL}/block`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; -export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. -export const UPTIME_STORY_API_GATEWAY = `${VALIDATOR_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this. +export const UPTIME_STORY_API = `${NYM_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this. +export const UPTIME_STORY_API_GATEWAY = `${NYM_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this. // errors export const MIXNODE_API_ERROR = "We're having trouble finding that record, please try again or Contact Us."; diff --git a/explorer/src/components/TableToolbar.tsx b/explorer/src/components/TableToolbar.tsx index 7452b4707a..7426433868 100644 --- a/explorer/src/components/TableToolbar.tsx +++ b/explorer/src/components/TableToolbar.tsx @@ -4,6 +4,8 @@ import Select, { SelectChangeEvent } from '@mui/material/Select'; import { Filters } from './Filters/Filters'; import { useIsMobile } from '../hooks/useIsMobile'; +const fieldsHeight = '42.25px'; + type TableToolBarProps = { onChangeSearch: (arg: string) => void; onChangePageSize: (event: SelectChangeEvent) => void; @@ -35,7 +37,7 @@ export const TableToolbar: React.FC = ({ }} > - + {childrenBefore}