diff --git a/.env.sample-dev b/.env.sample-dev index d77245ab12..3a48d8ebb0 100644 --- a/.env.sample-dev +++ b/.env.sample-dev @@ -14,7 +14,7 @@ GEOIPUPDATE_LICENSE_KEY=xxx # List of space-separated database edition IDs. Edition IDs may # consist of letters, digits, and dashes. For example, GeoIP2-City # would download the GeoIP2 City database (GeoIP2-City). -GEOIPUPDATE_EDITION_IDS=GeoLite2-Country +GEOIPUPDATE_EDITION_IDS=GeoLite2-City # The number of hours between geoipupdate runs. If this is not set # or is set to 0, geoipupdate will run once and exit. GEOIPUPDATE_FREQUENCY=72 diff --git a/CHANGELOG.md b/CHANGELOG.md index a9f160260d..0a3b87a801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,35 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +### Added + +### Changed + +## [v1.1.5] (2023-01-10) + +### Added + +- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715 + +### Changed + +- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686 +- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695 +- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670 +- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668 +- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863 +- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693 +- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700 +- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730 +- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723 + ## [v1.1.4] (2022-12-20) -This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies. -At the moment this is turned off by default, but available for use by application developers. -We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients. +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 +55,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 +75,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 +111,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 +137,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 +165,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 +218,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 +312,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 +323,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 +363,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 +399,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 +414,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 +424,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 +480,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 +496,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 +514,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 +530,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 +554,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 +564,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 +583,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 +620,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..ff29861fb7 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]] @@ -586,7 +660,11 @@ dependencies = [ [[package]] name = "client-core" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "async-trait", "client-connections", @@ -967,9 +1045,9 @@ dependencies = [ "coconut-interface", "cosmrs", "crypto", + "nym-api-requests", "rand 0.7.3", "thiserror", - "validator-api-requests", "validator-client", ] @@ -980,9 +1058,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 +1077,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 +1230,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", + "thiserror", "x25519-dalek", ] @@ -1133,11 +1248,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 +1581,7 @@ dependencies = [ "bitvec", "bls12_381 0.6.0", "bs58", - "criterion", + "criterion 0.3.5", "ff 0.11.0", "group 0.11.0", "lazy_static", @@ -1650,7 +1766,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.1" +version = "1.1.2" dependencies = [ "chrono", "clap 3.2.8", @@ -1697,9 +1813,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 +3050,7 @@ dependencies = [ "rand 0.8.5", "serde", "task", + "thiserror", "tokio", "tokio-util 0.7.3", "url", @@ -3089,6 +3206,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" @@ -3106,7 +3238,11 @@ dependencies = [ [[package]] name = "nym-cli" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "anyhow", "base64", @@ -3160,7 +3296,11 @@ dependencies = [ [[package]] name = "nym-client" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "clap 3.2.8", "client-connections", @@ -3199,7 +3339,11 @@ dependencies = [ [[package]] name = "nym-gateway" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "anyhow", "async-trait", @@ -3223,6 +3367,7 @@ dependencies = [ "mixnet-client", "mixnode-common", "network-defaults", + "nym-api-requests", "nymsphinx", "once_cell", "pemstore", @@ -3238,7 +3383,6 @@ dependencies = [ "tokio-tungstenite 0.14.0", "tokio-util 0.7.3", "url", - "validator-api-requests", "validator-client", "vergen 5.1.17", "version-checker", @@ -3246,7 +3390,11 @@ dependencies = [ [[package]] name = "nym-mixnode" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "anyhow", "bs58", @@ -3288,7 +3436,11 @@ dependencies = [ [[package]] name = "nym-network-requester" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "async-trait", "clap 3.2.8", @@ -3320,7 +3472,11 @@ dependencies = [ [[package]] name = "nym-network-statistics" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "dirs", "log", @@ -3334,9 +3490,29 @@ 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" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "clap 3.2.8", "client-connections", @@ -3403,7 +3579,11 @@ dependencies = [ [[package]] name = "nym-validator-api" +<<<<<<< HEAD version = "1.1.4" +======= +version = "1.1.5" +>>>>>>> 96444509d0f983f557c7663f0ffc615dcf053487 dependencies = [ "anyhow", "async-trait", @@ -3434,6 +3614,7 @@ dependencies = [ "logging", "mixnet-contract-common", "multisig-contract-common", + "nym-api-requests", "nymcoconut", "nymsphinx", "okapi", @@ -3459,7 +3640,6 @@ dependencies = [ "topology", "ts-rs", "url", - "validator-api-requests", "validator-client", "vergen 7.2.1", "version-checker", @@ -3492,7 +3672,7 @@ dependencies = [ "bincode", "bls12_381 0.6.0", "bs58", - "criterion", + "criterion 0.3.5", "digest 0.9.0", "dkg", "doc-comment", @@ -3541,6 +3721,7 @@ dependencies = [ "nymsphinx-types", "pemstore", "rand 0.7.3", + "thiserror", "topology", ] @@ -3615,6 +3796,7 @@ dependencies = [ "bytes", "nymsphinx-params", "nymsphinx-types", + "thiserror", "tokio-util 0.7.3", ] @@ -3624,6 +3806,7 @@ version = "0.1.0" dependencies = [ "crypto", "nymsphinx-types", + "thiserror", ] [[package]] @@ -3707,6 +3890,7 @@ name = "ordered-buffer" version = "0.1.0" dependencies = [ "log", + "thiserror", ] [[package]] @@ -4000,6 +4184,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 +4204,7 @@ dependencies = [ "cfg-if 1.0.0", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] @@ -6189,10 +6384,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 +6562,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 +6603,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 +6627,7 @@ dependencies = [ "mixnet-contract-common", "multisig-contract-common", "network-defaults", + "nym-api-requests", "prost 0.10.3", "reqwest", "serde", @@ -6446,7 +6637,6 @@ dependencies = [ "tokio", "ts-rs", "url", - "validator-api-requests", "vesting-contract", "vesting-contract-common", ] @@ -6905,9 +7095,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 8f5aba2678..883be67453 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..507552e887 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "client-core" -version = "1.1.4" +version = "1.1.5" 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 d881f639c3..13ca0fabb6 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 @@ -72,7 +72,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() { @@ -84,7 +84,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 c70f8f9b89..9bf2415d43 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, }; @@ -32,22 +34,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 { @@ -138,7 +125,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 @@ -165,6 +152,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, @@ -210,7 +201,7 @@ where real_receiver, rng, topology_access, - transmission_buffer: Default::default(), + transmission_buffer: TransmissionBuffer::new(), client_connection_rx, lane_queue_lengths, } @@ -300,7 +291,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 @@ -364,7 +355,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); @@ -505,7 +498,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(); @@ -550,7 +543,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"))] @@ -561,7 +554,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 5982e40b2c..1c7e963d09 100644 --- a/clients/client-core/src/client/received_buffer.rs +++ b/clients/client-core/src/client/received_buffer.rs @@ -407,21 +407,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; } }, } @@ -447,20 +446,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"); } } @@ -498,7 +496,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 1c6b9e7643..7ea78eac7b 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 08613614b8..33c1cece86 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/Cargo.toml b/clients/native/Cargo.toml index d97f7748fb..e669026fa2 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.4" +version = "1.1.5" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" 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 c9af755052..ececa9bd88 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/Cargo.toml b/clients/socks5/Cargo.toml index add5a3ee58..9d1762c8f3 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.4" +version = "1.1.5" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" 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 7f50bea28c..b11937ccff 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 29482089bf..8596296edb 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}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -165,7 +165,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 { @@ -191,7 +191,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(); @@ -221,7 +221,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 0c7b5f99da..55a1b7d11a 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 79% 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 3cb7ae2ce9..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,22 +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 nym_api_requests::coconut::{ + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; +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; -use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, -}; -use validator_api_requests::models::{ - GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, - InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, - MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, - RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, -}; pub mod error; pub mod routes; @@ -53,7 +54,7 @@ impl Client { &self, path: PathSegments<'_>, params: Params<'_, K, V>, - ) -> Result + ) -> Result where K: AsRef, V: AsRef, @@ -62,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, @@ -76,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, @@ -97,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>, @@ -121,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, @@ -147,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, ) @@ -162,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, @@ -176,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, ) @@ -187,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, @@ -204,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, @@ -221,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, @@ -238,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, @@ -254,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, @@ -272,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, @@ -286,7 +283,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -303,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, @@ -317,7 +314,7 @@ impl Client { ) .await } else { - self.query_validator_api( + self.query_nym_api( &[ routes::API_VERSION, routes::STATUS_ROUTES, @@ -334,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, @@ -351,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, @@ -365,11 +362,30 @@ impl Client { .await } + pub async fn compute_mixnode_reward_estimation( + &self, + mix_id: MixId, + request_body: &ComputeRewardEstParam, + ) -> Result { + self.post_nym_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + &mix_id.to_string(), + routes::COMPUTE_REWARD_ESTIMATION, + ], + NO_PARAMS, + request_body, + ) + .await + } + 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, @@ -385,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, @@ -402,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, @@ -419,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, @@ -436,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, @@ -453,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 87% 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 66a822b4fd..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"; @@ -28,6 +28,7 @@ pub const STATUS: &str = "status"; pub const REPORT: &str = "report"; pub const HISTORY: &str = "history"; pub const REWARD_ESTIMATION: &str = "reward-estimation"; +pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation"; pub const AVG_UPTIME: &str = "avg_uptime"; pub const STAKE_SATURATION: &str = "stake-saturation"; pub const INCLUSION_CHANCE: &str = "inclusion-probability"; 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/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 6cd9178a4a..703c4a6184 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 8374d94bbe..f7264699c4 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; @@ -34,8 +35,12 @@ const EXTENDED_PACKET_SIZE_500: usize = HEADER_SIZE + PAYLOAD_OVERHEAD_SIZE + 50 #[derive(Debug)] pub struct InvalidPacketSize; -#[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)] @@ -96,7 +101,7 @@ 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), @@ -147,7 +152,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 { @@ -179,7 +184,7 @@ impl PacketSize { } else if PacketSize::ExtendedPacket500.size() == size { Ok(PacketSize::ExtendedPacket500) } 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/.env.sample b/explorer-api/.env.sample index 1efd2de597..e5922f9cf0 100644 --- a/explorer-api/.env.sample +++ b/explorer-api/.env.sample @@ -1,2 +1,2 @@ # The path to the geoip database file -GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb +GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index cc665b1f83..b03926dfe6 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.1" +version = "1.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/explorer-api/README.md b/explorer-api/README.md index d4e193dbb0..86e64e4c12 100644 --- a/explorer-api/README.md +++ b/explorer-api/README.md @@ -40,7 +40,7 @@ It should be previously installed thanks to `geoipupdate` service. For example: ```shell -GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb cargo run +GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run ``` Note: explorer-api binary reads the provided `.env` file. diff --git a/explorer-api/src/buy_terms/http.rs b/explorer-api/src/buy_terms/http.rs deleted file mode 100644 index 5594a1a3be..0000000000 --- a/explorer-api/src/buy_terms/http.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::geo_ip::location::Location; -use crate::state::ExplorerApiStateContext; -use rocket::response::status; -use rocket::serde::json::Json; -use rocket::{Route, State}; -use rocket_okapi::okapi::openapi3::OpenApi; -use rocket_okapi::settings::OpenApiSettings; - -pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { - openapi_get_routes_spec![settings: terms] -} - -#[openapi(tag = "terms")] -#[get("/")] -pub(crate) async fn terms( - _state: &State, - location: Location, -) -> Result, status::Forbidden> { - if location.iso_alpha2 == "US" { - return Err(status::Forbidden(Some("US government sucks".to_string()))); - } - Ok(Json("Nym Terms & Conditions: Welcome".to_string())) -} diff --git a/explorer-api/src/buy_terms/mod.rs b/explorer-api/src/buy_terms/mod.rs deleted file mode 100644 index d064c8bd6f..0000000000 --- a/explorer-api/src/buy_terms/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod http; 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/geo_ip/location.rs b/explorer-api/src/geo_ip/location.rs index a1afec55d6..adbdb6cef3 100644 --- a/explorer-api/src/geo_ip/location.rs +++ b/explorer-api/src/geo_ip/location.rs @@ -3,14 +3,14 @@ use isocountry::CountryCode; use log::warn; -use maxminddb::{geoip2::Country, MaxMindDBError, Reader}; +use maxminddb::{geoip2::City, MaxMindDBError, Reader}; use std::{ net::{IpAddr, ToSocketAddrs}, str::FromStr, sync::Arc, }; -const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-Country.mmdb"; +const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-City.mmdb"; const FAKE_PORT: u16 = 1234; #[derive(Debug)] @@ -38,6 +38,8 @@ pub(crate) struct Location { pub(crate) iso_alpha3: String, /// English country short name (ISO 3166-1) pub(crate) name: String, + pub(crate) latitude: Option, + pub(crate) longitude: Option, } impl GeoIp { @@ -86,7 +88,7 @@ impl GeoIp { error!("No registered GeoIP database"); GeoIpError::InternalError })? - .lookup::(ip); + .lookup::(ip); match &result { Ok(v) => Ok(Some( Location::try_from(v).map_err(|_| GeoIpError::InternalError)?, @@ -99,11 +101,11 @@ impl GeoIp { } } -impl<'a> TryFrom<&Country<'a>> for Location { +impl<'a> TryFrom<&City<'a>> for Location { type Error = String; - fn try_from(country: &Country) -> Result { - let data = country.country.as_ref().ok_or_else(|| { + fn try_from(city: &City) -> Result { + let data = city.country.as_ref().ok_or_else(|| { warn!("No Country data found"); "No Country data found" })?; @@ -119,10 +121,13 @@ impl<'a> TryFrom<&Country<'a>> for Location { warn!("{}", &message); message })?; + Ok(Location { iso_alpha2, iso_alpha3: String::from(iso_codes.alpha3()), name: String::from(iso_codes.name()), + latitude: city.location.as_ref().and_then(|l| l.latitude), + longitude: city.location.as_ref().and_then(|l| l.longitude), }) } } diff --git a/explorer-api/src/http/mod.rs b/explorer-api/src/http/mod.rs index f5559124a5..31f638ee5f 100644 --- a/explorer-api/src/http/mod.rs +++ b/explorer-api/src/http/mod.rs @@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_okapi::swagger_ui::make_swagger_ui; -use crate::buy_terms::http::nym_terms_make_default_routes; use crate::country_statistics::http::country_statistics_make_default_routes; use crate::gateways::http::gateways_make_default_routes; use crate::http::swagger::get_docs; @@ -57,7 +56,6 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket { "/overview" => overview_make_default_routes(&openapi_settings), "/ping" => ping_make_default_routes(&openapi_settings), "/validators" => validators_make_default_routes(&openapi_settings), - "/terms" => nym_terms_make_default_routes(&openapi_settings), }; building_rocket diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 47dddf9f54..81478638d4 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -8,9 +8,8 @@ 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; mod client; pub(crate) mod commands; @@ -54,10 +53,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 +77,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_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs index d14eaf771e..51c46261c6 100644 --- a/explorer-api/src/mix_nodes/location.rs +++ b/explorer-api/src/mix_nodes/location.rs @@ -36,6 +36,8 @@ pub(crate) struct Location { pub(crate) two_letter_iso_country_code: String, pub(crate) three_letter_iso_country_code: String, pub(crate) country_name: String, + pub(crate) latitude: Option, + pub(crate) longitude: Option, } impl Location { @@ -44,6 +46,8 @@ impl Location { country_name: location.name, two_letter_iso_country_code: location.iso_alpha2, three_letter_iso_country_code: location.iso_alpha3, + latitude: location.latitude, + longitude: location.longitude, } } } 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/CHANGELOG.md b/explorer/CHANGELOG.md new file mode 100644 index 0000000000..e55425407b --- /dev/null +++ b/explorer/CHANGELOG.md @@ -0,0 +1,8 @@ +## UNRELEASED + +## [nym-explorer-v1.0.1](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.1) (2023-01-10) + +- Feat/2161 ne gate version by @gala1234 in https://github.com/nymtech/nym/pull/2743 +- fix(explorer): set gateway bond 6 decimals by @doums in https://github.com/nymtech/nym/pull/2741 +- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742 +- fix(explorer,explorer-api): mixnode location by @doums in https://github.com/nymtech/nym-api diff --git a/explorer/package.json b/explorer/package.json index b314878308..2478774987 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@nym/network-explorer", - "version": "1.0.0", + "version": "1.0.1", "private": true, "license": "Apache-2.0", "dependencies": { 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/DetailTable.tsx b/explorer/src/components/DetailTable.tsx index 1aab097a05..75aaae36ab 100644 --- a/explorer/src/components/DetailTable.tsx +++ b/explorer/src/components/DetailTable.tsx @@ -5,7 +5,7 @@ import { Tooltip } from '@nymproject/react/tooltip/Tooltip'; import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'; import { Box } from '@mui/system'; import { cellStyles } from './Universal-DataGrid'; -import { currencyToString } from '../utils/currency'; +import { unymToNym } from '../utils/currency'; import { GatewayEnrichedRowType } from './Gateways'; import { MixnodeRowType } from './MixNodes'; @@ -38,7 +38,7 @@ function formatCellValues(val: string | number, field: string) { ); } if (field === 'bond') { - return currencyToString(val.toString()); + return unymToNym(val, 6); } return val; } diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index e75c9aeb05..aa055a71d0 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -7,6 +7,7 @@ export type GatewayRowType = { bond: number; host: string; location: string; + version: string; }; export type GatewayEnrichedRowType = GatewayRowType & { @@ -26,6 +27,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy location: gw?.gateway?.location || '', bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', + version: gw.gateway.version || '', })); } @@ -40,6 +42,7 @@ export function gatewayEnrichedToGridRow( location: gateway?.gateway?.location || '', bond: gateway.pledge_amount.amount || 0, host: gateway.gateway.host || '', + version: gateway.gateway.version || '', clientsPort: gateway.gateway.clients_port || 0, mixPort: gateway.gateway.mix_port || 0, routingScore: `${report.most_recent}%`, diff --git a/explorer/src/pages/GatewayDetail/index.tsx b/explorer/src/pages/GatewayDetail/index.tsx index 5fb7dd9c9f..3372ded2a7 100644 --- a/explorer/src/pages/GatewayDetail/index.tsx +++ b/explorer/src/pages/GatewayDetail/index.tsx @@ -58,6 +58,12 @@ const columns: ColumnsType[] = [ headerAlign: 'left', flex: 1, }, + { + field: 'version', + title: 'Version', + headerAlign: 'left', + flex: 1, + }, ]; /** diff --git a/explorer/src/pages/Gateways/index.tsx b/explorer/src/pages/Gateways/index.tsx index 2430e4f9a5..f35f7382b4 100644 --- a/explorer/src/pages/Gateways/index.tsx +++ b/explorer/src/pages/Gateways/index.tsx @@ -10,7 +10,7 @@ import { TableToolbar } from '../../components/TableToolbar'; import { CustomColumnHeading } from '../../components/CustomColumnHeading'; import { Title } from '../../components/Title'; import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid'; -import { currencyToString } from '../../utils/currency'; +import { unymToNym } from '../../utils/currency'; import { Tooltip } from '../../components/Tooltip'; import { BIG_DIPPER } from '../../api/constants'; import { splice } from '../../utils'; @@ -77,7 +77,7 @@ export const PageGateways: React.FC = () => { to={`/network-components/gateway/${params.row.identityKey}`} data-testid="pledge-amount" > - {currencyToString(params.value)} + {unymToNym(params.value, 6)} ), }, @@ -128,7 +128,7 @@ export const PageGateways: React.FC = () => { field: 'owner', headerName: 'Owner', renderHeader: () => , - width: 380, + width: 180, headerAlign: 'left', headerClassName: 'MuiDataGrid-header-override', renderCell: (params: GridRenderCellParams) => ( @@ -142,6 +142,24 @@ export const PageGateways: React.FC = () => { ), }, + { + field: 'version', + headerName: 'Version', + renderHeader: () => , + width: 150, + headerAlign: 'left', + headerClassName: 'MuiDataGrid-header-override', + renderCell: (params: GridRenderCellParams) => ( + + {params.value} + + ), + }, ]; const handlePageSize = (event: SelectChangeEvent) => { diff --git a/explorer/src/pages/MixnodeDetail/index.tsx b/explorer/src/pages/MixnodeDetail/index.tsx index 1c38f69a53..95e18ea603 100644 --- a/explorer/src/pages/MixnodeDetail/index.tsx +++ b/explorer/src/pages/MixnodeDetail/index.tsx @@ -164,10 +164,10 @@ const PageMixnodeDetailWithState: React.FC = () => { {mixNode && ( {mixNode?.error && } - {mixNode.data && mixNode?.data?.location && ( + {mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && ( )} diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index f8350775c4..188a0c3ac0 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -77,8 +77,8 @@ export interface MixNodeResponseItem { status: MixnodeStatus; location: { country_name: string; - lat: number; - lng: number; + latitude?: number; + longitude?: number; three_letter_iso_country_code: string; two_letter_iso_country_code: string; }; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 528072ed96..a1761a7e17 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.4" +version = "1.1.5" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", @@ -64,7 +64,7 @@ network-defaults = { path = "../common/network-defaults" } nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore" } statistics-common = { path = "../common/statistics" } -validator-api-requests = { path = "../validator-api/validator-api-requests" } +nym-api-requests = { path = "../nym-api/nym-api-requests" } validator-client = { path = "../common/client-libs/validator-client", features = [ "nymd-client", ] } @@ -75,7 +75,7 @@ coconut = [ "coconut-interface", "gateway-requests/coconut", "credentials/coconut", - "validator-api-requests/coconut", + "nym-api-requests/coconut", ] [build-dependencies] diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index 3fc014db51..b030bfb67e 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -1,4 +1,4 @@ -// Copyright 2020 - Nym Technologies SA +// Copyright 2020-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::{GatewayMacSize, GatewayRequestsError}; @@ -10,7 +10,7 @@ use crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag}; use crypto::symmetric::stream_cipher::{self, CipherKey, KeySizeUser, IV}; use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; use pemstore::traits::PemStorableKey; -use std::fmt::{self, Display, Formatter}; +use thiserror::Error; // shared key is as long as the encryption key and the MAC key combined. pub type SharedKeySize = Sum; @@ -28,37 +28,23 @@ pub struct SharedKeys { mac_key: MacKey, } -#[derive(Debug)] +#[derive(Debug, Error)] pub enum SharedKeyConversionError { - DecodeError(bs58::decode::Error), - BytesOfInvalidLengthError, - StringOfInvalidLengthError, + #[error("the string representation of the shared keys was malformed - {0}")] + DecodeError(#[from] bs58::decode::Error), + #[error( + "the received shared keys had invalid size. Got: {received}, but expected: {expected}" + )] + InvalidSharedKeysSize { received: usize, expected: usize }, } -impl Display for SharedKeyConversionError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - SharedKeyConversionError::DecodeError(err) => write!( - f, - "encountered error while decoding the byte sequence: {}", - err - ), - SharedKeyConversionError::BytesOfInvalidLengthError => { - write!(f, "provided bytes have invalid length") - } - SharedKeyConversionError::StringOfInvalidLengthError => { - write!(f, "provided string has invalid length") - } - } - } -} - -impl std::error::Error for SharedKeyConversionError {} - impl SharedKeys { pub fn try_from_bytes(bytes: &[u8]) -> Result { if bytes.len() != SharedKeySize::to_usize() { - return Err(SharedKeyConversionError::BytesOfInvalidLengthError); + return Err(SharedKeyConversionError::InvalidSharedKeysSize { + received: bytes.len(), + expected: SharedKeySize::to_usize(), + }); } let encryption_key = @@ -157,15 +143,7 @@ impl SharedKeys { pub fn try_from_base58_string>( val: S, ) -> Result { - let decoded = match bs58::decode(val.into()).into_vec() { - Ok(decoded) => decoded, - Err(err) => return Err(SharedKeyConversionError::DecodeError(err)), - }; - - if decoded.len() != SharedKeySize::to_usize() { - return Err(SharedKeyConversionError::StringOfInvalidLengthError); - } - + let decoded = bs58::decode(val.into()).into_vec()?; SharedKeys::try_from_bytes(&decoded) } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index f73f85bca5..3d93800c73 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -99,7 +99,7 @@ impl fmt::Display for GatewayRequestsError { MalformedSphinxPacket => write!(f, "received sphinx packet was malformed"), MalformedEncryption => write!(f, "the received encrypted data was malformed"), InvalidPacketMode => write!(f, "provided packet mode is invalid"), - InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {}", err) + InvalidMixPacket(err) => write!(f, "provided mix packet was malformed - {err}") } } } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index dca393213a..abd668b33f 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -39,9 +39,9 @@ pub struct Init { #[clap(long)] datastore: Option, - /// Comma separated list of endpoints of the validators APIs + /// Comma separated list of endpoints of nym APIs #[clap(long)] - validator_apis: Option, + nym_apis: Option, /// Comma separated list of endpoints of the validator #[clap(long)] @@ -75,7 +75,7 @@ impl From for OverrideConfig { clients_port: init_config.clients_port, datastore: init_config.datastore, announce_host: init_config.announce_host, - validator_apis: init_config.validator_apis, + nym_apis: init_config.nym_apis, validators: init_config.validators, mnemonic: init_config.mnemonic, @@ -165,7 +165,7 @@ mod tests { clients_port: Some(43), announce_host: Some("foo-announce-host".to_string()), datastore: Some("foo-datastore".to_string()), - validator_apis: None, + nym_apis: None, validators: None, mnemonic: None, statistics_service_url: None, diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 02dec84802..b3fbb6f537 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -55,7 +55,7 @@ pub(crate) struct OverrideConfig { announce_host: Option, enabled_statistics: Option, statistics_service_url: Option, - validator_apis: Option, + nym_apis: Option, validators: Option, mnemonic: Option, @@ -119,11 +119,11 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi } } - if let Some(raw_validators) = args.validator_apis { - config = config.with_custom_validator_apis(parse_validators(&raw_validators)); + if let Some(raw_validators) = args.nym_apis { + config = config.with_custom_nym_apis(parse_validators(&raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + config = config.with_custom_nym_apis(::config::parse_validators(&raw_validators)) } } @@ -165,7 +165,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = bech32_address_validation::try_bech32_decode(address) { - let error_message = format!("Error: wallet address decoding failed: {}", err).red(); + let error_message = format!("Error: wallet address decoding failed: {err}").red(); println!("{}", error_message); println!("Exiting..."); process::exit(1); @@ -174,7 +174,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = bech32_address_validation::validate_bech32_prefix(&prefix, address) { - let error_message = format!("Error: wallet address type is wrong, {}", err).red(); + let error_message = format!("Error: wallet address type is wrong, {err}").red(); println!("{}", error_message); println!("Exiting..."); process::exit(1); diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 37320877fe..e91f260d1b 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -39,9 +39,9 @@ pub struct Run { #[clap(long)] datastore: Option, - /// Comma separated list of endpoints of the validators APIs + /// Comma separated list of endpoints of the nym APIs #[clap(long)] - validator_apis: Option, + nym_apis: Option, /// Comma separated list of endpoints of the validator #[clap(long)] @@ -75,7 +75,7 @@ impl From for OverrideConfig { clients_port: run_config.clients_port, datastore: run_config.datastore, announce_host: run_config.announce_host, - validator_apis: run_config.validator_apis, + nym_apis: run_config.nym_apis, validators: run_config.validators, mnemonic: run_config.mnemonic, diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index 17d6eebfd4..61e9495f09 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -104,7 +104,7 @@ pub fn execute(args: &Sign) { let signed_target = match SignedTarget::try_from(args.clone()) { Ok(s) => s, Err(err) => { - error!("{}", err); + error!("{err}"); return; } }; diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index 7d8e5481a6..de3549b4f1 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -57,7 +57,7 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -106,7 +106,7 @@ fn minor_0_12_upgrade( let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); upgraded_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); }); @@ -140,7 +140,7 @@ pub async fn execute(args: &Upgrade) { let package_version = parse_package_version(); let existing_config = Config::load_from_file(Some(&args.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/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index ac4f734dad..9eb15c1c5a 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -144,8 +144,8 @@ impl Config { self } - pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { - self.gateway.validator_api_urls = validator_api_urls; + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { + self.gateway.nym_api_urls = nym_api_urls; self } @@ -240,8 +240,8 @@ impl Config { self.gateway.statistics_service_url.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec { - self.gateway.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec { + self.gateway.nym_api_urls.clone() } #[cfg(feature = "coconut")] @@ -359,8 +359,8 @@ pub struct Gateway { /// Domain address of the statistics service statistics_service_url: Url, - /// Addresses to APIs running on validator from which the node gets the view of the network. - validator_api_urls: Vec, + /// Addresses to APIs from which the node gets the view of the network. + nym_api_urls: Vec, /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. validator_nymd_urls: Vec, @@ -418,7 +418,7 @@ impl Default for Gateway { public_sphinx_key_file: Default::default(), enabled_statistics: false, statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS).expect("Invalid default statistics service URL"), - validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], + nym_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], validator_nymd_urls: vec![Url::from_str(NYMD_VALIDATOR).expect("Invalid default nymd URL")], cosmos_mnemonic: bip39::Mnemonic::from_str("exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day").unwrap(), nym_root_directory: Config::default_root_directory(), diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index faba004a50..0d1759e820 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -60,8 +60,8 @@ enabled_statistics = {{ gateway.enabled_statistics }} statistics_service_url = '{{ gateway.statistics_service_url }}' # Addresses to APIs running on validator from which the node gets the view of the network. -validator_api_urls = [ - {{#each gateway.validator_api_urls }} +nym_api_urls = [ + {{#each gateway.nym_api_urls }} '{{this}}', {{/each}} ] diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index cb2d85b5a9..40ca0d7715 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -56,8 +56,8 @@ pub(crate) enum RequestHandlingError { APIError(#[from] validator_client::ValidatorClientError), #[cfg(feature = "coconut")] - #[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")] - NotEnoughValidatorAPIs { received: usize, needed: usize }, + #[error("Not enough nym API endpoints provided. Needed {needed}, received {received}")] + NotEnoughNymAPIs { received: usize, needed: usize }, #[cfg(feature = "coconut")] #[error("There was a problem with the proposal id: {reason}")] @@ -182,7 +182,7 @@ where /// * `mix_packet`: packet received from the client that should get forwarded into the network. fn forward_packet(&self, mix_packet: MixPacket) { if let Err(err) = self.inner.outbound_mix_sender.unbounded_send(mix_packet) { - error!("We failed to forward requested mix packet - {}. Presumably our mix forwarder has crashed. We cannot continue.", err); + error!("We failed to forward requested mix packet - {err}. Presumably our mix forwarder has crashed. We cannot continue."); process::exit(1); } } @@ -426,7 +426,7 @@ where None => break, Some(Ok(socket_msg)) => socket_msg, Some(Err(err)) => { - error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + error!("failed to obtain message from websocket stream! stopping connection handler: {err}"); break; } }; @@ -438,8 +438,7 @@ where if let Some(response) = self.handle_request(socket_msg).await { if let Err(err) = self.inner.send_websocket_message(response).await { warn!( - "Failed to send message over websocket: {}. Assuming the connection is dead.", - err + "Failed to send message over websocket: {err}. Assuming the connection is dead.", ); break; } @@ -447,8 +446,8 @@ where }, mix_messages = self.mix_receiver.next() => { let mix_messages = mix_messages.expect("sender was unexpectedly closed! this shouldn't have ever happened!"); - if let Err(e) = self.inner.push_packets_to_client(self.client.shared_keys, mix_messages).await { - warn!("failed to send the unwrapped sphinx packets back to the client - {:?}, assuming the connection is dead", e); + if let Err(err) = self.inner.push_packets_to_client(self.client.shared_keys, mix_messages).await { + warn!("failed to send the unwrapped sphinx packets back to the client - {err}, assuming the connection is dead"); break; } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index e83c23e5e9..ba53fe4dbd 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -21,7 +21,7 @@ const ONE_HOUR_SEC: u64 = 3600; const MAX_FEEGRANT_UNYM: u128 = 10000; pub(crate) struct CoconutVerifier { - api_clients: Vec, + nym_api_clients: Vec, nymd_client: Client, mix_denom_base: String, aggregated_verification_key: VerificationKey, @@ -35,13 +35,13 @@ impl CoconutVerifier { aggregated_verification_key: VerificationKey, ) -> Result { if api_clients.is_empty() { - return Err(RequestHandlingError::NotEnoughValidatorAPIs { + return Err(RequestHandlingError::NotEnoughNymAPIs { received: 0, needed: 1, }); } Ok(CoconutVerifier { - api_clients, + nym_api_clients: api_clients, nymd_client, mix_denom_base, aggregated_verification_key, @@ -87,12 +87,12 @@ impl CoconutVerifier { }); } - let req = validator_api_requests::coconut::VerifyCredentialBody::new( + let req = nym_api_requests::coconut::VerifyCredentialBody::new( credential.clone(), proposal_id, self.nymd_client.nymd.address().clone(), ); - for client in self.api_clients.iter() { + for client in self.nym_api_clients.iter() { self.nymd_client .nymd .grant_allowance( @@ -115,7 +115,7 @@ impl CoconutVerifier { ) .await?; if !ret?.verification_result { - debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.validator_api.current_url()); + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.api_client.nym_api_client.current_url()); } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 9e13fd2e69..9dd99eb23b 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -586,7 +586,7 @@ where let msg = match msg { Ok(msg) => msg, Err(err) => { - error!("failed to obtain message from websocket stream! stopping connection handler: {}", err); + error!("failed to obtain message from websocket stream! stopping connection handler: {err}"); break; } }; @@ -605,7 +605,7 @@ where if let Err(err) = self.send_websocket_message(err.into_error_message()).await { - debug!("Failed to send authentication error response - {}", err); + debug!("Failed to send authentication error response - {err}"); return None; } } @@ -614,7 +614,7 @@ where .send_websocket_message(auth_result.server_response.into()) .await { - debug!("Failed to send authentication response - {}", err); + debug!("Failed to send authentication response - {err}"); return None; } diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index ee76f0213a..580ec61750 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -55,7 +55,7 @@ impl Listener { let tcp_listener = match tokio::net::TcpListener::bind(self.address).await { Ok(listener) => listener, Err(err) => { - error!("Failed to bind the websocket 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 the websocket 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); } }; @@ -79,7 +79,7 @@ impl Listener { ); tokio::spawn(async move { handle.start_handling().await }); } - Err(e) => warn!("failed to get client: {:?}", e), + Err(err) => warn!("failed to get client: {err}"), } } } diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 8dd85d9ded..5db66bd7cc 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -142,7 +142,7 @@ impl ConnectionHandler { .store_processed_packet_payload(client_address, unsent_plaintext) .await { - Err(err) => error!("Failed to store client data - {}", err), + Err(err) => error!("Failed to store client data - {err}"), Ok(_) => trace!("Stored packet for {}", client_address), }, Ok(_) => trace!("Pushed received packet to {}", client_address), @@ -163,8 +163,8 @@ impl ConnectionHandler { let processed_final_hop = match self.packet_processor.process_received(framed_sphinx_packet) { - Err(e) => { - debug!("We failed to process received sphinx packet - {:?}", e); + Err(err) => { + debug!("We failed to process received sphinx packet - {err}"); return; } Ok(processed_final_hop) => processed_final_hop, diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 1cb1f2850e..d301dc692b 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -26,7 +26,7 @@ impl Listener { 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); } }; @@ -37,7 +37,7 @@ impl Listener { let handler = connection_handler.clone(); tokio::spawn(handler.handle_connection(socket, remote_addr)); } - Err(e) => warn!("failed to get client: {:?}", e), + Err(err) => warn!("failed to get client: {err}"), } } } diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index af9970cb47..a344f475f1 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -6,21 +6,17 @@ use mixnode_common::packet_processor::error::MixProcessingError; pub use mixnode_common::packet_processor::processor::MixProcessingResult; use mixnode_common::packet_processor::processor::{ProcessedFinalHop, SphinxPacketProcessor}; use nymsphinx::framing::packet::FramedSphinxPacket; +use thiserror::Error; -#[derive(Debug)] +#[derive(Error, Debug)] pub enum GatewayProcessingError { - PacketProcessingError(MixProcessingError), + #[error("failed to process received mix packet - {0}")] + PacketProcessingError(#[from] MixProcessingError), + + #[error("received a forward hop mix packet")] ForwardHopReceivedError, } -impl From for GatewayProcessingError { - fn from(e: MixProcessingError) -> Self { - use GatewayProcessingError::*; - - PacketProcessingError(e) - } -} - // PacketProcessor contains all data required to correctly unwrap and store sphinx packets #[derive(Clone)] pub struct PacketProcessor { diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 1d23068db7..7500302787 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -46,7 +46,7 @@ async fn initialise_storage(config: &Config) -> PersistentStorage { let path = config.get_persistent_store_path(); let retrieval_limit = config.get_message_retrieval_limit(); match PersistentStorage::init(path, retrieval_limit).await { - Err(err) => panic!("failed to initialise gateway storage - {}", err), + Err(err) => panic!("failed to initialise gateway storage - {err}"), Ok(storage) => storage, } } @@ -231,12 +231,12 @@ where } fn random_api_client(&self) -> validator_client::ApiClient { - let endpoints = self.config.get_validator_api_endpoints(); - let validator_api = endpoints + let endpoints = self.config.get_nym_api_endpoints(); + let nym_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - validator_client::ApiClient::new(validator_api.clone()) + validator_client::ApiClient::new(nym_api.clone()) } #[cfg(feature = "coconut")] @@ -269,7 +269,7 @@ where let existing_gateways = match validator_client.get_cached_gateways().await { Ok(gateways) => gateways, Err(err) => { - error!("failed to grab initial network gateways - {}\n Please try to startup again in few minutes", err); + error!("failed to grab initial network gateways - {err}\n Please try to startup again in few minutes"); process::exit(1); } }; diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index aeed709455..cbb7c27f90 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -173,13 +173,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()); } diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 776b7594f3..fbffe76322 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.4" +version = "1.1.5" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 2bab09b8c9..07b2d420c8 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -18,7 +18,7 @@ pub(crate) fn execute(args: &Describe) { match Config::load_from_file(Some(&args.id)) { Ok(cfg) => cfg, Err(err) => { - error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {})", &args.id, err); + error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id); return; } }; diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index f716953c11..2edad136f9 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -96,10 +96,10 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config { } if let Some(ref raw_validators) = args.validators { - config = config.with_custom_validator_apis(parse_validators(raw_validators)); + config = config.with_custom_nym_apis(parse_validators(raw_validators)); } else if std::env::var(CONFIGURED).is_ok() { if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) { - config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators)) + config = config.with_custom_nym_apis(::config::parse_validators(&raw_validators)) } } @@ -125,7 +125,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { if let Err(bech32_address_validation::Bech32Error::DecodeFailed(err)) = bech32_address_validation::try_bech32_decode(address) { - let error_message = format!("Error: wallet address decoding failed: {}", err).red(); + let error_message = format!("Error: wallet address decoding failed: {err}").red(); println!("{}", error_message); println!("Exiting..."); process::exit(1); @@ -134,7 +134,7 @@ pub(crate) fn validate_bech32_address_or_exit(address: &str) { if let Err(bech32_address_validation::Bech32Error::WrongPrefix(err)) = bech32_address_validation::validate_bech32_prefix(&prefix, address) { - let error_message = format!("Error: wallet address type is wrong, {}", err).red(); + let error_message = format!("Error: wallet address type is wrong, {err}").red(); println!("{}", error_message); println!("Exiting..."); process::exit(1); diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index d104d47988..d11996830a 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -57,7 +57,7 @@ fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { - eprintln!("failed to parse client version! - {:?}", err); + eprintln!("failed to parse client version! - {err}"); process::exit(1) }); @@ -106,7 +106,7 @@ fn minor_0_12_upgrade( let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); upgraded_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); }); @@ -140,7 +140,7 @@ pub(crate) fn execute(args: &Upgrade) { let package_version = parse_package_version(); let existing_config = Config::load_from_file(Some(&args.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/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 3f5208cee8..bc9278bc85 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -155,8 +155,8 @@ impl Config { self } - pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { - self.mixnode.validator_api_urls = validator_api_urls; + pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { + self.mixnode.nym_api_urls = nym_api_urls; self } @@ -233,8 +233,8 @@ impl Config { self.mixnode.public_sphinx_key_file.clone() } - pub fn get_validator_api_endpoints(&self) -> Vec { - self.mixnode.validator_api_urls.clone() + pub fn get_nym_api_endpoints(&self) -> Vec { + self.mixnode.nym_api_urls.clone() } pub fn get_node_stats_logging_delay(&self) -> Duration { @@ -369,8 +369,8 @@ struct MixNode { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Addresses to APIs running on validator from which the node gets the view of the network. - validator_api_urls: Vec, + /// Addresses to nym APIs from which the node gets the view of the network. + nym_api_urls: Vec, /// nym_home_directory specifies absolute path to the home nym MixNodes directory. /// It is expected to use default value and hence .toml file should not redefine this field. @@ -412,7 +412,7 @@ impl Default for MixNode { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], + nym_api_urls: vec![Url::from_str(API_VALIDATOR).expect("Invalid default API URL")], nym_root_directory: Config::default_root_directory(), wallet_address: "nymXXXXXXXX".to_string(), } diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index 2af12a195b..f222275083 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -54,8 +54,8 @@ verloc_port = {{ mixnode.verloc_port }} http_api_port = {{ mixnode.http_api_port }} # Addresses to APIs running on validator from which the node gets the view of the network. -validator_api_urls = [ - {{#each mixnode.validator_api_urls }} +nym_api_urls = [ + {{#each mixnode.nym_api_urls }} '{{this}}', {{/each}} ] diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs index c9baaecacf..52203c89ab 100644 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -5,7 +5,7 @@ use crate::node::listener::connection_handler::packet_processing::{ MixProcessingResult, PacketProcessor, }; use crate::node::packet_delayforwarder::PacketDelayForwardSender; -use crate::node::ShutdownListener; +use crate::node::TaskClient; use futures::StreamExt; use log::{error, info}; use nymsphinx::forwarding::packet::MixPacket; @@ -58,7 +58,7 @@ impl ConnectionHandler { // all processing such, key caching, etc. was done. // however, if it was a forward hop, we still need to delay it match self.packet_processor.process_received(framed_sphinx_packet) { - Err(e) => debug!("We failed to process received sphinx packet - {:?}", e), + Err(err) => debug!("We failed to process received sphinx packet - {err}"), Ok(res) => match res { MixProcessingResult::ForwardHop(forward_packet, delay) => { self.delay_and_forward_packet(forward_packet, delay) @@ -74,7 +74,7 @@ impl ConnectionHandler { self, conn: TcpStream, remote: SocketAddr, - mut shutdown: ShutdownListener, + mut shutdown: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); let mut framed_conn = Framed::new(conn, SphinxCodec); diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs index a1542fba79..18ab26bc63 100644 --- a/mixnode/src/node/listener/mod.rs +++ b/mixnode/src/node/listener/mod.rs @@ -8,17 +8,17 @@ use std::process; use tokio::net::TcpListener; use tokio::task::JoinHandle; -use super::ShutdownListener; +use super::TaskClient; pub(crate) mod connection_handler; pub(crate) struct Listener { address: SocketAddr, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl Listener { - pub(crate) fn new(address: SocketAddr, shutdown: ShutdownListener) -> Self { + pub(crate) fn new(address: SocketAddr, shutdown: TaskClient) -> Self { Listener { address, shutdown } } @@ -27,7 +27,7 @@ impl Listener { let listener = match 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); } }; @@ -40,7 +40,7 @@ impl Listener { let handler = connection_handler.clone(); tokio::spawn(handler.handle_connection(socket, remote_addr, self.shutdown.clone())); } - Err(err) => warn!("Failed to accept incoming connection - {:?}", err), + Err(err) => warn!("Failed to accept incoming connection - {err}"), } }, _ = self.shutdown.recv() => { diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 0bca9b31b0..9fdbff3ebb 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -26,7 +26,7 @@ use rand::thread_rng; use std::net::SocketAddr; use std::process; use std::sync::Arc; -use task::{wait_for_signal, ShutdownListener, ShutdownNotifier}; +use task::{wait_for_signal, TaskClient, TaskManager}; use version_checker::parse_version; mod http; @@ -153,7 +153,7 @@ impl MixNode { fn start_node_stats_controller( &self, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> (SharedNodeStats, node_statistics::UpdateSender) { info!("Starting node stats controller..."); let controller = node_statistics::Controller::new( @@ -171,7 +171,7 @@ impl MixNode { &self, node_stats_update_sender: node_statistics::UpdateSender, delay_forwarding_channel: PacketDelayForwardSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { info!("Starting socket listener..."); @@ -191,7 +191,7 @@ impl MixNode { fn start_packet_delay_forwarder( &mut self, node_stats_update_sender: node_statistics::UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> PacketDelayForwardSender { info!("Starting packet delay-forwarder..."); @@ -215,7 +215,7 @@ impl MixNode { packet_sender } - fn start_verloc_measurements(&self, shutdown: ShutdownListener) -> AtomicVerlocResult { + fn start_verloc_measurements(&self, shutdown: TaskClient) -> AtomicVerlocResult { info!("Starting the round-trip-time measurer..."); // this is a sanity check to make sure we didn't mess up with the minimum version at some point @@ -245,7 +245,7 @@ impl MixNode { .tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size()) .testing_interval(self.config.get_measurement_testing_interval()) .retry_timeout(self.config.get_measurement_retry_timeout()) - .validator_api_urls(self.config.get_validator_api_endpoints()) + .nym_api_urls(self.config.get_nym_api_endpoints()) .build(); let mut verloc_measurer = @@ -256,12 +256,12 @@ impl MixNode { } fn random_api_client(&self) -> validator_client::ApiClient { - let endpoints = self.config.get_validator_api_endpoints(); - let validator_api = endpoints + let endpoints = self.config.get_nym_api_endpoints(); + let nym_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - validator_client::ApiClient::new(validator_api.clone()) + validator_client::ApiClient::new(nym_api.clone()) } // TODO: ask DH whether this function still makes sense in ^0.10 @@ -288,7 +288,7 @@ impl MixNode { .map(|node| node.bond_information.mix_node.identity_key.clone()) } - 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"); @@ -315,7 +315,7 @@ impl MixNode { } } - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let (node_stats_pointer, node_stats_update_sender) = self.start_node_stats_controller(shutdown.subscribe()); diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs index 86f98401ab..2fa8c39b8e 100644 --- a/mixnode/src/node/node_statistics.rs +++ b/mixnode/src/node/node_statistics.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use tokio::sync::{RwLock, RwLockReadGuard}; -use super::ShutdownListener; +use super::TaskClient; // convenience aliases type PacketsMap = HashMap; @@ -211,14 +211,14 @@ impl CurrentPacketData { struct UpdateHandler { current_data: CurrentPacketData, update_receiver: PacketDataReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl UpdateHandler { fn new( current_data: CurrentPacketData, update_receiver: PacketDataReceiver, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { UpdateHandler { current_data, @@ -293,7 +293,7 @@ struct StatsUpdater { updating_delay: Duration, current_packet_data: CurrentPacketData, current_stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl StatsUpdater { @@ -301,7 +301,7 @@ impl StatsUpdater { updating_delay: Duration, current_packet_data: CurrentPacketData, current_stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { StatsUpdater { updating_delay, @@ -335,11 +335,11 @@ impl StatsUpdater { struct PacketStatsConsoleLogger { logging_delay: Duration, stats: SharedNodeStats, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl PacketStatsConsoleLogger { - fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: ShutdownListener) -> Self { + fn new(logging_delay: Duration, stats: SharedNodeStats, shutdown: TaskClient) -> Self { PacketStatsConsoleLogger { logging_delay, stats, @@ -451,7 +451,7 @@ impl Controller { pub(crate) fn new( logging_delay: Duration, stats_updating_delay: Duration, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> Self { let (sender, receiver) = mpsc::unbounded(); let shared_packet_data = CurrentPacketData::new(); @@ -503,13 +503,13 @@ impl Controller { #[cfg(test)] mod tests { use super::*; - use task::ShutdownNotifier; + use task::TaskManager; #[tokio::test] async fn node_stats_reported_are_received() { let logging_delay = Duration::from_millis(20); let stats_updating_delay = Duration::from_millis(10); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let node_stats_controller = Controller::new(logging_delay, stats_updating_delay, shutdown.subscribe()); diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs index 81e0303afd..da098bcdf9 100644 --- a/mixnode/src/node/packet_delayforwarder.rs +++ b/mixnode/src/node/packet_delayforwarder.rs @@ -9,7 +9,7 @@ use nymsphinx::forwarding::packet::MixPacket; use std::io; use tokio::time::Instant; -use super::ShutdownListener; +use super::TaskClient; // Delay + MixPacket vs Instant + MixPacket @@ -28,7 +28,7 @@ where packet_sender: PacketDelayForwardSender, packet_receiver: PacketDelayForwardReceiver, node_stats_update_sender: UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, } impl DelayForwarder @@ -38,7 +38,7 @@ where pub(crate) fn new( client: C, node_stats_update_sender: UpdateSender, - shutdown: ShutdownListener, + shutdown: TaskClient, ) -> DelayForwarder { let (packet_sender, packet_receiver) = mpsc::unbounded(); @@ -134,7 +134,7 @@ mod tests { use std::sync::{Arc, Mutex}; use std::time::Duration; - use task::ShutdownNotifier; + use task::TaskManager; use nymsphinx::addressing::nodes::NymNodeRoutingAddress; use nymsphinx_params::packet_sizes::PacketSize; @@ -205,7 +205,7 @@ mod tests { let node_stats_update_sender = UpdateSender::new(stats_sender); let client = TestClient::default(); let client_packets_sent = client.packets_sent.clone(); - let shutdown = ShutdownNotifier::default(); + let shutdown = TaskManager::default(); let mut delay_forwarder = DelayForwarder::new(client, node_stats_update_sender, shutdown.subscribe()); let packet_sender = delay_forwarder.sender(); diff --git a/validator-api/Cargo.toml b/nym-api/Cargo.toml similarity index 97% rename from validator-api/Cargo.toml rename to nym-api/Cargo.toml index 2a8c510dd0..960e0599f9 100644 --- a/validator-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-validator-api" -version = "1.1.4" +version = "1.1.5" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", @@ -84,7 +84,7 @@ nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore", optional = true } task = { path = "../common/task" } topology = { path = "../common/topology" } -validator-api-requests = { path = "validator-api-requests" } +nym-api-requests = { path = "nym-api-requests" } validator-client = { path = "../common/client-libs/validator-client", features = [ "nymd-client", ] } @@ -97,7 +97,7 @@ coconut = [ "cw3", "gateway-client/coconut", "credentials/coconut", - "validator-api-requests/coconut", + "nym-api-requests/coconut", "nymcoconut", "coconut-dkg-common", "dkg", diff --git a/validator-api/README.md b/nym-api/README.md similarity index 100% rename from validator-api/README.md rename to nym-api/README.md diff --git a/validator-api/Rocket.toml b/nym-api/Rocket.toml similarity index 100% rename from validator-api/Rocket.toml rename to nym-api/Rocket.toml diff --git a/validator-api/build.rs b/nym-api/build.rs similarity index 92% rename from validator-api/build.rs rename to nym-api/build.rs index 1e967f0aab..6c92dffc59 100644 --- a/validator-api/build.rs +++ b/nym-api/build.rs @@ -5,7 +5,7 @@ use vergen::{vergen, Config}; #[tokio::main] async fn main() { let out_dir = env::var("OUT_DIR").unwrap(); - let database_path = format!("{}/validator-api-example.sqlite", out_dir); + let database_path = format!("{}/nym-api-example.sqlite", out_dir); let mut conn = SqliteConnection::connect(&format!("sqlite://{}?mode=rwc", database_path)) .await diff --git a/validator-api/migrations/20210722120000_create_initial_tables.sql b/nym-api/migrations/20210722120000_create_initial_tables.sql similarity index 100% rename from validator-api/migrations/20210722120000_create_initial_tables.sql rename to nym-api/migrations/20210722120000_create_initial_tables.sql diff --git a/validator-api/migrations/20210819120000_monitor_runs.sql b/nym-api/migrations/20210819120000_monitor_runs.sql similarity index 100% rename from validator-api/migrations/20210819120000_monitor_runs.sql rename to nym-api/migrations/20210819120000_monitor_runs.sql diff --git a/validator-api/migrations/20210824120000_rewarding_data.sql b/nym-api/migrations/20210824120000_rewarding_data.sql similarity index 100% rename from validator-api/migrations/20210824120000_rewarding_data.sql rename to nym-api/migrations/20210824120000_rewarding_data.sql diff --git a/validator-api/migrations/20211018120000_core_nodes_testing.sql b/nym-api/migrations/20211018120000_core_nodes_testing.sql similarity index 100% rename from validator-api/migrations/20211018120000_core_nodes_testing.sql rename to nym-api/migrations/20211018120000_core_nodes_testing.sql diff --git a/validator-api/migrations/20211101110000_no_gateway_rewarding.sql b/nym-api/migrations/20211101110000_no_gateway_rewarding.sql similarity index 100% rename from validator-api/migrations/20211101110000_no_gateway_rewarding.sql rename to nym-api/migrations/20211101110000_no_gateway_rewarding.sql diff --git a/validator-api/migrations/20220121120000_epoch_interval_renaming.sql b/nym-api/migrations/20220121120000_epoch_interval_renaming.sql similarity index 100% rename from validator-api/migrations/20220121120000_epoch_interval_renaming.sql rename to nym-api/migrations/20220121120000_epoch_interval_renaming.sql diff --git a/validator-api/migrations/20220323120000_drop_foreign_keys_constraints.sql b/nym-api/migrations/20220323120000_drop_foreign_keys_constraints.sql similarity index 100% rename from validator-api/migrations/20220323120000_drop_foreign_keys_constraints.sql rename to nym-api/migrations/20220323120000_drop_foreign_keys_constraints.sql diff --git a/validator-api/migrations/20220325120000_signed_deposits.sql b/nym-api/migrations/20220325120000_signed_deposits.sql similarity index 100% rename from validator-api/migrations/20220325120000_signed_deposits.sql rename to nym-api/migrations/20220325120000_signed_deposits.sql diff --git a/validator-api/migrations/20220410222400_add_timestamp_indexes.sql b/nym-api/migrations/20220410222400_add_timestamp_indexes.sql similarity index 100% rename from validator-api/migrations/20220410222400_add_timestamp_indexes.sql rename to nym-api/migrations/20220410222400_add_timestamp_indexes.sql diff --git a/validator-api/migrations/20220804120000_mixnet_contract_v2.sql b/nym-api/migrations/20220804120000_mixnet_contract_v2.sql similarity index 100% rename from validator-api/migrations/20220804120000_mixnet_contract_v2.sql rename to nym-api/migrations/20220804120000_mixnet_contract_v2.sql diff --git a/validator-api/validator-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml similarity index 95% rename from validator-api/validator-api-requests/Cargo.toml rename to nym-api/nym-api-requests/Cargo.toml index ecd29e78a4..6787dcacfa 100644 --- a/validator-api/validator-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "validator-api-requests" +name = "nym-api-requests" version = "0.1.0" edition = "2021" diff --git a/validator-api/validator-api-requests/src/coconut.rs b/nym-api/nym-api-requests/src/coconut.rs similarity index 100% rename from validator-api/validator-api-requests/src/coconut.rs rename to nym-api/nym-api-requests/src/coconut.rs diff --git a/validator-api/validator-api-requests/src/lib.rs b/nym-api/nym-api-requests/src/lib.rs similarity index 100% rename from validator-api/validator-api-requests/src/lib.rs rename to nym-api/nym-api-requests/src/lib.rs diff --git a/validator-api/validator-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs similarity index 99% rename from validator-api/validator-api-requests/src/models.rs rename to nym-api/nym-api-requests/src/models.rs index eee713a701..df5fe07768 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -114,7 +114,7 @@ impl MixNodeBondAnnotated { } } -#[derive(Deserialize, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct ComputeRewardEstParam { pub performance: Option, pub active_in_rewarded_set: Option, diff --git a/validator-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs similarity index 94% rename from validator-api/src/coconut/client.rs rename to nym-api/src/coconut/client.rs index 87a64a4e6e..92af589921 100644 --- a/validator-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -4,7 +4,7 @@ use crate::coconut::error::Result; use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; @@ -22,7 +22,7 @@ pub trait Client { &self, blinded_serial_number: String, ) -> Result; - async fn get_current_epoch_state(&self) -> Result; + async fn get_current_epoch(&self) -> Result; async fn get_current_epoch_threshold(&self) -> Result>; async fn get_self_registered_dealer_details(&self) -> Result; async fn get_current_dealers(&self) -> Result>; @@ -31,6 +31,7 @@ pub trait Client { async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) -> Result<()>; async fn execute_proposal(&self, proposal_id: u64) -> Result<()>; + async fn advance_epoch_state(&self) -> Result<()>; async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/validator-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs similarity index 100% rename from validator-api/src/coconut/comm.rs rename to nym-api/src/coconut/comm.rs diff --git a/validator-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs similarity index 94% rename from validator-api/src/coconut/deposit.rs rename to nym-api/src/coconut/deposit.rs index 8dba92a705..dcee8cebc0 100644 --- a/validator-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -8,7 +8,7 @@ use coconut_bandwidth_contract_common::events::{ use credentials::coconut::bandwidth::BandwidthVoucher; use crypto::asymmetric::encryption; use crypto::asymmetric::identity::{self, Signature}; -use validator_api_requests::coconut::BlindSignRequestBody; +use nym_api_requests::coconut::BlindSignRequestBody; use validator_client::nymd::TxResponse; use super::error::{CoconutError, Result}; @@ -164,17 +164,13 @@ mod test { let err = extract_encryption_key(&req, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::Ed25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::identity::Ed25519RecoveryError::MalformedBytes( - crypto::asymmetric::identity::SignatureError::new(), - ), + crypto::asymmetric::identity::Ed25519RecoveryError::MalformedSignatureString { .. } ) - .to_string() - ); + )); let correct_request = BlindSignRequestBody::new( &blind_sign_req, @@ -279,17 +275,13 @@ mod test { let err = extract_encryption_key(&correct_request, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::Ed25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::identity::Ed25519RecoveryError::MalformedBytes( - crypto::asymmetric::identity::SignatureError::new(), - ), + crypto::asymmetric::identity::Ed25519RecoveryError::MalformedPublicKeyString { .. } ) - .to_string(), - ); + )); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ Tag { @@ -338,15 +330,13 @@ mod test { let err = extract_encryption_key(&correct_request, tx_entry.clone()) .await .unwrap_err(); - assert_eq!( - err.to_string(), + + assert!(matches!( + err, CoconutError::X25519ParseError( - // this is really just a useless, dummy error value needed to generate the error type - // and get its string representation - crypto::asymmetric::encryption::KeyRecoveryError::InvalidPublicKeyBytes, + crypto::asymmetric::encryption::KeyRecoveryError::MalformedPublicKeyString { .. } ) - .to_string(), - ); + )); let expected_encryption_key = "HxnTpWTkgigSTAysVKLE8pEiUULHdTT1BxFfzfJvQRi6"; tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ diff --git a/validator-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs similarity index 92% rename from validator-api/src/coconut/dkg/client.rs rename to nym-api/src/coconut/dkg/client.rs index d8a0cf5d69..61b583b84b 100644 --- a/validator-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -4,7 +4,7 @@ use crate::coconut::client::Client; use crate::coconut::error::CoconutError; use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, NodeIndex}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, NodeIndex}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use cw3::ProposalResponse; @@ -35,13 +35,13 @@ impl DkgClient { self.inner.address().await } - pub(crate) async fn get_current_epoch_state(&self) -> Result { - let mut ret = self.inner.get_current_epoch_state().await; + pub(crate) async fn get_current_epoch(&self) -> Result { + let mut ret = self.inner.get_current_epoch().await; for _ in 0..Self::RETRIES { if ret.is_ok() { return ret; } - ret = self.inner.get_current_epoch_state().await; + ret = self.inner.get_current_epoch().await; } ret } @@ -86,6 +86,10 @@ impl DkgClient { self.inner.list_proposals().await } + pub(crate) async fn advance_epoch_state(&self) -> Result<(), CoconutError> { + self.inner.advance_epoch_state().await + } + pub(crate) async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/validator-api/src/coconut/dkg/complaints.rs b/nym-api/src/coconut/dkg/complaints.rs similarity index 100% rename from validator-api/src/coconut/dkg/complaints.rs rename to nym-api/src/coconut/dkg/complaints.rs diff --git a/validator-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs similarity index 85% rename from validator-api/src/coconut/dkg/controller.rs rename to nym-api/src/coconut/dkg/controller.rs index 6a7268af47..bebb68f4d1 100644 --- a/validator-api/src/coconut/dkg/controller.rs +++ b/nym-api/src/coconut/dkg/controller.rs @@ -18,8 +18,8 @@ use dkg::bte::keys::KeyPair as DkgKeyPair; use rand::rngs::OsRng; use rand::RngCore; use std::path::PathBuf; -use std::time::Duration; -use task::ShutdownListener; +use std::time::{Duration, SystemTime}; +use task::TaskClient; use tokio::time::interval; use validator_client::nymd::SigningNymdClient; @@ -83,16 +83,16 @@ impl DkgController { } pub(crate) async fn handle_epoch_state(&mut self) { - match self.dkg_client.get_current_epoch_state().await { + match self.dkg_client.get_current_epoch().await { Err(e) => warn!("Could not get current epoch state {}", e), - Ok(epoch_state) => { - if let Err(e) = self.state.is_consistent(epoch_state).await { + Ok(epoch) => { + if let Err(e) = self.state.is_consistent(epoch.state).await { error!( "Epoch state is corrupted - {}, the process should be terminated", e ); } - let ret = match epoch_state { + let ret = match epoch.state { EpochState::PublicKeySubmission => { public_key_submission(&self.dkg_client, &mut self.state).await } @@ -122,7 +122,7 @@ impl DkgController { }; if let Err(e) = ret { warn!("Could not handle this iteration for the epoch state: {}", e); - } else if epoch_state != EpochState::InProgress { + } else if epoch.state != EpochState::InProgress { let persistent_state = PersistentState::from(&self.state); if let Err(e) = persistent_state.save_to_file(self.state.persistent_state_path()) @@ -130,11 +130,20 @@ impl DkgController { warn!("Could not backup the state for this iteration: {}", e); } } + if let Ok(current_timestamp) = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) + { + if current_timestamp.as_secs() >= epoch.finish_timestamp.seconds() { + // We try advancing the epoch state, on a best-effort basis + info!("Trying to advance the epoch"); + self.dkg_client.advance_epoch_state().await.ok(); + } + } } } } - pub(crate) async fn run(mut self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(mut self, mut shutdown: TaskClient) { let mut interval = interval(self.polling_rate); while !shutdown.is_shutdown() { tokio::select! { diff --git a/validator-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs similarity index 100% rename from validator-api/src/coconut/dkg/dealing.rs rename to nym-api/src/coconut/dkg/dealing.rs diff --git a/validator-api/src/coconut/dkg/mod.rs b/nym-api/src/coconut/dkg/mod.rs similarity index 100% rename from validator-api/src/coconut/dkg/mod.rs rename to nym-api/src/coconut/dkg/mod.rs diff --git a/validator-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs similarity index 100% rename from validator-api/src/coconut/dkg/public_key.rs rename to nym-api/src/coconut/dkg/public_key.rs diff --git a/validator-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state.rs similarity index 100% rename from validator-api/src/coconut/dkg/state.rs rename to nym-api/src/coconut/dkg/state.rs diff --git a/validator-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs similarity index 100% rename from validator-api/src/coconut/dkg/verification_key.rs rename to nym-api/src/coconut/dkg/verification_key.rs diff --git a/validator-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs similarity index 92% rename from validator-api/src/coconut/error.rs rename to nym-api/src/coconut/error.rs index 234d1cb96c..4c3a712eb2 100644 --- a/validator-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -14,22 +14,22 @@ use crypto::asymmetric::{ use dkg::error::DkgError; use validator_client::nymd::error::NymdError; -use crate::node_status_api::models::ValidatorApiStorageError; +use crate::node_status_api::models::NymApiStorageError; pub type Result = std::result::Result; #[derive(Debug, Error)] pub enum CoconutError { - #[error("{0}")] + #[error(transparent)] IOError(#[from] std::io::Error), - #[error("{0}")] + #[error(transparent)] SerdeJsonError(#[from] serde_json::Error), - #[error("Could not parse Ed25519 data")] + #[error("Could not parse Ed25519 data - {0}")] Ed25519ParseError(#[from] Ed25519RecoveryError), - #[error("Could not parse X25519 data")] + #[error("Could not parse X25519 data - {0}")] X25519ParseError(#[from] KeyRecoveryError), #[error("Could not parse tx hash in request body")] @@ -77,7 +77,7 @@ pub enum CoconutError { CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), #[error("Storage error - {0}")] - StorageError(#[from] ValidatorApiStorageError), + StorageError(#[from] NymApiStorageError), #[error("Credentials error - {0}")] CredentialsError(#[from] credentials::error::Error), diff --git a/validator-api/src/coconut/keypair.rs b/nym-api/src/coconut/keypair.rs similarity index 100% rename from validator-api/src/coconut/keypair.rs rename to nym-api/src/coconut/keypair.rs diff --git a/validator-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs similarity index 91% rename from validator-api/src/coconut/mod.rs rename to nym-api/src/coconut/mod.rs index 25bcb4afa0..ba3afa12b6 100644 --- a/validator-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -13,7 +13,7 @@ pub(crate) mod tests; use crate::coconut::client::Client as LocalClient; use crate::coconut::deposit::extract_encryption_key; use crate::coconut::error::{CoconutError, Result}; -use crate::ValidatorApiStorage; +use crate::NymApiStorage; use coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, @@ -22,19 +22,19 @@ use coconut_interface::KeyPair as CoconutKeyPair; use coconut_interface::{ Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, }; -use config::defaults::VALIDATOR_API_VERSION; +use config::defaults::NYM_API_VERSION; use credentials::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, }; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; use crypto::symmetric::stream_cipher; use keypair::KeyPair; -use validator_api_requests::coconut::{ +use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; +use validator_client::nym_api::routes::{BANDWIDTH, COCONUT_ROUTES}; use validator_client::nymd::{Coin, Fee}; -use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; use getset::{CopyGetters, Getters}; use rand_07::rngs::OsRng; @@ -51,7 +51,7 @@ pub struct State { mix_denom: String, key_pair: KeyPair, comm_channel: Arc, - storage: ValidatorApiStorage, + storage: NymApiStorage, rng: Arc>, } @@ -61,7 +61,7 @@ impl State { mix_denom: String, key_pair: KeyPair, comm_channel: D, - storage: ValidatorApiStorage, + storage: NymApiStorage, ) -> Self where C: LocalClient + Send + Sync + 'static, @@ -100,16 +100,16 @@ impl State { let (keypair, shared_key) = { let mut rng = *self.rng.lock().await; new_ephemeral_shared_key::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, _, >(&mut rng, remote_key) }; let chunk_data = signature.to_bytes(); - let zero_iv = stream_cipher::zero_iv::(); - let encrypted_data = stream_cipher::encrypt::( + let zero_iv = stream_cipher::zero_iv::(); + let encrypted_data = stream_cipher::encrypt::( &shared_key, &zero_iv, &chunk_data, @@ -169,7 +169,7 @@ impl InternalSignRequest { mix_denom: String, key_pair: KeyPair, comm_channel: D, - storage: ValidatorApiStorage, + storage: NymApiStorage, ) -> AdHoc where C: LocalClient + Send + Sync + 'static, @@ -179,10 +179,7 @@ impl InternalSignRequest { AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... - format!( - "/{}/{}/{}", - VALIDATOR_API_VERSION, COCONUT_ROUTES, BANDWIDTH - ), + format!("/{}/{}/{}", NYM_API_VERSION, COCONUT_ROUTES, BANDWIDTH), routes![ post_blind_sign, post_partial_bandwidth_credential, diff --git a/validator-api/src/coconut/tests.rs b/nym-api/src/coconut/tests.rs similarity index 97% rename from validator-api/src/coconut/tests.rs rename to nym-api/src/coconut/tests.rs index cc75a112b2..d0165e1f1e 100644 --- a/validator-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests.rs @@ -15,32 +15,32 @@ use config::defaults::VOUCHER_INFO; use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; use credentials::coconut::bandwidth::BandwidthVoucher; use credentials::coconut::params::{ - ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, }; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; +use nym_api_requests::coconut::{ + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; use nymcoconut::tests::helpers::theta_from_keys_and_attributes; use nymcoconut::{ prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, Parameters, }; -use validator_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, -}; -use validator_client::nymd::Coin; -use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; -use validator_client::validator_api::routes::{ +use validator_client::nym_api::routes::{ API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, }; +use validator_client::nymd::Coin; +use validator_client::nymd::{tx::Hash, AccountId, DeliverTx, Event, Fee, Tag, TxResponse}; use crate::coconut::State; -use crate::ValidatorApiStorage; +use crate::NymApiStorage; use async_trait::async_trait; use coconut_dkg_common::dealer::{ ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, }; use coconut_dkg_common::event_attributes::{DKG_PROPOSAL_ID, NODE_INDEX}; -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState, TOTAL_DEALINGS}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch, TOTAL_DEALINGS}; use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; use contracts_common::dealings::ContractSafeBytes; use crypto::asymmetric::{encryption, identity}; @@ -66,7 +66,7 @@ pub(crate) struct DummyClient { proposal_db: Arc>>, spent_credential_db: Arc>>, - epoch_state: Arc>, + epoch: Arc>, dealer_details: Arc>>, threshold: Arc>>, dealings: Arc>>>, @@ -80,7 +80,7 @@ impl DummyClient { tx_db: Arc::new(RwLock::new(HashMap::new())), proposal_db: Arc::new(RwLock::new(HashMap::new())), spent_credential_db: Arc::new(RwLock::new(HashMap::new())), - epoch_state: Arc::new(RwLock::new(EpochState::default())), + epoch: Arc::new(RwLock::new(Epoch::default())), dealer_details: Arc::new(RwLock::new(HashMap::new())), threshold: Arc::new(RwLock::new(None)), dealings: Arc::new(RwLock::new(HashMap::new())), @@ -109,8 +109,8 @@ impl DummyClient { self } - pub fn _with_epoch_state(mut self, epoch_state: &Arc>) -> Self { - self.epoch_state = Arc::clone(epoch_state); + pub fn _with_epoch(mut self, epoch: &Arc>) -> Self { + self.epoch = Arc::clone(epoch); self } @@ -188,8 +188,8 @@ impl super::client::Client for DummyClient { }) } - async fn get_current_epoch_state(&self) -> Result { - Ok(*self.epoch_state.read().unwrap()) + async fn get_current_epoch(&self) -> Result { + Ok(*self.epoch.read().unwrap()) } async fn get_current_epoch_threshold(&self) -> Result> { @@ -273,6 +273,10 @@ impl super::client::Client for DummyClient { Ok(()) } + async fn advance_epoch_state(&self) -> Result<()> { + todo!() + } + async fn register_dealer( &self, bte_public_key_with_proof: EncodedBTEPublicKeyWithProof, @@ -456,7 +460,7 @@ async fn signed_before() { let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage = NymApiStorage::init(db_dir).await.unwrap(); let tx_db = Arc::new(RwLock::new(HashMap::new())); tx_db .write() @@ -532,7 +536,7 @@ async fn state_functions() { let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage = NymApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let staged_key_pair = crate::coconut::KeyPair::new(); staged_key_pair.set(key_pair).await; @@ -601,11 +605,11 @@ async fn state_functions() { crypto::asymmetric::encryption::PublicKey::from_bytes(&response.remote_key).unwrap(); let encryption_key = recompute_shared_key::< - ValidatorApiCredentialEncryptionAlgorithm, - ValidatorApiCredentialHkdfAlgorithm, + NymApiCredentialEncryptionAlgorithm, + NymApiCredentialHkdfAlgorithm, >(&remote_key, encryption_keypair.private_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, &response.encrypted_signature, @@ -665,7 +669,7 @@ async fn blind_sign_correct() { let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage = NymApiStorage::init(db_dir).await.unwrap(); let tx_db = Arc::new(RwLock::new(HashMap::new())); let mut tx_entry = tx_entry_fixture(&tx_hash.to_string()); @@ -776,7 +780,7 @@ async fn signature_test() { let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage = NymApiStorage::init(db_dir).await.unwrap(); let nymd_client = DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); @@ -865,7 +869,7 @@ async fn verification_of_bandwidth_credential() { theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes).unwrap(); let key_pair = key_pairs.remove(0); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage1 = ValidatorApiStorage::init(db_dir).await.unwrap(); + let storage1 = NymApiStorage::init(db_dir).await.unwrap(); let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); let staged_key_pair = crate::coconut::KeyPair::new(); staged_key_pair.set(key_pair).await; diff --git a/validator-api/src/config/mod.rs b/nym-api/src/config/mod.rs similarity index 98% rename from validator-api/src/config/mod.rs rename to nym-api/src/config/mod.rs index d38acfb546..2e30bf34dd 100644 --- a/validator-api/src/config/mod.rs +++ b/nym-api/src/config/mod.rs @@ -3,7 +3,7 @@ use crate::config::template::config_template; use config::defaults::mainnet::MIXNET_CONTRACT_ADDRESS; -use config::defaults::DEFAULT_VALIDATOR_API_PORT; +use config::defaults::DEFAULT_NYM_API_PORT; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -67,11 +67,11 @@ impl NymConfig for Config { dirs::home_dir() .expect("Failed to evaluate $HOME value") .join(".nym") - .join("validator-api") + .join("nym-api") } fn try_default_root_directory() -> Option { - dirs::home_dir().map(|path| path.join(".nym").join("validator-api")) + dirs::home_dir().map(|path| path.join(".nym").join("nym-api")) } fn root_directory(&self) -> PathBuf { @@ -90,7 +90,7 @@ impl NymConfig for Config { #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(default)] pub struct Base { - /// ID specifies the human readable ID of this particular validator-api. + /// ID specifies the human readable ID of this particular nym-api. id: String, local_validator: Url, @@ -114,7 +114,7 @@ impl Default for Base { .expect("default local validator is malformed!"); let mut default_announce_address = default_validator.clone(); default_announce_address - .set_port(Some(DEFAULT_VALIDATOR_API_PORT)) + .set_port(Some(DEFAULT_NYM_API_PORT)) .expect("default local validator is malformed!"); Base { id: String::default(), diff --git a/validator-api/src/config/template.rs b/nym-api/src/config/template.rs similarity index 97% rename from validator-api/src/config/template.rs rename to nym-api/src/config/template.rs index c2a3670e28..79773f56b7 100644 --- a/validator-api/src/config/template.rs +++ b/nym-api/src/config/template.rs @@ -6,11 +6,11 @@ pub(crate) fn config_template() -> &'static str { # This is a TOML config file. # For more information, see https://github.com/toml-lang/toml -##### main base validator-api config options ##### +##### main base nym-api config options ##### [base] -# ID specifies the human readable ID of this particular validator-api. +# ID specifies the human readable ID of this particular nym-api. id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. diff --git a/validator-api/src/contract_cache/mod.rs b/nym-api/src/contract_cache/mod.rs similarity index 98% rename from validator-api/src/contract_cache/mod.rs rename to nym-api/src/contract_cache/mod.rs index f635933ae6..0775ca5a85 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/nym-api/src/contract_cache/mod.rs @@ -9,6 +9,7 @@ use mixnet_contract_common::{ mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, RewardedSetNodeStatus, }; +use nym_api_requests::models::MixnodeStatus; use okapi::openapi3::OpenApi; use rocket::fairing::AdHoc; use rocket::Route; @@ -20,10 +21,9 @@ use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use tokio::sync::{watch, RwLock}; use tokio::time; -use validator_api_requests::models::MixnodeStatus; use validator_client::nymd::CosmWasmClient; pub(crate) mod routes; @@ -192,13 +192,13 @@ impl ValidatorCacheRefresher { .await; if let Err(err) = self.update_notifier.send(CacheNotification::Updated) { - warn!("Failed to notify validator cache refresh: {}", err); + warn!("Failed to notify validator cache refresh: {err}"); } Ok(()) } - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) + pub(crate) async fn run(&self, mut shutdown: TaskClient) where C: CosmWasmClient + Sync + Send, { @@ -213,7 +213,7 @@ impl ValidatorCacheRefresher { } ret = self.refresh_cache() => { if let Err(err) = ret { - error!("Failed to refresh validator cache - {}", err); + error!("Failed to refresh validator cache - {err}"); } else { // relaxed memory ordering is fine here. worst case scenario network monitor // will just have to wait for an additional backoff to see the change. diff --git a/validator-api/src/contract_cache/routes.rs b/nym-api/src/contract_cache/routes.rs similarity index 98% rename from validator-api/src/contract_cache/routes.rs rename to nym-api/src/contract_cache/routes.rs index 6aba1ba4f9..2d6b71e606 100644 --- a/validator-api/src/contract_cache/routes.rs +++ b/nym-api/src/contract_cache/routes.rs @@ -11,7 +11,7 @@ use crate::{ use mixnet_contract_common::{ mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, Interval, MixId, }; -use validator_api_requests::models::MixNodeBondAnnotated; +use nym_api_requests::models::MixNodeBondAnnotated; use rocket::{serde::json::Json, State}; use rocket_okapi::openapi; diff --git a/validator-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs similarity index 85% rename from validator-api/src/epoch_operations/error.rs rename to nym-api/src/epoch_operations/error.rs index bb94df5f49..4455dfcfcc 100644 --- a/validator-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::node_status_api::models::ValidatorApiStorageError; +use crate::node_status_api::models::NymApiStorageError; use thiserror::Error; use validator_client::nymd::error::NymdError; use validator_client::ValidatorClientError; @@ -15,7 +15,7 @@ pub enum RewardingError { // The inner error should be modified at some point... #[error("We run into storage issues - {0}")] - StorageError(ValidatorApiStorageError), + StorageError(NymApiStorageError), #[error("Failed to query the smart contract - {0}")] ValidatorClientError(ValidatorClientError), @@ -38,8 +38,8 @@ impl From for RewardingError { } } -impl From for RewardingError { - fn from(err: ValidatorApiStorageError) -> Self { +impl From for RewardingError { + fn from(err: NymApiStorageError) -> Self { RewardingError::StorageError(err) } } diff --git a/validator-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs similarity index 100% rename from validator-api/src/epoch_operations/helpers.rs rename to nym-api/src/epoch_operations/helpers.rs diff --git a/validator-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs similarity index 96% rename from validator-api/src/epoch_operations/mod.rs rename to nym-api/src/epoch_operations/mod.rs index 8615393779..7f109d3dea 100644 --- a/validator-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -15,7 +15,7 @@ use crate::contract_cache::ValidatorCache; use crate::nymd_client::Client; use crate::storage::models::RewardingReport; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::{ reward_params::Performance, CurrentIntervalResponse, ExecuteMsg, Interval, MixId, @@ -35,7 +35,7 @@ mod helpers; use crate::epoch_operations::helpers::stake_to_f64; use crate::node_status_api::ONE_DAY; use error::RewardingError; -use task::ShutdownListener; +use task::TaskClient; #[derive(Debug, Clone, Copy)] pub(crate) struct MixnodeToReward { @@ -56,7 +56,7 @@ impl From for ExecuteMsg { pub struct RewardedSetUpdater { nymd_client: Client, validator_cache: ValidatorCache, - storage: ValidatorApiStorage, + storage: NymApiStorage, } // Weight of a layer being chose is reciprocal to current count in layer @@ -79,7 +79,7 @@ impl RewardedSetUpdater { pub(crate) async fn new( nymd_client: Client, validator_cache: ValidatorCache, - storage: ValidatorApiStorage, + storage: NymApiStorage, ) -> Result { Ok(RewardedSetUpdater { nymd_client, @@ -208,7 +208,7 @@ impl RewardedSetUpdater { let rewarded_set: Vec = match self.nymd_client.get_rewarded_set_mixnodes().await { Ok(nodes) => nodes.into_iter().map(|(id, _)| id).collect::>(), Err(err) => { - warn!("failed to obtain the current rewarded set - {}. falling back to the cached version", err); + warn!("failed to obtain the current rewarded set - {err}. falling back to the cached version"); self.validator_cache .rewarded_set() .await @@ -285,7 +285,7 @@ impl RewardedSetUpdater { // Reward all the nodes in the still current, soon to be previous rewarded set log::info!("Rewarding the current rewarded set..."); if let Err(err) = self.reward_current_rewarded_set(interval).await { - log::error!("FAILED to reward rewarded set - {}", err); + log::error!("FAILED to reward rewarded set - {err}"); // since we haven't advanced the epoch yet, we will attempt to reward those nodes again // next time we enter this function (i.e. within 2min or so) // @@ -303,7 +303,7 @@ impl RewardedSetUpdater { log::info!("Reconciling all pending epoch events..."); if let Err(err) = self.nymd_client.reconcile_epoch_events().await { - log::error!("FAILED to reconcile epoch events... - {}", err); + log::error!("FAILED to reconcile epoch events... - {err}"); return Err(err.into()); } else { log::info!("Reconciled all pending epoch events... SUCCESS"); @@ -314,7 +314,7 @@ impl RewardedSetUpdater { .update_rewarded_set_and_advance_epoch(&all_mixnodes) .await { - log::error!("FAILED to advance the current epoch... - {}", err); + log::error!("FAILED to advance the current epoch... - {err}"); return Err(err); } else { log::info!("Advanced the epoch and updated the rewarded set... SUCCESS"); @@ -373,7 +373,7 @@ impl RewardedSetUpdater { Ok(()) } - async fn wait_until_epoch_end(&mut self, shutdown: &mut ShutdownListener) -> Option { + async fn wait_until_epoch_end(&mut self, shutdown: &mut TaskClient) -> Option { const POLL_INTERVAL: Duration = Duration::from_secs(120); loop { @@ -421,10 +421,7 @@ impl RewardedSetUpdater { } } - pub(crate) async fn run( - &mut self, - mut shutdown: ShutdownListener, - ) -> Result<(), RewardingError> { + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) -> Result<(), RewardingError> { self.validator_cache.wait_for_initial_values().await; while !shutdown.is_shutdown() { @@ -434,11 +431,11 @@ impl RewardedSetUpdater { Some(interval) => interval, }; if let Err(err) = self.update_blacklist(&interval_details).await { - error!("failed to update the node blacklist - {}", err); + error!("failed to update the node blacklist - {err}"); continue; } if let Err(err) = self.perform_epoch_operations(interval_details).await { - error!("failed to perform epoch operations - {}", err); + error!("failed to perform epoch operations - {err}"); sleep(Duration::from_secs(30)).await; } } diff --git a/validator-api/src/main.rs b/nym-api/src/main.rs similarity index 92% rename from validator-api/src/main.rs rename to nym-api/src/main.rs index 3055d57f88..256bb527b9 100644 --- a/validator-api/src/main.rs +++ b/nym-api/src/main.rs @@ -9,7 +9,7 @@ use crate::contract_cache::ValidatorCacheRefresher; use crate::network_monitor::NetworkMonitorBuilder; use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; use crate::nymd_client::Client; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use ::config::defaults::mainnet::read_var_if_not_default; use ::config::defaults::setup_env; use ::config::defaults::var_names::{CONFIGURED, MIXNET_CONTRACT_ADDRESS, MIX_DENOM}; @@ -31,7 +31,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use std::{fs, process}; -use task::ShutdownNotifier; +use task::TaskManager; use tokio::sync::Notify; #[cfg(feature = "coconut")] use url::Url; @@ -113,20 +113,20 @@ fn long_version() -> String { fn parse_args() -> ArgMatches { let build_details = long_version(); - let base_app = App::new("Nym Validator API") + let base_app = App::new("Nym API") .version(crate_version!()) .long_version(&*build_details) .author("Nymtech") .arg( Arg::with_name(CONFIG_ENV_FILE) - .help("Path pointing to an env file that configures the validator API") + .help("Path pointing to an env file that configures the Nym API") .long(CONFIG_ENV_FILE) .short('c') .takes_value(true) ) .arg( Arg::with_name(ID) - .help("Id of the validator-api we want to run") + .help("Id of the nym-api we want to run") .long(ID) .takes_value(true) ) @@ -145,13 +145,13 @@ fn parse_args() -> ArgMatches { ) .arg( Arg::with_name(NYMD_VALIDATOR_ARG) - .help("Endpoint to nymd part of the validator from which the monitor will grab nodes to test") + .help("Endpoint to nymd instance from which the monitor will grab nodes to test") .long(NYMD_VALIDATOR_ARG) .takes_value(true) ) .arg(Arg::with_name(MIXNET_CONTRACT_ARG) .long(MIXNET_CONTRACT_ARG) - .help("Address of the validator contract managing the network") + .help("Address of the mixnet contract managing the network") .takes_value(true), ) .arg(Arg::with_name(MNEMONIC_ARG) @@ -174,19 +174,19 @@ fn parse_args() -> ArgMatches { .arg( Arg::with_name(MIN_MIXNODE_RELIABILITY_ARG) .long(MIN_MIXNODE_RELIABILITY_ARG) - .help("Mixnodes with relialability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.") + .help("Mixnodes with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.") .takes_value(true) ) .arg( Arg::with_name(MIN_GATEWAY_RELIABILITY_ARG) .long(MIN_GATEWAY_RELIABILITY_ARG) - .help("Gateways with relialability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.") + .help("Gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.") .takes_value(true) ) .arg( Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME) .long(ENABLED_CREDENTIALS_MODE_ARG_NAME) - .help("Set this validator api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement") + .help("Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement") ); #[cfg(feature = "coconut")] @@ -206,7 +206,7 @@ fn parse_args() -> ArgMatches { base_app.get_matches() } -async fn wait_for_interrupt(mut shutdown: ShutdownNotifier) { +async fn wait_for_interrupt(mut shutdown: TaskManager) { wait_for_signal().await; log::info!("Sending shutdown"); @@ -215,7 +215,7 @@ async fn wait_for_interrupt(mut shutdown: ShutdownNotifier) { log::info!("Waiting for tasks to finish... (Press ctrl-c to force)"); shutdown.wait_for_shutdown().await; - log::info!("Stopping nym validator API"); + log::info!("Stopping nym API"); } #[cfg(unix)] @@ -278,7 +278,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if let Some(raw_validator) = matches.value_of(NYMD_VALIDATOR_ARG) { let parsed = match raw_validator.parse() { Err(err) => { - error!("Passed validator argument is invalid - {}", err); + error!("Passed validator argument is invalid - {err}"); process::exit(1) } Ok(url) => url, @@ -336,7 +336,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { if matches.is_present(WRITE_CONFIG_ARG) { info!("Saving the configuration to a file"); if let Err(err) = config.save_to_file(None) { - error!("Failed to write config to a file - {}", err); + error!("Failed to write config to a file - {err}"); process::exit(1) } } @@ -380,7 +380,7 @@ fn setup_network_monitor<'a>( } // get instances of managed states - let node_status_storage = rocket.state::().unwrap().clone(); + let node_status_storage = rocket.state::().unwrap().clone(); let validator_cache = rocket.state::().unwrap().clone(); Some(NetworkMonitorBuilder::new( @@ -433,7 +433,7 @@ async fn setup_rocket( // This is not a very nice approach. A lazy value would be more suitable, but that's still // a nightly feature: https://github.com/rust-lang/rust/issues/74465 let storage = if cfg!(feature = "coconut") || config.get_network_monitor_enabled() { - Some(ValidatorApiStorage::init(config.get_node_status_api_database_path()).await?) + Some(NymApiStorage::init(config.get_node_status_api_database_path()).await?) } else { None }; @@ -453,7 +453,7 @@ async fn setup_rocket( // see if we should start up network monitor let rocket = if config.get_network_monitor_enabled() { - rocket.attach(storage::ValidatorApiStorage::stage(storage.unwrap())) + rocket.attach(storage::NymApiStorage::stage(storage.unwrap())) } else { rocket }; @@ -490,7 +490,7 @@ fn get_servers() -> Vec { }] } -async fn run_validator_api(matches: ArgMatches) -> Result<()> { +async fn run_nym_api(matches: ArgMatches) -> Result<()> { let system_version = env!("CARGO_PKG_VERSION"); // try to load config from the file, if it doesn't exist, use default values @@ -527,7 +527,7 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { let liftoff_notify = Arc::new(Notify::new()); // We need a bigger timeout - let shutdown = ShutdownNotifier::new(10); + let shutdown = TaskManager::new(10); #[cfg(feature = "coconut")] let coconut_keypair = coconut::keypair::KeyPair::new(); @@ -565,7 +565,7 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { // we're not starting signing client let validator_cache_listener = if config.get_network_monitor_enabled() { // Main storage - let storage = rocket.state::().unwrap().clone(); + let storage = rocket.state::().unwrap().clone(); // setup our daily uptime updater. Note that if network monitor is disabled, then we have // no data for the updates and hence we don't need to start it up @@ -610,8 +610,8 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { // Spawn the node status cache refresher. // It is primarily refreshed in-sync with the validator cache, however provide a fallback // caching interval that is twice the validator cache - let storage = rocket.state::().cloned(); - let mut validator_api_cache_refresher = node_status_api::NodeStatusCacheRefresher::new( + let storage = rocket.state::().cloned(); + let mut nym_api_cache_refresher = node_status_api::NodeStatusCacheRefresher::new( node_status_cache, config.get_caching_interval().saturating_mul(2), validator_cache, @@ -619,12 +619,12 @@ async fn run_validator_api(matches: ArgMatches) -> Result<()> { storage, ); let shutdown_listener = shutdown.subscribe(); - tokio::spawn(async move { validator_api_cache_refresher.run(shutdown_listener).await }); + tokio::spawn(async move { nym_api_cache_refresher.run(shutdown_listener).await }); // launch the rocket! // Rocket handles shutdown on it's own, but its shutdown handling should be incorporated // with that of the rest of the tasks. - // Currently it's runtime is forcefully terminated once the validator-api exits. + // Currently it's runtime is forcefully terminated once the nym-api exits. let shutdown_handle = rocket.shutdown(); tokio::spawn(rocket.launch()); @@ -663,5 +663,5 @@ async fn main() -> Result<()> { .value_of(CONFIG_ENV_FILE) .map(|s| PathBuf::from_str(s).expect("invalid env config file")); setup_env(config_env_file); - run_validator_api(args).await + run_nym_api(args).await } diff --git a/validator-api/src/network_monitor/chunker.rs b/nym-api/src/network_monitor/chunker.rs similarity index 100% rename from validator-api/src/network_monitor/chunker.rs rename to nym-api/src/network_monitor/chunker.rs diff --git a/validator-api/src/network_monitor/gateways_reader.rs b/nym-api/src/network_monitor/gateways_reader.rs similarity index 100% rename from validator-api/src/network_monitor/gateways_reader.rs rename to nym-api/src/network_monitor/gateways_reader.rs diff --git a/validator-api/src/network_monitor/mod.rs b/nym-api/src/network_monitor/mod.rs similarity index 96% rename from validator-api/src/network_monitor/mod.rs rename to nym-api/src/network_monitor/mod.rs index 8b85d5fd5a..ff4d6bd585 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/nym-api/src/network_monitor/mod.rs @@ -6,7 +6,7 @@ use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; use gateway_client::bandwidth::BandwidthController; use std::sync::Arc; -use task::ShutdownNotifier; +use task::TaskManager; use validator_client::nymd::SigningNymdClient; use crate::config::Config; @@ -22,7 +22,7 @@ use crate::network_monitor::monitor::sender::PacketSender; use crate::network_monitor::monitor::summary_producer::SummaryProducer; use crate::network_monitor::monitor::Monitor; use crate::nymd_client::Client; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; pub(crate) mod chunker; pub(crate) mod gateways_reader; @@ -36,7 +36,7 @@ pub(crate) struct NetworkMonitorBuilder<'a> { config: &'a Config, _nymd_client: Client, system_version: String, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, validator_cache: ValidatorCache, } @@ -45,7 +45,7 @@ impl<'a> NetworkMonitorBuilder<'a> { config: &'a Config, _nymd_client: Client, system_version: &str, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, validator_cache: ValidatorCache, ) -> Self { NetworkMonitorBuilder { @@ -142,7 +142,7 @@ impl NetworkMonitorRunnables { // TODO: note, that is not exactly doing what we want, because when // `ReceivedProcessor` is constructed, it already spawns a future // this needs to be refactored! - pub(crate) fn spawn_tasks(self, shutdown: &ShutdownNotifier) { + pub(crate) fn spawn_tasks(self, shutdown: &TaskManager) { let mut packet_receiver = self.packet_receiver; let mut monitor = self.monitor; let shutdown_listener = shutdown.subscribe(); diff --git a/validator-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs similarity index 100% rename from validator-api/src/network_monitor/monitor/gateway_clients_cache.rs rename to nym-api/src/network_monitor/monitor/gateway_clients_cache.rs diff --git a/validator-api/src/network_monitor/monitor/gateways_pinger.rs b/nym-api/src/network_monitor/monitor/gateways_pinger.rs similarity index 98% rename from validator-api/src/network_monitor/monitor/gateways_pinger.rs rename to nym-api/src/network_monitor/monitor/gateways_pinger.rs index 24cd581bf3..986c1db5fb 100644 --- a/validator-api/src/network_monitor/monitor/gateways_pinger.rs +++ b/nym-api/src/network_monitor/monitor/gateways_pinger.rs @@ -7,7 +7,7 @@ use crypto::asymmetric::identity; use crypto::asymmetric::identity::PUBLIC_KEY_LENGTH; use log::{debug, info, trace, warn}; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use tokio::time::{sleep, Instant}; // TODO: should it perhaps be moved to config along other timeout values? @@ -144,7 +144,7 @@ impl GatewayPinger { info!("Pinging all active gateways took {:?}", time_taken); } - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&self, mut shutdown: TaskClient) { while !shutdown.is_shutdown() { tokio::select! { _ = sleep(self.pinging_interval) => { diff --git a/validator-api/src/network_monitor/monitor/mod.rs b/nym-api/src/network_monitor/monitor/mod.rs similarity index 98% rename from validator-api/src/network_monitor/monitor/mod.rs rename to nym-api/src/network_monitor/monitor/mod.rs index e2d1a07128..f0f17bad09 100644 --- a/validator-api/src/network_monitor/monitor/mod.rs +++ b/nym-api/src/network_monitor/monitor/mod.rs @@ -8,11 +8,11 @@ use crate::network_monitor::monitor::sender::PacketSender; use crate::network_monitor::monitor::summary_producer::{SummaryProducer, TestSummary}; use crate::network_monitor::test_packet::TestPacket; use crate::network_monitor::test_route::TestRoute; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use log::{debug, error, info}; use std::collections::{HashMap, HashSet}; use std::process; -use task::ShutdownListener; +use task::TaskClient; use tokio::time::{sleep, Duration, Instant}; pub(crate) mod gateway_clients_cache; @@ -29,7 +29,7 @@ pub(super) struct Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, run_interval: Duration, gateway_ping_interval: Duration, packet_delivery_timeout: Duration, @@ -52,7 +52,7 @@ impl Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - node_status_storage: ValidatorApiStorage, + node_status_storage: NymApiStorage, ) -> Self { Monitor { test_nonce: 1, @@ -301,7 +301,7 @@ impl Monitor { self.test_nonce += 1; } - pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) { self.received_processor.start_receiving(); // wait for validator cache to be ready diff --git a/validator-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs similarity index 100% rename from validator-api/src/network_monitor/monitor/preparer.rs rename to nym-api/src/network_monitor/monitor/preparer.rs diff --git a/validator-api/src/network_monitor/monitor/processor.rs b/nym-api/src/network_monitor/monitor/processor.rs similarity index 83% rename from validator-api/src/network_monitor/monitor/processor.rs rename to nym-api/src/network_monitor/monitor/processor.rs index 7b0649a49d..4a86c52612 100644 --- a/validator-api/src/network_monitor/monitor/processor.rs +++ b/nym-api/src/network_monitor/monitor/processor.rs @@ -2,45 +2,39 @@ // SPDX-License-Identifier: Apache-2.0 use crate::network_monitor::gateways_reader::GatewayMessages; -use crate::network_monitor::test_packet::TestPacket; +use crate::network_monitor::test_packet::{TestPacket, TestPacketError}; use crate::network_monitor::ROUTE_TESTING_TEST_NONCE; use crypto::asymmetric::encryption; use futures::channel::mpsc; use futures::lock::{Mutex, MutexGuard}; use futures::{SinkExt, StreamExt}; use log::warn; -use nymsphinx::receiver::MessageReceiver; -use std::fmt::{self, Display, Formatter}; +use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError}; use std::mem; use std::sync::Arc; +use thiserror::Error; pub(crate) type ReceivedProcessorSender = mpsc::UnboundedSender; pub(crate) type ReceivedProcessorReceiver = mpsc::UnboundedReceiver; -#[derive(Debug)] +#[derive(Error, Debug)] enum ProcessingError { - MalformedPacketReceived, - NonTestPacketReceived, - NonMatchingNonce(u64), - ReceivedOutsideTestRun, -} + #[error( + "could not recover underlying data from the received packet since it was malformed - {0}" + )] + MalformedPacketReceived(#[from] MessageRecoveryError), -impl Display for ProcessingError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ProcessingError::MalformedPacketReceived => write!(f, "received malformed packet"), - ProcessingError::NonTestPacketReceived => write!(f, "received a non-test packet"), - ProcessingError::NonMatchingNonce(nonce) => write!( - f, - "received packet with nonce {} which is different than the expected", - nonce - ), - ProcessingError::ReceivedOutsideTestRun => write!( - f, - "received packet while the test is currently not in progress" - ), - } - } + #[error("received a mix packet that was NOT a proper network monitor test packet")] + NonTestPacketReceived, + + #[error("the received test packet was malformed - {0}")] + MalformedTestPacket(#[from] TestPacketError), + + #[error("received packet with an unexpected nonce. Got: {received}, expected: {expected}")] + NonMatchingNonce { received: u64, expected: u64 }, + + #[error("received a mix packet while no test run is currently in progress")] + ReceivedOutsideTestRun, } // we can't use Notify due to possible edge case where both notification are consumed at once @@ -80,23 +74,20 @@ impl ReceivedProcessorInner { .recover_plaintext_from_regular_packet( self.client_encryption_keypair.private_key(), &mut message, - ) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; - let fragment = self - .message_receiver - .recover_fragment(plaintext) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; + )?; + let fragment = self.message_receiver.recover_fragment(plaintext)?; let (recovered, _) = self .message_receiver - .insert_new_fragment(fragment) - .map_err(|_| ProcessingError::MalformedPacketReceived)? + .insert_new_fragment(fragment)? .ok_or(ProcessingError::NonTestPacketReceived)?; // if it's a test packet it MUST BE reconstructed with single fragment - let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data()) - .map_err(|_| ProcessingError::MalformedPacketReceived)?; + let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data())?; // we know nonce is NOT none if test_packet.test_nonce() != self.test_nonce.unwrap() { - return Err(ProcessingError::NonMatchingNonce(test_packet.test_nonce())); + return Err(ProcessingError::NonMatchingNonce { + received: test_packet.test_nonce(), + expected: self.test_nonce.unwrap(), + }); } self.received_packets.push(test_packet); @@ -162,7 +153,7 @@ impl ReceivedProcessor { Some(messages) => { for message in messages { if let Err(err) = inner.on_message(message) { - warn!(target: "Monitor", "failed to process received gateway message - {}", err) + warn!(target: "Monitor", "failed to process received gateway message - {err}") } } } diff --git a/validator-api/src/network_monitor/monitor/receiver.rs b/nym-api/src/network_monitor/monitor/receiver.rs similarity index 96% rename from validator-api/src/network_monitor/monitor/receiver.rs rename to nym-api/src/network_monitor/monitor/receiver.rs index 991da5fa05..c232458c6f 100644 --- a/validator-api/src/network_monitor/monitor/receiver.rs +++ b/nym-api/src/network_monitor/monitor/receiver.rs @@ -7,7 +7,7 @@ use crypto::asymmetric::identity; use futures::channel::mpsc; use futures::StreamExt; use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver}; -use task::ShutdownListener; +use task::TaskClient; pub(crate) type GatewayClientUpdateSender = mpsc::UnboundedSender; pub(crate) type GatewayClientUpdateReceiver = mpsc::UnboundedReceiver; @@ -56,7 +56,7 @@ impl PacketReceiver { .expect("packet processor seems to have crashed!"); } - pub(crate) async fn run(&mut self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&mut self, mut shutdown: TaskClient) { while !shutdown.is_shutdown() { tokio::select! { biased; diff --git a/validator-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs similarity index 99% rename from validator-api/src/network_monitor/monitor/sender.rs rename to nym-api/src/network_monitor/monitor/sender.rs index d2fa815a11..5c6f3df22d 100644 --- a/validator-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -24,7 +24,7 @@ use std::pin::Pin; use std::sync::Arc; use std::task::Poll; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use gateway_client::bandwidth::BandwidthController; @@ -166,11 +166,7 @@ impl PacketSender { } } - pub(crate) fn spawn_gateways_pinger( - &self, - pinging_interval: Duration, - shutdown: ShutdownListener, - ) { + pub(crate) fn spawn_gateways_pinger(&self, pinging_interval: Duration, shutdown: TaskClient) { let gateway_pinger = GatewayPinger::new( self.active_gateway_clients.clone(), self.fresh_gateway_client_data @@ -209,7 +205,7 @@ impl PacketSender { ack_sender, fresh_gateway_client_data.gateway_response_timeout, Some(fresh_gateway_client_data.bandwidth_controller.clone()), - task::ShutdownListener::dummy(), + task::TaskClient::dummy(), ); gateway_client diff --git a/validator-api/src/network_monitor/monitor/summary_producer.rs b/nym-api/src/network_monitor/monitor/summary_producer.rs similarity index 100% rename from validator-api/src/network_monitor/monitor/summary_producer.rs rename to nym-api/src/network_monitor/monitor/summary_producer.rs diff --git a/validator-api/src/network_monitor/test_packet.rs b/nym-api/src/network_monitor/test_packet.rs similarity index 88% rename from validator-api/src/network_monitor/test_packet.rs rename to nym-api/src/network_monitor/test_packet.rs index 8f5e0bde5e..4806c3efe4 100644 --- a/validator-api/src/network_monitor/test_packet.rs +++ b/nym-api/src/network_monitor/test_packet.rs @@ -3,12 +3,14 @@ use crate::network_monitor::monitor::preparer::TestedNode; use crypto::asymmetric::identity; +use crypto::asymmetric::identity::Ed25519RecoveryError; use mixnet_contract_common::MixId; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::mem; use std::str::Utf8Error; +use thiserror::Error; use topology::{gateway, mix}; const MIXNODE_TYPE: u8 = 0; @@ -67,24 +69,25 @@ impl NodeType { } } -#[derive(Debug)] +#[derive(Debug, Error)] pub(crate) enum TestPacketError { - IncompletePacket, + #[error( + "the received packet was incomplete. Got {received} bytes but expected at least {min_expected}" + )] + IncompletePacket { + received: usize, + min_expected: usize, + }, + + // TODO: ideally this should contain more information but that'd require more refactoring than I'm willing to commit to now + #[error("the received packet did not contain a valid node type")] InvalidNodeType, - InvalidNodeKey, - InvalidOwner(Utf8Error), -} -impl From for TestPacketError { - fn from(_: identity::Ed25519RecoveryError) -> Self { - TestPacketError::InvalidNodeKey - } -} + #[error("the received node identity key was malformed - {0}")] + InvalidNodeKey(#[from] Ed25519RecoveryError), -impl From for TestPacketError { - fn from(err: Utf8Error) -> Self { - TestPacketError::InvalidOwner(err) - } + #[error("the received packet contained malformed owner data - {0}")] + InvalidOwner(#[from] Utf8Error), } #[derive(Eq, Clone, Debug)] @@ -184,7 +187,10 @@ impl TestPacket { let n = mem::size_of::(); if b.len() < 2 * n + 1 + identity::PUBLIC_KEY_LENGTH { - return Err(TestPacketError::IncompletePacket); + return Err(TestPacketError::IncompletePacket { + received: b.len(), + min_expected: 2 * n + 1 + identity::PUBLIC_KEY_LENGTH, + }); } // those unwraps can't fail as we've already checked for the size diff --git a/validator-api/src/network_monitor/test_route/mod.rs b/nym-api/src/network_monitor/test_route/mod.rs similarity index 100% rename from validator-api/src/network_monitor/test_route/mod.rs rename to nym-api/src/network_monitor/test_route/mod.rs diff --git a/validator-api/src/node_status_api/cache.rs b/nym-api/src/node_status_api/cache.rs similarity index 97% rename from validator-api/src/node_status_api/cache.rs rename to nym-api/src/node_status_api/cache.rs index 289a75a3f5..5a76ffd85a 100644 --- a/validator-api/src/node_status_api/cache.rs +++ b/nym-api/src/node_status_api/cache.rs @@ -2,22 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use crate::contract_cache::{Cache, CacheNotification, ValidatorCache}; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::reward_params::Performance; use mixnet_contract_common::{ IdentityKey, Interval, MixId, MixNodeDetails, RewardedSetNodeStatus, RewardingParams, }; +use nym_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus}; use rocket::fairing::AdHoc; use std::collections::HashMap; use std::{sync::Arc, time::Duration}; -use task::ShutdownListener; +use task::TaskClient; use tokio::sync::RwLockReadGuard; use tokio::{ sync::{watch, RwLock}, time, }; -use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus}; use self::inclusion_probabilities::InclusionProbabilities; @@ -154,7 +154,7 @@ pub struct NodeStatusCacheRefresher { // Sources for when refreshing data contract_cache: ValidatorCache, contract_cache_listener: watch::Receiver, - storage: Option, + storage: Option, } impl NodeStatusCacheRefresher { @@ -163,7 +163,7 @@ impl NodeStatusCacheRefresher { fallback_caching_interval: Duration, contract_cache: ValidatorCache, contract_cache_listener: watch::Receiver, - storage: Option, + storage: Option, ) -> Self { Self { cache, @@ -174,7 +174,7 @@ impl NodeStatusCacheRefresher { } } - pub async fn run(&mut self, mut shutdown: ShutdownListener) { + pub async fn run(&mut self, mut shutdown: TaskClient) { let mut fallback_interval = time::interval(self.fallback_caching_interval); while !shutdown.is_shutdown() { tokio::select! { @@ -320,7 +320,7 @@ impl NodeStatusCacheRefresher { .rewarding_details .uncapped_bond_saturation(&interval_reward_params); - // If the performance can't be obtained, because the validator-api was not started with + // If the performance can't be obtained, because the nym-api was not started with // the monitoring (and hence, storage), then reward estimates will be all zero let performance = self .get_performance_from_storage(mixnode.mix_id(), current_interval) diff --git a/validator-api/src/node_status_api/cache/inclusion_probabilities.rs b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs similarity index 98% rename from validator-api/src/node_status_api/cache/inclusion_probabilities.rs rename to nym-api/src/node_status_api/cache/inclusion_probabilities.rs index 21c1bdcab5..80643dae27 100644 --- a/validator-api/src/node_status_api/cache/inclusion_probabilities.rs +++ b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs @@ -1,9 +1,9 @@ use contracts_common::truncate_decimal; use mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams}; +use nym_api_requests::models::InclusionProbability; use serde::Serialize; use std::time::Duration; use tap::TapFallible; -use validator_api_requests::models::InclusionProbability; const MAX_SIMULATION_SAMPLES: u64 = 5000; const MAX_SIMULATION_TIME_SEC: u64 = 15; diff --git a/validator-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs similarity index 97% rename from validator-api/src/node_status_api/helpers.rs rename to nym-api/src/node_status_api/helpers.rs index 4b084533bc..a2d102ebdd 100644 --- a/validator-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -3,24 +3,24 @@ use crate::contract_cache::Cache; use crate::node_status_api::models::ErrorResponse; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use crate::{NodeStatusCache, ValidatorCache}; use cosmwasm_std::Decimal; use mixnet_contract_common::reward_params::Performance; use mixnet_contract_common::{Interval, MixId, RewardedSetNodeStatus}; -use rocket::http::Status; -use rocket::State; -use validator_api_requests::models::{ +use nym_api_requests::models::{ AllInclusionProbabilitiesResponse, ComputeRewardEstParam, InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; +use rocket::http::Status; +use rocket::State; use super::reward_estimate::compute_reward_estimate; pub(crate) async fn _mixnode_report( - storage: &ValidatorApiStorage, + storage: &NymApiStorage, mix_id: MixId, ) -> Result { storage @@ -31,7 +31,7 @@ pub(crate) async fn _mixnode_report( } pub(crate) async fn _mixnode_uptime_history( - storage: &ValidatorApiStorage, + storage: &NymApiStorage, mix_id: MixId, ) -> Result { storage @@ -42,7 +42,7 @@ pub(crate) async fn _mixnode_uptime_history( } pub(crate) async fn _mixnode_core_status_count( - storage: &State, + storage: &State, mix_id: MixId, since: Option, ) -> Result { @@ -106,7 +106,7 @@ pub(crate) async fn _get_mixnode_reward_estimation( async fn average_mixnode_performance( mix_id: MixId, current_interval: Interval, - storage: &ValidatorApiStorage, + storage: &NymApiStorage, ) -> Result { storage .get_average_mixnode_uptime_in_the_last_24hrs( @@ -255,7 +255,7 @@ pub(crate) async fn _get_mixnode_inclusion_probability( pub(crate) async fn _get_mixnode_avg_uptime( cache: &ValidatorCache, - storage: &ValidatorApiStorage, + storage: &NymApiStorage, mix_id: MixId, ) -> Result { let current_interval = cache diff --git a/validator-api/src/node_status_api/local_guard.rs b/nym-api/src/node_status_api/local_guard.rs similarity index 100% rename from validator-api/src/node_status_api/local_guard.rs rename to nym-api/src/node_status_api/local_guard.rs diff --git a/validator-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs similarity index 100% rename from validator-api/src/node_status_api/mod.rs rename to nym-api/src/node_status_api/mod.rs diff --git a/validator-api/src/node_status_api/models.rs b/nym-api/src/node_status_api/models.rs similarity index 84% rename from validator-api/src/node_status_api/models.rs rename to nym-api/src/node_status_api/models.rs index ae9661bf23..43a11627b3 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/nym-api/src/node_status_api/models.rs @@ -5,6 +5,10 @@ use crate::node_status_api::utils::NodeUptimes; use crate::storage::models::NodeStatus; use mixnet_contract_common::reward_params::Performance; use mixnet_contract_common::{IdentityKey, MixId}; +use nym_api_requests::models::{ + GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse, + MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, RequestError, +}; use okapi::openapi3::{Responses, SchemaObject}; use rocket::http::Status; use rocket::response::{self, Responder, Response}; @@ -17,18 +21,15 @@ use schemars::gen::SchemaGenerator; use schemars::schema::{InstanceType, Schema}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sqlx::Error; use std::convert::TryFrom; -use std::fmt::{self, Display, Formatter}; +use thiserror::Error; use time::OffsetDateTime; -use validator_api_requests::models::{ - GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse, - MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, RequestError, -}; -// todo: put into some error enum -#[derive(Debug)] -pub struct InvalidUptime; +#[derive(Error, Debug)] +#[error("Received uptime value was within 0-100 range (got {received})")] +pub struct InvalidUptime { + received: isize, +} // value in range 0-100 #[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, JsonSchema)] @@ -56,7 +57,9 @@ impl Uptime { let uptime = ((numerator as f32 / denominator as f32) * 100.0).round() as u8; if uptime > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: uptime as isize, + }) } else { Ok(Uptime(uptime)) } @@ -70,7 +73,9 @@ impl Uptime { let uptime = (running_sum / count as f32).round() as u8; if uptime > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: uptime as isize, + }) } else { Ok(Uptime(uptime)) } @@ -92,7 +97,9 @@ impl TryFrom for Uptime { fn try_from(value: u8) -> Result { if value > 100 { - Err(InvalidUptime) + Err(InvalidUptime { + received: value as isize, + }) } else { Ok(Uptime(value)) } @@ -104,7 +111,9 @@ impl TryFrom for Uptime { fn try_from(value: i64) -> Result { if !(0..=100).contains(&value) { - Err(InvalidUptime) + Err(InvalidUptime { + received: value as isize, + }) } else { Ok(Uptime(value as u8)) } @@ -364,48 +373,28 @@ impl OpenApiResponderInner for ErrorResponse { } #[derive(Debug, thiserror::Error)] -pub enum ValidatorApiStorageError { - MixnodeReportNotFound(MixId), - GatewayReportNotFound(String), - MixnodeUptimeHistoryNotFound(MixId), - GatewayUptimeHistoryNotFound(String), +pub enum NymApiStorageError { + #[error("could not find status report associated with mixnode {mix_id}")] + MixnodeReportNotFound { mix_id: MixId }, + + #[error("Could not find status report associated with gateway {identity}")] + GatewayReportNotFound { identity: IdentityKey }, + + #[error("could not find uptime history associated with mixnode {mix_id}")] + MixnodeUptimeHistoryNotFound { mix_id: MixId }, + + #[error("could not find uptime history associated with gateway {identity}")] + GatewayUptimeHistoryNotFound { identity: IdentityKey }, // I don't think we want to expose errors to the user about what really happened - InternalDatabaseError(String), -} + #[error("experienced internal database error")] + InternalDatabaseError(#[from] sqlx::Error), -impl From for ValidatorApiStorageError { - fn from(err: Error) -> Self { - ValidatorApiStorageError::InternalDatabaseError(err.to_string()) - } -} + // the same is true here (also note that the message is subtly different so we would be able to distinguish them) + #[error("experienced internal storage error")] + DatabaseInconsistency { reason: String }, -impl Display for ValidatorApiStorageError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - ValidatorApiStorageError::MixnodeReportNotFound(mix_id) => write!( - f, - "Could not find status report associated with mixnode {}", - mix_id - ), - ValidatorApiStorageError::GatewayReportNotFound(identity) => write!( - f, - "Could not find status report associated with gateway {}", - identity - ), - ValidatorApiStorageError::MixnodeUptimeHistoryNotFound(mix_id) => write!( - f, - "Could not find uptime history associated with mixnode {}", - mix_id - ), - ValidatorApiStorageError::GatewayUptimeHistoryNotFound(identity) => write!( - f, - "Could not find uptime history associated with gateway {}", - identity - ), - ValidatorApiStorageError::InternalDatabaseError(err) => { - write!(f, "The internal database has experienced an issue: {err}") - } - } - } + // this one would never be returned to users since it's only possible on startup + #[error("failed to perform startup SQL migration - {0}")] + StartupMigrationFailure(#[from] sqlx::migrate::MigrateError), } diff --git a/validator-api/src/node_status_api/reward_estimate.rs b/nym-api/src/node_status_api/reward_estimate.rs similarity index 100% rename from validator-api/src/node_status_api/reward_estimate.rs rename to nym-api/src/node_status_api/reward_estimate.rs diff --git a/validator-api/src/node_status_api/routes.rs b/nym-api/src/node_status_api/routes.rs similarity index 94% rename from validator-api/src/node_status_api/routes.rs rename to nym-api/src/node_status_api/routes.rs index 037ca7b117..70bb3db91e 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/nym-api/src/node_status_api/routes.rs @@ -10,25 +10,25 @@ use crate::node_status_api::helpers::{ _mixnode_report, _mixnode_uptime_history, }; use crate::node_status_api::models::ErrorResponse; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use crate::ValidatorCache; use mixnet_contract_common::MixId; -use rocket::http::Status; -use rocket::serde::json::Json; -use rocket::State; -use rocket_okapi::openapi; -use validator_api_requests::models::{ +use nym_api_requests::models::{ AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; +use rocket::http::Status; +use rocket::serde::json::Json; +use rocket::State; +use rocket_okapi::openapi; #[openapi(tag = "status")] #[get("/gateway//report")] pub(crate) async fn gateway_report( - storage: &State, + storage: &State, identity: &str, ) -> Result, ErrorResponse> { storage @@ -42,7 +42,7 @@ pub(crate) async fn gateway_report( #[openapi(tag = "status")] #[get("/gateway//history")] pub(crate) async fn gateway_uptime_history( - storage: &State, + storage: &State, identity: &str, ) -> Result, ErrorResponse> { storage @@ -56,7 +56,7 @@ pub(crate) async fn gateway_uptime_history( #[openapi(tag = "status")] #[get("/gateway//core-status-count?")] pub(crate) async fn gateway_core_status_count( - storage: &State, + storage: &State, identity: &str, since: Option, ) -> Json { @@ -74,7 +74,7 @@ pub(crate) async fn gateway_core_status_count( #[openapi(tag = "status")] #[get("/mixnode//report")] pub(crate) async fn mixnode_report( - storage: &State, + storage: &State, mix_id: MixId, ) -> Result, ErrorResponse> { Ok(Json(_mixnode_report(storage, mix_id).await?)) @@ -83,7 +83,7 @@ pub(crate) async fn mixnode_report( #[openapi(tag = "status")] #[get("/mixnode//history")] pub(crate) async fn mixnode_uptime_history( - storage: &State, + storage: &State, mix_id: MixId, ) -> Result, ErrorResponse> { Ok(Json(_mixnode_uptime_history(storage, mix_id).await?)) @@ -92,7 +92,7 @@ pub(crate) async fn mixnode_uptime_history( #[openapi(tag = "status")] #[get("/mixnode//core-status-count?")] pub(crate) async fn mixnode_core_status_count( - storage: &State, + storage: &State, mix_id: MixId, since: Option, ) -> Result, ErrorResponse> { @@ -171,7 +171,7 @@ pub(crate) async fn get_mixnode_inclusion_probability( #[get("/mixnode//avg_uptime")] pub(crate) async fn get_mixnode_avg_uptime( cache: &State, - storage: &State, + storage: &State, mix_id: MixId, ) -> Result, ErrorResponse> { Ok(Json(_get_mixnode_avg_uptime(cache, storage, mix_id).await?)) diff --git a/validator-api/src/node_status_api/uptime_updater.rs b/nym-api/src/node_status_api/uptime_updater.rs similarity index 90% rename from validator-api/src/node_status_api/uptime_updater.rs rename to nym-api/src/node_status_api/uptime_updater.rs index dcb4f6424c..0c36c1a124 100644 --- a/validator-api/src/node_status_api/uptime_updater.rs +++ b/nym-api/src/node_status_api/uptime_updater.rs @@ -2,22 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node_status_api::models::{ - GatewayStatusReport, MixnodeStatusReport, ValidatorApiStorageError, + GatewayStatusReport, MixnodeStatusReport, NymApiStorageError, }; use crate::node_status_api::ONE_DAY; -use crate::storage::ValidatorApiStorage; +use crate::storage::NymApiStorage; use log::error; use std::time::Duration; -use task::ShutdownListener; +use task::TaskClient; use time::{OffsetDateTime, PrimitiveDateTime, Time}; use tokio::time::{interval, sleep}; pub(crate) struct HistoricalUptimeUpdater { - storage: ValidatorApiStorage, + storage: NymApiStorage, } impl HistoricalUptimeUpdater { - pub(crate) fn new(storage: ValidatorApiStorage) -> Self { + pub(crate) fn new(storage: NymApiStorage) -> Self { HistoricalUptimeUpdater { storage } } @@ -30,8 +30,7 @@ impl HistoricalUptimeUpdater { async fn get_active_nodes( &self, now: OffsetDateTime, - ) -> Result<(Vec, Vec), ValidatorApiStorageError> - { + ) -> Result<(Vec, Vec), NymApiStorageError> { let day_ago = (now - ONE_DAY).unix_timestamp(); let active_mixnodes = self .storage @@ -46,7 +45,7 @@ impl HistoricalUptimeUpdater { Ok((active_mixnodes, active_gateways)) } - async fn update_uptimes(&self) -> Result<(), ValidatorApiStorageError> { + async fn update_uptimes(&self) -> Result<(), NymApiStorageError> { let now = OffsetDateTime::now_utc(); let today_iso_8601 = now.date().to_string(); @@ -69,7 +68,7 @@ impl HistoricalUptimeUpdater { Ok(()) } - pub(crate) async fn run(&self, mut shutdown: ShutdownListener) { + pub(crate) async fn run(&self, mut shutdown: TaskClient) { // update uptimes at 23:00 UTC each day so that we'd have data from the actual [almost] whole day // and so that we would avoid the edge case of starting validator API at 23:59 and having some // nodes update for different days diff --git a/validator-api/src/node_status_api/utils.rs b/nym-api/src/node_status_api/utils.rs similarity index 100% rename from validator-api/src/node_status_api/utils.rs rename to nym-api/src/node_status_api/utils.rs diff --git a/validator-api/src/nymd_client.rs b/nym-api/src/nymd_client.rs similarity index 95% rename from validator-api/src/nymd_client.rs rename to nym-api/src/nymd_client.rs index 1f126d6fdc..f7780b2871 100644 --- a/validator-api/src/nymd_client.rs +++ b/nym-api/src/nymd_client.rs @@ -3,7 +3,7 @@ use crate::config::Config; use crate::epoch_operations::MixnodeToReward; -use config::defaults::{NymNetworkDetails, DEFAULT_VALIDATOR_API_PORT}; +use config::defaults::{NymNetworkDetails, DEFAULT_NYM_API_PORT}; use mixnet_contract_common::families::{Family, FamilyHead}; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::reward_params::RewardingParams; @@ -30,7 +30,7 @@ use coconut_bandwidth_contract_common::spend_credential::SpendCredentialResponse #[cfg(feature = "coconut")] use coconut_dkg_common::dealer::{ContractDealing, DealerDetails, DealerDetailsResponse}; #[cfg(feature = "coconut")] -use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; +use coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, Epoch}; #[cfg(feature = "coconut")] use coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; #[cfg(feature = "coconut")] @@ -59,7 +59,7 @@ impl Client { pub(crate) fn new_query(config: &Config) -> Self { // the api address is irrelevant here as **WE ARE THE API** // and we won't be talking on the socket here. - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) + let api_url = format!("http://localhost:{}", DEFAULT_NYM_API_PORT) .parse() .unwrap(); let nymd_url = config.get_nymd_validator_url(); @@ -82,7 +82,7 @@ impl Client { pub(crate) fn new_signing(config: &Config) -> Self { // the api address is irrelevant here as **WE ARE THE API** // and we won't be talking on the socket here. - let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) + let api_url = format!("http://localhost:{}", DEFAULT_NYM_API_PORT) .parse() .unwrap(); let nymd_url = config.get_nymd_validator_url(); @@ -329,8 +329,8 @@ where .await?) } - async fn get_current_epoch_state(&self) -> crate::coconut::error::Result { - Ok(self.0.read().await.nymd.get_current_epoch_state().await?) + async fn get_current_epoch(&self) -> crate::coconut::error::Result { + Ok(self.0.read().await.nymd.get_current_epoch().await?) } async fn get_current_epoch_threshold( @@ -405,6 +405,16 @@ where Ok(()) } + async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> { + self.0 + .write() + .await + .nymd + .advance_dkg_epoch_state(None) + .await?; + Ok(()) + } + async fn register_dealer( &self, bte_key: EncodedBTEPublicKeyWithProof, diff --git a/validator-api/src/storage/manager.rs b/nym-api/src/storage/manager.rs similarity index 100% rename from validator-api/src/storage/manager.rs rename to nym-api/src/storage/manager.rs diff --git a/validator-api/src/storage/mod.rs b/nym-api/src/storage/mod.rs similarity index 76% rename from validator-api/src/storage/mod.rs rename to nym-api/src/storage/mod.rs index 21a5b2ca23..1d513defdf 100644 --- a/validator-api/src/storage/mod.rs +++ b/nym-api/src/storage/mod.rs @@ -4,8 +4,8 @@ use crate::network_monitor::monitor::summary_producer::{GatewayResult, MixnodeResult}; use crate::network_monitor::test_route::TestRoute; use crate::node_status_api::models::{ - GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, Uptime, - ValidatorApiStorageError, + GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, + NymApiStorageError, Uptime, }; use crate::node_status_api::{ONE_DAY, ONE_HOUR}; use crate::storage::manager::StorageManager; @@ -23,13 +23,13 @@ pub(crate) mod models; // note that clone here is fine as upon cloning the same underlying pool will be used #[derive(Clone)] -pub(crate) struct ValidatorApiStorage { +pub(crate) struct NymApiStorage { pub manager: StorageManager, } -impl ValidatorApiStorage { - pub async fn init(database_path: PathBuf) -> Result { - // TODO: we can inject here more stuff based on our validator-api global config +impl NymApiStorage { + pub async fn init(database_path: PathBuf) -> Result { + // TODO: we can inject here more stuff based on our nym-api global config // struct. Maybe different pool size or timeout intervals? let mut opts = sqlx::sqlite::SqliteConnectOptions::new() .filename(&database_path) @@ -41,31 +41,27 @@ impl ValidatorApiStorage { let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { Ok(db) => db, - Err(e) => { - error!("Failed to connect to SQLx database: {}", e); - return Err(ValidatorApiStorageError::InternalDatabaseError( - e.to_string(), - )); + Err(err) => { + error!("Failed to connect to SQLx database: {err}"); + return Err(err.into()); } }; - if let Err(e) = sqlx::migrate!("./migrations").run(&connection_pool).await { - error!("Failed to initialize SQLx database: {}", e); - return Err(ValidatorApiStorageError::InternalDatabaseError( - e.to_string(), - )); + if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await { + error!("Failed to initialize SQLx database: {err}"); + return Err(err.into()); } info!("Database migration finished!"); - let storage = ValidatorApiStorage { + let storage = NymApiStorage { manager: StorageManager { connection_pool }, }; Ok(storage) } - pub(crate) fn stage(storage: ValidatorApiStorage) -> AdHoc { + pub(crate) fn stage(storage: NymApiStorage) -> AdHoc { AdHoc::try_on_ignite("SQLx Database", |rocket| async { Ok(rocket.manage(storage)) }) @@ -75,7 +71,7 @@ impl ValidatorApiStorage { pub(crate) async fn mix_identity_to_mix_ids( &self, identity: &str, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { Ok(self .manager .get_mixnode_mix_ids_by_identity(identity) @@ -86,7 +82,7 @@ impl ValidatorApiStorage { pub(crate) async fn mix_identity_to_latest_mix_id( &self, identity: &str, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { Ok(self .mix_identity_to_mix_ids(identity) .await? @@ -97,7 +93,7 @@ impl ValidatorApiStorage { pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr( &self, end_ts_secs: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { let result = self .manager .get_all_avg_gateway_reliability_in_last_24hr(end_ts_secs) @@ -109,7 +105,7 @@ impl ValidatorApiStorage { pub(crate) async fn get_all_avg_mix_reliability_in_last_24hr( &self, end_ts_secs: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { let result = self .manager .get_all_avg_mix_reliability_in_last_24hr(end_ts_secs) @@ -129,7 +125,7 @@ impl ValidatorApiStorage { &self, mix_id: MixId, since: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { let statuses = self .manager .get_mixnode_statuses_since(mix_id, since) @@ -149,7 +145,7 @@ impl ValidatorApiStorage { &self, identity: &str, since: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { let statuses = self .manager .get_gateway_statuses_since(identity, since) @@ -166,7 +162,7 @@ impl ValidatorApiStorage { pub(crate) async fn construct_mixnode_report( &self, mix_id: MixId, - ) -> Result { + ) -> Result { let now = OffsetDateTime::now_utc(); let day_ago = (now - ONE_DAY).unix_timestamp(); let hour_ago = (now - ONE_HOUR).unix_timestamp(); @@ -175,7 +171,7 @@ impl ValidatorApiStorage { // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if statuses.is_empty() { - return Err(ValidatorApiStorageError::MixnodeReportNotFound(mix_id)); + return Err(NymApiStorageError::MixnodeReportNotFound { mix_id }); } // determine the number of runs the mixnode should have been online for @@ -210,7 +206,7 @@ impl ValidatorApiStorage { pub(crate) async fn construct_gateway_report( &self, identity: &str, - ) -> Result { + ) -> Result { let now = OffsetDateTime::now_utc(); let day_ago = (now - ONE_DAY).unix_timestamp(); let hour_ago = (now - ONE_HOUR).unix_timestamp(); @@ -219,9 +215,9 @@ impl ValidatorApiStorage { // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if statuses.is_empty() { - return Err(ValidatorApiStorageError::GatewayReportNotFound( - identity.to_owned(), - )); + return Err(NymApiStorageError::GatewayReportNotFound { + identity: identity.to_owned(), + }); } // determine the number of runs the gateway should have been online for @@ -249,13 +245,11 @@ impl ValidatorApiStorage { pub(crate) async fn get_mixnode_uptime_history( &self, mix_id: MixId, - ) -> Result { + ) -> Result { let history = self.manager.get_mixnode_historical_uptimes(mix_id).await?; if history.is_empty() { - return Err(ValidatorApiStorageError::MixnodeUptimeHistoryNotFound( - mix_id, - )); + return Err(NymApiStorageError::MixnodeUptimeHistoryNotFound { mix_id }); } let mixnode_owner = @@ -279,16 +273,16 @@ impl ValidatorApiStorage { pub(crate) async fn get_gateway_uptime_history( &self, identity: &str, - ) -> Result { + ) -> Result { let history = self .manager .get_gateway_historical_uptimes(identity) .await?; if history.is_empty() { - return Err(ValidatorApiStorageError::GatewayUptimeHistoryNotFound( - identity.to_owned(), - )); + return Err(NymApiStorageError::GatewayUptimeHistoryNotFound { + identity: identity.to_owned(), + }); } let gateway_owner = @@ -307,7 +301,7 @@ impl ValidatorApiStorage { &self, mix_id: MixId, end_ts_secs: i64, - ) -> Result { + ) -> Result { let start = end_ts_secs - 86400; self.get_average_mixnode_uptime_in_time_interval(mix_id, start, end_ts_secs) .await @@ -326,7 +320,7 @@ impl ValidatorApiStorage { mix_id: MixId, start: i64, end: i64, - ) -> Result { + ) -> Result { let mixnode_database_id = match self.manager.get_mixnode_database_id(mix_id).await? { Some(id) => id, None => return Ok(Uptime::zero()), @@ -356,7 +350,7 @@ impl ValidatorApiStorage { &self, start: i64, end: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { if (end - start) as u64 != ONE_DAY.as_secs() { warn!("Our current interval length breaks the 24h length assumption") } @@ -400,7 +394,7 @@ impl ValidatorApiStorage { &self, start: i64, end: i64, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { if (end - start) as u64 != ONE_DAY.as_secs() { warn!("Our current interval length breaks the 24h length assumption") } @@ -441,36 +435,42 @@ impl ValidatorApiStorage { &self, monitor_run_db_id: i64, test_route: TestRoute, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { // we MUST have those entries in the database, otherwise the route wouldn't have been chosen // in the first place let layer1_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_one_mix().mix_id) - .await - .map_err(|_| ValidatorApiStorageError::InternalDatabaseError("".to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer1 mixnode from network monitor run {monitor_run_db_id}"), + })?; let layer2_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_two_mix().mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer2 mixnode from network monitor run {monitor_run_db_id}"), + })?; let layer3_mix_db_id = self .manager .get_mixnode_database_id(test_route.layer_three_mix().mix_id) - .await - .map_err(|_| ValidatorApiStorageError::InternalDatabaseError("".to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!("could not get db id for layer3 mixnode from network monitor run {monitor_run_db_id}"), + })?; let gateway_db_id = self .manager .get_gateway_id(&test_route.gateway().identity_key.to_base58_string()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - .ok_or_else(|| ValidatorApiStorageError::InternalDatabaseError("".to_string()))?; + .await? + .ok_or_else(|| NymApiStorageError::DatabaseInconsistency { + reason: format!( + "could not get db id for gateway from network monitor run {monitor_run_db_id}" + ), + })?; self.manager .submit_testing_route_used(TestingRoute { @@ -480,8 +480,7 @@ impl ValidatorApiStorage { layer3_mix_db_id, monitor_run_db_id, }) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; Ok(()) } @@ -497,12 +496,8 @@ impl ValidatorApiStorage { &self, mix_id: MixId, since: Option, - ) -> Result { - let db_id = self - .manager - .get_mixnode_database_id(mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + ) -> Result { + let db_id = self.manager.get_mixnode_database_id(mix_id).await?; if let Some(node_id) = db_id { let since = since @@ -511,7 +506,7 @@ impl ValidatorApiStorage { self.manager .get_mixnode_testing_route_presence_count_since(node_id, since) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } else { Ok(0) } @@ -529,12 +524,8 @@ impl ValidatorApiStorage { &self, identity: &str, since: Option, - ) -> Result { - let node_id = self - .manager - .get_gateway_id(identity) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + ) -> Result { + let node_id = self.manager.get_gateway_id(identity).await?; if let Some(node_id) = node_id { let since = since @@ -543,7 +534,7 @@ impl ValidatorApiStorage { self.manager .get_gateway_testing_route_presence_count_since(node_id, since) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } else { Ok(0) } @@ -562,26 +553,20 @@ impl ValidatorApiStorage { mixnode_results: Vec, gateway_results: Vec, test_routes: Vec, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { info!("Submitting new node results to the database. There are {} mixnode results and {} gateway results", mixnode_results.len(), gateway_results.len()); let now = OffsetDateTime::now_utc().unix_timestamp(); - let monitor_run_id = self - .manager - .insert_monitor_run(now) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + let monitor_run_id = self.manager.insert_monitor_run(now).await?; self.manager .submit_mixnode_statuses(now, mixnode_results) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; self.manager .submit_gateway_statuses(now, gateway_results) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; for test_route in test_routes { self.insert_test_route(monitor_run_id, test_route).await?; @@ -600,14 +585,14 @@ impl ValidatorApiStorage { &self, since: i64, until: i64, - ) -> Result { + ) -> Result { let run_count = self.manager.get_monitor_runs_count(since, until).await?; if run_count < 0 { // I don't think it's ever possible for SQL to return a negative value from COUNT? - return Err(ValidatorApiStorageError::InternalDatabaseError( - "Negative run count".to_string(), - )); + return Err(NymApiStorageError::DatabaseInconsistency { + reason: "Negative run count".to_string(), + }); } Ok(run_count as usize) } @@ -625,16 +610,11 @@ impl ValidatorApiStorage { today_iso_8601: &str, mixnode_reports: &[MixnodeStatusReport], gateway_reports: &[GatewayStatusReport], - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { for report in mixnode_reports { // if this ever fails, we have a super weird error because we just constructed report for that node // and we never delete node data! - let node_id = match self - .manager - .get_mixnode_database_id(report.mix_id) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - { + let node_id = match self.manager.get_mixnode_database_id(report.mix_id).await? { Some(node_id) => node_id, None => { error!( @@ -647,19 +627,13 @@ impl ValidatorApiStorage { self.manager .insert_mixnode_historical_uptime(node_id, today_iso_8601, report.last_day.u8()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; } for report in gateway_reports { // if this ever fails, we have a super weird error because we just constructed report for that node // and we never delete node data! - let node_id = match self - .manager - .get_gateway_id(&report.identity) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))? - { + let node_id = match self.manager.get_gateway_id(&report.identity).await? { Some(node_id) => node_id, None => { error!( @@ -672,8 +646,7 @@ impl ValidatorApiStorage { self.manager .insert_gateway_historical_uptime(node_id, today_iso_8601, report.last_day.u8()) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + .await?; } Ok(()) @@ -682,11 +655,11 @@ impl ValidatorApiStorage { pub(crate) async fn check_if_historical_uptimes_exist_for_date( &self, date_iso_8601: &str, - ) -> Result { + ) -> Result { self.manager .check_for_historical_uptime_existence(date_iso_8601) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } /// Removes all ipv4 and ipv6 statuses for all mixnodes and gateways that are older than the @@ -695,49 +668,43 @@ impl ValidatorApiStorage { /// # Arguments /// /// * `until`: timestamp specifying the purge cutoff. - pub(crate) async fn purge_old_statuses( - &self, - until: i64, - ) -> Result<(), ValidatorApiStorageError> { - self.manager - .purge_old_mixnode_statuses(until) - .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string()))?; + pub(crate) async fn purge_old_statuses(&self, until: i64) -> Result<(), NymApiStorageError> { + self.manager.purge_old_mixnode_statuses(until).await?; self.manager .purge_old_gateway_statuses(until) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } pub(crate) async fn insert_rewarding_report( &self, report: RewardingReport, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { self.manager .insert_rewarding_report(report) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } pub(crate) async fn get_rewarding_report( &self, absolute_epoch_id: EpochId, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { self.manager .get_rewarding_report(absolute_epoch_id) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } #[cfg(feature = "coconut")] pub(crate) async fn get_blinded_signature_response( &self, tx_hash: &str, - ) -> Result, ValidatorApiStorageError> { + ) -> Result, NymApiStorageError> { self.manager .get_blinded_signature_response(tx_hash) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } #[cfg(feature = "coconut")] @@ -745,10 +712,10 @@ impl ValidatorApiStorage { &self, tx_hash: &str, blinded_signature_response: &str, - ) -> Result<(), ValidatorApiStorageError> { + ) -> Result<(), NymApiStorageError> { self.manager .insert_blinded_signature_response(tx_hash, blinded_signature_response) .await - .map_err(|e| ValidatorApiStorageError::InternalDatabaseError(e.to_string())) + .map_err(|err| err.into()) } } diff --git a/validator-api/src/storage/models.rs b/nym-api/src/storage/models.rs similarity index 100% rename from validator-api/src/storage/models.rs rename to nym-api/src/storage/models.rs diff --git a/validator-api/src/swagger.rs b/nym-api/src/swagger.rs similarity index 100% rename from validator-api/src/swagger.rs rename to nym-api/src/swagger.rs diff --git a/validator-api/tests/README.md b/nym-api/tests/README.md similarity index 100% rename from validator-api/tests/README.md rename to nym-api/tests/README.md diff --git a/validator-api/tests/functional_test/validator-api/status/status-mixnode.test.ts b/nym-api/tests/functional_test/validator-api/status/status-mixnode.test.ts similarity index 100% rename from validator-api/tests/functional_test/validator-api/status/status-mixnode.test.ts rename to nym-api/tests/functional_test/validator-api/status/status-mixnode.test.ts diff --git a/validator-api/tests/jest.config.js b/nym-api/tests/jest.config.js similarity index 100% rename from validator-api/tests/jest.config.js rename to nym-api/tests/jest.config.js diff --git a/validator-api/tests/package.json b/nym-api/tests/package.json similarity index 100% rename from validator-api/tests/package.json rename to nym-api/tests/package.json diff --git a/validator-api/tests/src/config/config.yaml b/nym-api/tests/src/config/config.yaml similarity index 100% rename from validator-api/tests/src/config/config.yaml rename to nym-api/tests/src/config/config.yaml diff --git a/validator-api/tests/src/config/configHandler.ts b/nym-api/tests/src/config/configHandler.ts similarity index 100% rename from validator-api/tests/src/config/configHandler.ts rename to nym-api/tests/src/config/configHandler.ts diff --git a/validator-api/tests/src/endpoints/Status.ts b/nym-api/tests/src/endpoints/Status.ts similarity index 100% rename from validator-api/tests/src/endpoints/Status.ts rename to nym-api/tests/src/endpoints/Status.ts diff --git a/validator-api/tests/src/endpoints/abstracts/APIClient.ts b/nym-api/tests/src/endpoints/abstracts/APIClient.ts similarity index 100% rename from validator-api/tests/src/endpoints/abstracts/APIClient.ts rename to nym-api/tests/src/endpoints/abstracts/APIClient.ts diff --git a/validator-api/tests/src/interfaces/StatusInterfaces.ts b/nym-api/tests/src/interfaces/StatusInterfaces.ts similarity index 100% rename from validator-api/tests/src/interfaces/StatusInterfaces.ts rename to nym-api/tests/src/interfaces/StatusInterfaces.ts diff --git a/validator-api/tests/src/restClient/RestClient.ts b/nym-api/tests/src/restClient/RestClient.ts similarity index 100% rename from validator-api/tests/src/restClient/RestClient.ts rename to nym-api/tests/src/restClient/RestClient.ts diff --git a/validator-api/tests/tsconfig.eslint.json b/nym-api/tests/tsconfig.eslint.json similarity index 100% rename from validator-api/tests/tsconfig.eslint.json rename to nym-api/tests/tsconfig.eslint.json diff --git a/validator-api/tests/tsconfig.json b/nym-api/tests/tsconfig.json similarity index 100% rename from validator-api/tests/tsconfig.json rename to nym-api/tests/tsconfig.json diff --git a/validator-api/tests/yarn.lock b/nym-api/tests/yarn.lock similarity index 100% rename from validator-api/tests/yarn.lock rename to nym-api/tests/yarn.lock diff --git a/nym-connect/CHANGELOG.md b/nym-connect/CHANGELOG.md index 8e6d4eb168..b515382104 100644 --- a/nym-connect/CHANGELOG.md +++ b/nym-connect/CHANGELOG.md @@ -1,5 +1,12 @@ ## UNRELEASED +## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10) + +- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684 +- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692 +- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704 +- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742 + ## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20) This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling. diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index 427d578caf..b96753bb3c 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -632,7 +632,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.1.3" +version = "1.1.4" dependencies = [ "async-trait", "client-connections", @@ -1008,8 +1008,8 @@ dependencies = [ "bls12_381 0.5.0", "coconut-interface", "crypto", + "nym-api-requests", "thiserror", - "validator-api-requests", "validator-client", ] @@ -1067,6 +1067,7 @@ dependencies = [ "pemstore", "rand 0.7.3", "subtle-encoding", + "thiserror", "x25519-dalek", ] @@ -3379,9 +3380,23 @@ dependencies = [ "syn", ] +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "coconut-interface", + "cosmrs", + "cosmwasm-std", + "getset", + "mixnet-contract-common", + "schemars", + "serde", +] + [[package]] name = "nym-connect" -version = "1.1.3" +version = "1.1.4" dependencies = [ "anyhow", "bip39", @@ -3423,7 +3438,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.3" +version = "1.1.4" dependencies = [ "clap", "client-connections", @@ -3511,6 +3526,7 @@ dependencies = [ "nymsphinx-types", "pemstore", "rand 0.7.3", + "thiserror", "topology", ] @@ -3584,6 +3600,7 @@ dependencies = [ "bytes", "nymsphinx-params", "nymsphinx-types", + "thiserror", "tokio-util", ] @@ -3593,6 +3610,7 @@ version = "0.1.0" dependencies = [ "crypto", "nymsphinx-types", + "thiserror", ] [[package]] @@ -3719,6 +3737,7 @@ name = "ordered-buffer" version = "0.1.0" dependencies = [ "log", + "thiserror", ] [[package]] @@ -6461,20 +6480,6 @@ dependencies = [ "getrandom 0.2.7", ] -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", -] - [[package]] name = "validator-client" version = "0.1.0" @@ -6499,6 +6504,7 @@ dependencies = [ "mixnet-contract-common", "multisig-contract-common", "network-defaults", + "nym-api-requests", "prost", "reqwest", "serde", @@ -6507,7 +6513,6 @@ dependencies = [ "thiserror", "tokio", "url", - "validator-api-requests", "vesting-contract", "vesting-contract-common", ] diff --git a/nym-connect/package.json b/nym-connect/package.json index bff93c5a67..f74779b2d4 100644 --- a/nym-connect/package.json +++ b/nym-connect/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.4", + "version": "1.1.5", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index 1ef082ea8d..0d7118b97a 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.4" +version = "1.1.5" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index 7d4e62f7cd..fcbd9a2d96 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -130,7 +130,7 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str if let Ok(raw_validators) = std::env::var(config_common::defaults::var_names::API_VALIDATOR) { config .get_base_mut() - .set_custom_validator_apis(config_common::parse_validators(&raw_validators)); + .set_custom_nym_apis(config_common::parse_validators(&raw_validators)); } // Setup gateway by either registering a new one, or reusing exiting keys diff --git a/nym-connect/src-tauri/src/menu.rs b/nym-connect/src-tauri/src/menu.rs index fb6f88fb68..2f9be83e07 100644 --- a/nym-connect/src-tauri/src/menu.rs +++ b/nym-connect/src-tauri/src/menu.rs @@ -16,8 +16,7 @@ impl AddDefaultSubmenus for Menu { fn add_default_app_submenus(self) -> Self { let submenu = Submenu::new( "Help", - Menu::new() - .add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")) + Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")), ); self.add_submenu(submenu) } diff --git a/nym-connect/src-tauri/src/state.rs b/nym-connect/src-tauri/src/state.rs index 9da1dab42b..f0aa869edd 100644 --- a/nym-connect/src-tauri/src/state.rs +++ b/nym-connect/src-tauri/src/state.rs @@ -101,7 +101,7 @@ impl State { // Setup configuration by writing to file if let Err(err) = self.init_config().await { - log::error!("Failed to initialize: {}", err); + log::error!("Failed to initialize: {err}"); // Wait a little to give the user some rudimentary feedback that the click actually // registered. diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/src-tauri/src/tasks.rs index ef54fddcaa..3201648e6e 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/src-tauri/src/tasks.rs @@ -100,7 +100,7 @@ pub fn start_status_listener( log::info!("SOCKS5 proxy sent status message: {}", msg); window .emit( - "socks5-event", + "socks5-status-event", Payload { title: "SOCKS5 update".into(), message: msg.to_string(), @@ -134,13 +134,13 @@ pub fn start_disconnect_listener( .unwrap(); } Ok(Socks5ExitStatusMessage::Failed(err)) => { - log::info!("SOCKS5 task reported error: {}", err); + log::info!("SOCKS5 task reported error: {err}"); window .emit( "socks5-event", Payload { title: "SOCKS5 error".into(), - message: format!("SOCKS5 failed: {}", err), + message: format!("SOCKS5 failed: {err}"), }, ) .unwrap(); diff --git a/nym-connect/src-tauri/tauri.conf.json b/nym-connect/src-tauri/tauri.conf.json index 05d22942c4..0e11432d04 100644 --- a/nym-connect/src-tauri/tauri.conf.json +++ b/nym-connect/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.4" + "version": "1.1.5" }, "build": { "distDir": "../dist", diff --git a/nym-connect/src/components/AppVersion.tsx b/nym-connect/src/components/AppVersion.tsx index 1b8adf40aa..f82f99b5a2 100644 --- a/nym-connect/src/components/AppVersion.tsx +++ b/nym-connect/src/components/AppVersion.tsx @@ -1,5 +1,5 @@ -import { Typography, Box } from '@mui/material'; import React from 'react'; +import { Typography, Box } from '@mui/material'; import { useClientContext } from 'src/context/main'; export const AppVersion = () => { diff --git a/nym-connect/src/components/AppWindowFrame.tsx b/nym-connect/src/components/AppWindowFrame.tsx index 80bed93f77..3abc14a8e6 100644 --- a/nym-connect/src/components/AppWindowFrame.tsx +++ b/nym-connect/src/components/AppWindowFrame.tsx @@ -15,5 +15,6 @@ export const AppWindowFrame: React.FC = ({ children }) => ( > {children} + ); diff --git a/nym-connect/src/components/ConnectionButton.tsx b/nym-connect/src/components/ConnectionButton.tsx index 42903a80ea..6d8a592681 100644 --- a/nym-connect/src/components/ConnectionButton.tsx +++ b/nym-connect/src/components/ConnectionButton.tsx @@ -76,9 +76,9 @@ export const ConnectionButton: React.FC<{ return ( diff --git a/nym-connect/src/components/ConnectionStatus.tsx b/nym-connect/src/components/ConnectionStatus.tsx index c2bfcc82f0..c2f9add9ad 100644 --- a/nym-connect/src/components/ConnectionStatus.tsx +++ b/nym-connect/src/components/ConnectionStatus.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Box, CircularProgress, Typography } from '@mui/material'; +import { Box, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material'; import { DateTime } from 'luxon'; import { ConnectionStatusKind } from '../types'; import { ServiceProvider } from '../types/directory'; +import { ServiceProviderInfo } from './ServiceProviderInfo'; const FONT_SIZE = '10px'; const FONT_WEIGHT = '600'; @@ -14,8 +15,8 @@ const ConnectionStatusContent: React.FC<{ switch (status) { case ConnectionStatusKind.connected: return ( - - Connected + + Connected to ); case ConnectionStatusKind.disconnecting: @@ -44,7 +45,7 @@ const ConnectionStatusContent: React.FC<{ ml={1} textTransform="uppercase" textAlign="center" - fontSize="10px" + fontSize={FONT_SIZE} sx={{ wordSpacing: 3, letterSpacing: 2 }} > You are not protected @@ -59,7 +60,7 @@ export const ConnectionStatus: React.FC<{ status: ConnectionStatusKind; connectedSince?: DateTime; serviceProvider?: ServiceProvider; -}> = ({ status, connectedSince, serviceProvider }) => { +}> = ({ status, serviceProvider }) => { const color = status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting ? '#21D072' @@ -70,13 +71,13 @@ export const ConnectionStatus: React.FC<{ - - {serviceProvider && ( - - To {serviceProvider.description} - - )} - + {serviceProvider ? ( + }> + + {serviceProvider && {serviceProvider.description}} + + + ) : null} ); }; diff --git a/nym-connect/src/components/ServiceProviderInfo.tsx b/nym-connect/src/components/ServiceProviderInfo.tsx new file mode 100644 index 0000000000..a6289b5c41 --- /dev/null +++ b/nym-connect/src/components/ServiceProviderInfo.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Divider, Stack, Typography } from '@mui/material'; +import { ServiceProvider } from 'src/types/directory'; + +export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: ServiceProvider }) => ( + + + Connection info + + {serviceProvider.description} + + + Gateway {serviceProvider.gateway} + + + + Provider {serviceProvider.address.slice(0, 35)}... + + +); diff --git a/nym-connect/src/context/main.tsx b/nym-connect/src/context/main.tsx index 945952f2ef..3258978ff8 100644 --- a/nym-connect/src/context/main.tsx +++ b/nym-connect/src/context/main.tsx @@ -86,6 +86,8 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode .catch((e) => console.log(e)); listen('socks5-event', (e: TauriEvent) => { + console.log(e); + setError(e.payload); }).then((result) => { unlisten.push(result); @@ -189,6 +191,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode [ appVersion, mode, + appVersion, error, connectedSince, showHelp, diff --git a/nym-connect/src/layouts/ConnectedLayout.tsx b/nym-connect/src/layouts/ConnectedLayout.tsx index 0bf61fd4c0..9cf37b9f12 100644 --- a/nym-connect/src/layouts/ConnectedLayout.tsx +++ b/nym-connect/src/layouts/ConnectedLayout.tsx @@ -37,11 +37,13 @@ export const ConnectedLayout: React.FC<{ }) => ( <> - + - - + + + + {/* */} diff --git a/nym-outfox/Cargo.toml b/nym-outfox/Cargo.toml new file mode 100644 index 0000000000..48a05c842c --- /dev/null +++ b/nym-outfox/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nym-outfox" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rayon = "1.5.1" +blake3 = "1.3" +zeroize = "1.5" +chacha20 = "0.9.0" +curve25519-dalek = "3.2" +chacha20poly1305 = "0.10.1" +# Need this star over here to pull in js into getrandom +getrandom = { version = "*", features = ["js"] } +thiserror = "1" + +[dev-dependencies] +criterion = "0.4" +fastrand = "1.8" diff --git a/nym-outfox/src/error.rs b/nym-outfox/src/error.rs new file mode 100644 index 0000000000..afbba2fc4b --- /dev/null +++ b/nym-outfox/src/error.rs @@ -0,0 +1,20 @@ +use crate::lion::MIN_MESSAGE_LEN; +use chacha20::cipher::InvalidLength; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum OutFoxError { + #[error("Lengths mismatch, expected: {expected}, got: {got}")] + LenMismatch { expected: usize, got: usize }, + #[error("{source}")] + ChaCha20InvalidLength { + #[from] + source: InvalidLength, + }, + #[error("ChaCha20Poly1305 - Opaque error")] + ChaCha20Poly1305Error, + #[error("Key length must be 32 bytes")] + InvalidKeyLength, + #[error("Message length must be greater then {MIN_MESSAGE_LEN} bytes")] + InvalidMessageLength, +} diff --git a/nym-outfox/src/format.rs b/nym-outfox/src/format.rs new file mode 100644 index 0000000000..2ddf895e1f --- /dev/null +++ b/nym-outfox/src/format.rs @@ -0,0 +1,267 @@ +//! # The `outfox` format +//! +//! We define a simple mix packet format geared towards simplicity and performance with the features that are +//! specifically required by a stratified mix topology. +//! +//! `Outfox` assumes that all +//! paths are the same length (no need to hide path lengths), mixes are arranged in layers and therefore +//! know their position in a message path (no need to hide this position). These assumptions allow us +//! to do away with some of the padding traditionally used; further we prioritize efficient computation +//! over very low-bandwidth, as it seems the rate of public key operations is a bottleneck for mixes +//! rather than the availablility of bandwidth. +//! +//! ## Overview and Parameters +//! +//! In a mix network with a stratified topology packets are mixed by nodes at each of the layers. Each layer +//! 'strips' the packet from one layer of encryption, recovers the address of the mix at the next layer, and +//! passes the decoded packet to them. An identifier per processed message is stored and checked to prevent +//! replays of processed messages at each layer. Additional measures, such as adding delays, adding dummy +//! traffic or dropping messages can be empued at each mix to frustrate traffic analysis. +//! +//! +//! A layer of mix processing is defined by three parameters, includes in the structure [MixStageParameters]: +//! * The `routing_information_length_bytes` (`R`) states the number of bytes representing +//! routing information at this layer. +//! * The `remaining_header_length_bytes` (`H`) represents the remaining bytes of the packet header. +//! * The `payload_length_bytes` (`P`). +//! +//! In addition we define two system-wide constants, namely `GROUPELEMENTBYTES` (`GE`=32) and +//! `TAGBYTES` (`T`=24). +//! +//! ## Packet format, decoding +//! +//! A mix at this layer takes in messages of length `GE+T+R+H+P`, and outputs messages of length `H+P`. +//! +//! An input message is processed as follows: +//! +//! * The input packet is parsed as a `[Pk, Tag, Header, Payload]` of length `[GE, T, R+H, P]` respectivelly. +//! * A master key is derived by performing scalar multiplication with the mix secret 's', ie `K = s * Pk`. +//! The master key is stored and checked for duplicates (if it is found processing ends.) +//! * The master key is used to perform AEAD decryption of the `Header` with an IV of zeros and the `tag`. If +//! decryption fails processing ends. Otherwise the Header is parsed as `[Routing, Next_Header]` of length +//! `[R, H]` respectivelly. The routing data `Routing` can be used by the mix to dertermine the next mix. +//! * Finally, the master key is used to perform lion decoding of the `Payload` into `Next_Payload`. +//! * The output packet for the next mix is `[Next_Header, Next_Payload]`. +//! +//! As an AEAD we use `chacha20poly1305_ietf` and for public key operations we use `curve25519`. +//! +//! ## Packet encoding +//! +//! Encoding is +//! performed layer by layer starting with the last hop on the route, and ending with the first. At each stage +//! of encoding a new Secret key `Sk` and corresponding `Pk` is chosen. The layer master key for the layer is +//! derived using the mix public key. And the master key is used to AEAD encrypt the concatenation of the +//! routing data for the layer, and the remaining Header; separately the master key is used to lion encrypt +//! the payload. The process is repeated for each layer (from last to first) to construct the full message. + +use chacha20poly1305::AeadInPlace; +use chacha20poly1305::ChaCha20Poly1305; +use chacha20poly1305::KeyInit; + +use chacha20poly1305::Tag; +use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; +use curve25519_dalek::montgomery::MontgomeryPoint; +use curve25519_dalek::scalar::Scalar; + +use std::convert::TryInto; + +const GROUPELEMENTBYTES: usize = 32; +const TAGBYTES: usize = 16; + +use std::ops::Range; + +use crate::error::OutFoxError; +use crate::lion::*; + +/// A structure that holds mix packet construction parameters. These incluse the length +/// of the routing information at each hop, the number of hops, and the payload length. +pub struct MixCreationParameters { + /// The routing length is inner first, so \[0\] is the innermost routing length, etc (in bytes) + pub routing_information_length_by_stage: Vec, + /// The payload length (in bytes) + pub payload_length_bytes: usize, +} + +impl MixCreationParameters { + /// Create a set of parameters for a mix packet format. + pub fn new(payload_length_bytes: usize) -> MixCreationParameters { + MixCreationParameters { + routing_information_length_by_stage: Vec::new(), + payload_length_bytes, + } + } + + /// Add another outer layer containing some byte length of routing data. + pub fn add_outer_layer(&mut self, routing_information_length_bytes: usize) { + self.routing_information_length_by_stage + .push(routing_information_length_bytes); + } + + /// The length of the buffer needed to build a packet. + pub fn total_packet_length(&self) -> usize { + let mut len = self.payload_length_bytes; + for stage_len in &self.routing_information_length_by_stage { + len += stage_len + GROUPELEMENTBYTES + TAGBYTES + } + len + } + + /// Get the mix packet parameters for a single stage of mixing. + pub fn get_stage_params(&self, layer_number: usize) -> (Range, MixStageParameters) { + assert!(layer_number < self.routing_information_length_by_stage.len()); + + let mut remaining_header_length_bytes = 0; + for (i, stage_len) in self.routing_information_length_by_stage.iter().enumerate() { + if i == layer_number { + let params = MixStageParameters { + routing_information_length_bytes: *stage_len, + remaining_header_length_bytes, + payload_length_bytes: self.payload_length_bytes, + }; + + let total_size = self.total_packet_length(); + let inner_size = params.incoming_packet_length(); + + return (total_size - inner_size..total_size, params); + } else { + remaining_header_length_bytes += stage_len + GROUPELEMENTBYTES + TAGBYTES; + } + } + + unreachable!(); + } +} + +/// A structure representing the parameters of a single stage of mixing. +pub struct MixStageParameters { + /// The routing information length for this stage of mixing + pub routing_information_length_bytes: usize, + /// The reamining header length for this stage of mixing + pub remaining_header_length_bytes: usize, + /// The payload length + pub payload_length_bytes: usize, +} + +impl MixStageParameters { + pub fn incoming_packet_length(&self) -> usize { + GROUPELEMENTBYTES + TAGBYTES + self.outgoing_packet_length() + } + + pub fn outgoing_packet_length(&self) -> usize { + self.routing_information_length_bytes + + self.remaining_header_length_bytes + + self.payload_length_bytes + } + + pub fn pub_element_range(&self) -> Range { + 0..GROUPELEMENTBYTES + } + + pub fn tag_range(&self) -> Range { + GROUPELEMENTBYTES..GROUPELEMENTBYTES + TAGBYTES + } + + pub fn routing_data_range(&self) -> Range { + GROUPELEMENTBYTES + TAGBYTES + ..GROUPELEMENTBYTES + TAGBYTES + self.routing_information_length_bytes + } + + pub fn header_range(&self) -> Range { + GROUPELEMENTBYTES + TAGBYTES + ..GROUPELEMENTBYTES + + TAGBYTES + + self.routing_information_length_bytes + + self.remaining_header_length_bytes + } + + pub fn payload_range(&self) -> Range { + self.incoming_packet_length() - self.payload_length_bytes..self.incoming_packet_length() + } + + pub fn encode_mix_layer( + &self, + buffer: &mut [u8], + user_secret_key: &Scalar, + mix_public_key: &MontgomeryPoint, + routing_data: &[u8], + ) -> Result { + if buffer.len() != self.incoming_packet_length() { + return Err(OutFoxError::LenMismatch { + expected: buffer.len(), + got: self.incoming_packet_length(), + }); + } + + if routing_data.len() != self.routing_information_length_bytes { + return Err(OutFoxError::LenMismatch { + expected: routing_data.len(), + got: self.routing_information_length_bytes, + }); + } + + let user_public_key = (&ED25519_BASEPOINT_TABLE * user_secret_key).to_montgomery(); + let shared_key = user_secret_key * mix_public_key; + + // Copy rounting data into buffer + buffer[self.routing_data_range()].copy_from_slice(routing_data); + + // Perform the AEAD + let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..])?; + // TODO: Should this be all 0s? + let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + let tag = header_aead_key + .encrypt_in_place_detached(&nonce.into(), &[], &mut buffer[self.header_range()]) + .map_err(|_| OutFoxError::ChaCha20Poly1305Error)?; + + // Copy Tag into buffer + buffer[self.tag_range()].copy_from_slice(&tag[..]); + + // Copy own public key into buffer + buffer[self.pub_element_range()].copy_from_slice(&user_public_key.0[..]); + + // Do a round of LION on the payload + lion_transform_encrypt(&mut buffer[self.payload_range()], &shared_key.0)?; + + Ok(shared_key) + } + + pub fn decode_mix_layer( + &self, + buffer: &mut [u8], + mix_secret_key: &Scalar, + ) -> Result { + // Check the length of the incoming buffer is correct. + if buffer.len() != self.incoming_packet_length() { + return Err(OutFoxError::LenMismatch { + expected: buffer.len(), + got: self.incoming_packet_length(), + }); + } + + // Derive the shared key for this packet + let user_public_key = MontgomeryPoint(buffer[self.pub_element_range()].try_into().unwrap()); + let shared_key = mix_secret_key * user_public_key; + + // Compute the AEAD and check the Tag, if wrong return Err + let header_aead_key = ChaCha20Poly1305::new_from_slice(&shared_key.0[..]).unwrap(); + let nonce = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + let tag_bytes = buffer[self.tag_range()].to_vec(); + let tag = Tag::from_slice(&tag_bytes); + + header_aead_key + .decrypt_in_place_detached( + &nonce.into(), + &[], + &mut buffer[self.header_range()], + tag.as_slice().try_into().unwrap(), + ) + .map_err(|_| OutFoxError::ChaCha20Poly1305Error)?; + + // Do a round of LION on the payload + lion_transform_decrypt(&mut buffer[self.payload_range()], &shared_key.0)?; + + Ok(shared_key) + } +} diff --git a/nym-outfox/src/lib.rs b/nym-outfox/src/lib.rs new file mode 100644 index 0000000000..fbfcc32078 --- /dev/null +++ b/nym-outfox/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod format; +pub mod lion; diff --git a/nym-outfox/src/lion.rs b/nym-outfox/src/lion.rs new file mode 100644 index 0000000000..e4ea5e2c55 --- /dev/null +++ b/nym-outfox/src/lion.rs @@ -0,0 +1,114 @@ +//! # The lion all-or-nothing transform +//! +//! The lion transform implements a keyed permutation (block cipher) with a +//! variable length block size. It takes a key of 32 bytes, and a message of +//! length >= 48 bytes. +//! +//! The cryptographic primitives used to implement the transform are a +//! stream cipher `PRF1(IV, KEY)` (using `crypto_stream_xsalsa20`), a message authentication +//! code `PRF2(MSG, KEY)` (using `HMAC-SHA-512-256`) and a key derivation function `KDF(KEY, ID)` +//! (using `Blake2b`). +//! +//! The message to encode is split into two parts `M = [L0, R0]`, where L is 24 bytes, and +//! R is the remaining of the message. +//! +//! Encoding then proceeds in 3 steps: +//! * `R1 = PRF1(L0, KDF(key, subkey_0)) XOR R0;` +//! * `L1 = PRF2(R1, KDF(key, subkey_1)) XOR L0;` +//! * `R2 = PRF1(L1, KDF(key, subkey_2)) XOR R1;` +//! +//! The output of the transform is the concatenated byte string `M' = [L1, R2]` which has the same +//! length as the original message. +//! +//! ## Manual key schedule. +//! +//! If you just want to encode / decode using lion as a wide-block block cipher simply use the +//! [lion_transform_encrypt] and [lion_transform_decrypt] functions. +//! +//! If you know what you are doing you can determine your own key schedule for the transform. The +//! key schedule for encypt and decrypt are [1, 2, 3] and [3, 2, 1] respectivelly. You may define +//! a key schedule that is symmetric (such as [1, 2, 1]) to build a transform T(k,m) that has the +//! property m = T(k, T(k, m)). + +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use chacha20::Key; +use chacha20::XChaCha20; +use chacha20::XNonce; +use zeroize::Zeroize; + +use crate::error::OutFoxError; + +pub const MIN_MESSAGE_LEN: usize = 24 * 2; +const CONTEXT: &str = "LIONKEYS"; +const TAG_LEN: usize = 24; + +/// The lion transform encryption function. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. The message is +/// mutated to the encrypted message. +pub fn lion_transform_encrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutFoxError> { + lion_transform(message, key, [1, 2, 3]) +} + +/// The lion transform decryption function. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. The message +/// is mutated to the decrypted message. +pub fn lion_transform_decrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutFoxError> { + lion_transform(message, key, [3, 2, 1]) +} + +/// The core of the lion transform function, that takes a message and a key, +/// and applies the all-or-nothing transform. The key schedule represents the +/// values of the 3 subkeys used by the 3 phases of the transform. +/// +/// The `key` must be 32 bytes, and the `message` >= 48. +/// +/// Unless you know what you are doing use [lion_transform_encrypt] and +/// [lion_transform_decrypt] instead. +pub fn lion_transform( + message: &mut [u8], + key: &[u8], + key_schedule: [u64; 3], +) -> Result<(), OutFoxError> { + if key.len() != 32 { + return Err(OutFoxError::InvalidKeyLength); + } + + if message.len() < MIN_MESSAGE_LEN { + return Err(OutFoxError::InvalidMessageLength); + } + + // Stage 1: Use stream cipher with Nonce from left size, to xor to the right side + let mut derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[0]), key); + let lion_stage_1_key = Key::from_slice(&derived_key); + let left_short_message = XNonce::from_slice(&message[..TAG_LEN]); + + let mut cipher = XChaCha20::new(lion_stage_1_key, left_short_message); + cipher.apply_keystream(&mut message[TAG_LEN..]); + + // Stage 2: Use HMAC of right size, and xor to the left side + derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[1]), key); + + let mac = blake3::keyed_hash(&derived_key, &message[TAG_LEN..]); + let tag_to_xor = mac.as_bytes(); + + // Xor resulting HMAC into the left (short) message + for i in 0..TAG_LEN { + message[i] ^= tag_to_xor[i]; + } + + // Stage 3: (same as 1) + derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[2]), key); + let lion_stage_3_key = Key::from_slice(&derived_key); + let left_short_message_final = XNonce::from_slice(&message[..TAG_LEN]); + + let mut cipher = XChaCha20::new(lion_stage_3_key, left_short_message_final); + + cipher.apply_keystream(&mut message[TAG_LEN..]); + + // clean up temp key + derived_key.zeroize(); + + Ok(()) +} diff --git a/nym-outfox/tests/unittests.rs b/nym-outfox/tests/unittests.rs new file mode 100644 index 0000000000..ea5b46571d --- /dev/null +++ b/nym-outfox/tests/unittests.rs @@ -0,0 +1,142 @@ +extern crate nym_outfox; + +#[cfg(test)] +mod tests { + + use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; + use curve25519_dalek::scalar::Scalar; + use std::convert::TryInto; + + use nym_outfox::format::*; + use nym_outfox::lion::*; + + use std::iter::repeat_with; + + pub fn randombytes(n: usize) -> Vec { + repeat_with(|| fastrand::u8(..)).take(n).collect() + } + + #[test] + fn test_encode_decode() { + let mix_params = MixStageParameters { + routing_information_length_bytes: 32, + remaining_header_length_bytes: (32 + 16 + 32) * 4, + payload_length_bytes: 1024, // 1kb + }; + + let user_secret_bytes = randombytes(32); + let mix_secret_bytes = randombytes(32); + + let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap()); + let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap()); + let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery(); + + let routing = [0; 32]; + + let buffer = randombytes(mix_params.incoming_packet_length()); + + let mut new_buffer = buffer.clone(); + + let _ = mix_params + .encode_mix_layer( + &mut new_buffer[..], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + assert!(&new_buffer[mix_params.payload_range()] != &buffer[mix_params.payload_range()]); + assert!(&new_buffer[mix_params.routing_data_range()] != &routing[..]); + + let _ = mix_params + .decode_mix_layer(&mut new_buffer[..], &mix_secret) + .unwrap(); + + assert!(&new_buffer[mix_params.payload_range()] == &buffer[mix_params.payload_range()]); + assert!(&new_buffer[mix_params.routing_data_range()] == &routing[..]); + } + + #[test] + fn test_lion() { + let key = randombytes(32); + let message = randombytes(1024); + + let mut message_clone = message.clone(); + lion_transform(&mut message_clone[..], &key, [1, 2, 3]).unwrap(); + assert!(&message_clone[..] != &message[..]); + + let mut message_clone_2 = message.clone(); + lion_transform_encrypt(&mut message_clone_2, &key).unwrap(); + assert_eq!(message_clone_2, message_clone); + + lion_transform(&mut message_clone[..], &key[..], [3, 2, 1]).unwrap(); + assert!(&message_clone[..] == &message[..]); + } + + #[test] + fn test_packet_params() { + // Dummy keys -- we will use the same key for each layer + let user_secret_bytes = randombytes(32); + let mix_secret_bytes = randombytes(32); + + let user_secret = Scalar::from_bytes_mod_order(user_secret_bytes.try_into().unwrap()); + let mix_secret = Scalar::from_bytes_mod_order(mix_secret_bytes.try_into().unwrap()); + let mix_public_key = (&ED25519_BASEPOINT_TABLE * &mix_secret).to_montgomery(); + + let routing = [0; 32]; + + let mut params = MixCreationParameters::new(1025); + params.add_outer_layer(32); + params.add_outer_layer(32); + params.add_outer_layer(32); + + let mut buf = vec![0; params.total_packet_length()]; + + let (range0, layer_params0) = params.get_stage_params(0); + let _ = layer_params0 + .encode_mix_layer( + &mut buf[range0.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + let (range1, layer_params1) = params.get_stage_params(1); + let _ = layer_params1 + .encode_mix_layer( + &mut buf[range1.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + let (range2, layer_params2) = params.get_stage_params(2); + let _ = layer_params2 + .encode_mix_layer( + &mut buf[range2.clone()], + &user_secret, + &mix_public_key, + &routing[..], + ) + .unwrap(); + + assert!( + &buf[params.total_packet_length() - 1025..params.total_packet_length()] != [0; 1025] + ); + + let _ = layer_params2 + .decode_mix_layer(&mut buf[range2], &mix_secret) + .unwrap(); + + let _ = layer_params1 + .decode_mix_layer(&mut buf[range1], &mix_secret) + .unwrap(); + + let _ = layer_params0 + .decode_mix_layer(&mut buf[range0], &mix_secret) + .unwrap(); + } +} diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index ae119b7749..6e34972352 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -1,6 +1,20 @@ # Changelog -## UNRELEASED +## [nym-wallet-v1.1.6](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.6) (2023-01-10) + +- wallet: rewrite some abci errors on the fly by @octol in https://github.com/nymtech/nym/pull/2716 +- Feature/node settings apy playground by @fmtabbara in https://github.com/nymtech/nym/pull/1677 +- Fix param input layout **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2720 +- Add epoch info to unbond modal **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2718 +- Feat/2130 tables update rebase by @gala1234ss as url param by @doums in https://github.com/nymtech/nym/pull/2780 + +## [nym-wallet-v1.1.5](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.5) (2022-12-22) + +This release contains the APY calculator feature. + +## [nym-wallet-v1.1.5](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.5) (2022-12-22) + +This release contains the APY calculator feature. ## [nym-wallet-v1.1.4](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.1.4) (2022-12-20) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 41db8105db..f182f91f7d 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2799,6 +2799,20 @@ dependencies = [ "libc", ] +[[package]] +name = "nym-api-requests" +version = "0.1.0" +dependencies = [ + "bs58", + "coconut-interface", + "cosmrs", + "cosmwasm-std", + "getset", + "mixnet-contract-common", + "schemars", + "serde", +] + [[package]] name = "nym-types" version = "1.0.0" @@ -2862,7 +2876,7 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.1.2" +version = "1.1.6" dependencies = [ "aes-gcm", "argon2 0.3.4", @@ -5309,20 +5323,6 @@ dependencies = [ "getrandom 0.2.5", ] -[[package]] -name = "validator-api-requests" -version = "0.1.0" -dependencies = [ - "bs58", - "coconut-interface", - "cosmrs", - "cosmwasm-std", - "getset", - "mixnet-contract-common", - "schemars", - "serde", -] - [[package]] name = "validator-client" version = "0.1.0" @@ -5347,6 +5347,7 @@ dependencies = [ "mixnet-contract-common", "multisig-contract-common", "network-defaults", + "nym-api-requests", "prost", "reqwest", "serde", @@ -5355,7 +5356,6 @@ dependencies = [ "thiserror", "tokio", "url", - "validator-api-requests", "vesting-contract", "vesting-contract-common", ] diff --git a/nym-wallet/nym-wallet-recovery-cli/src/main.rs b/nym-wallet/nym-wallet-recovery-cli/src/main.rs index 7cb2c3e0d7..9798308fac 100644 --- a/nym-wallet/nym-wallet-recovery-cli/src/main.rs +++ b/nym-wallet/nym-wallet-recovery-cli/src/main.rs @@ -82,7 +82,7 @@ fn decrypt_file(file: File, passwords: &[String], parse: &ParseMode) -> Result<( match decrypt_login(login, passwords, parse) { Ok(is_success) if is_success => successes += 1, Ok(_) => println!("None of the provided passwords succeeded"), - Err(err) => println!("Failed: {}", err), + Err(err) => println!("Failed: {err}"), } } @@ -133,9 +133,9 @@ fn decrypt_login(login: &Value, passwords: &[String], parse: &ParseMode) -> Resu } return Ok(true); } - Err(err) => println!(" failed to parse\n{}", err), + Err(err) => println!(" failed to parse\n{err}"), }, - Err(err) => println!(" failed\n{}", err), + Err(err) => println!(" failed\n{err}"), } } @@ -160,10 +160,10 @@ fn get_login_entry(login: &Value) -> Result<(&str, &str, &str)> { fn base64_decode(ciphertext: &str, iv: &str, salt: &str) -> Result<(Vec, Vec, Vec)> { let ciphertext = base64::decode(ciphertext) - .map_err(|err| anyhow!("Unable to base64 decode ciphertext: {}", err))?; - let iv = base64::decode(iv).map_err(|err| anyhow!("Unable to base64 decode iv: {}", err))?; + .map_err(|err| anyhow!("Unable to base64 decode ciphertext: {err}"))?; + let iv = base64::decode(iv).map_err(|err| anyhow!("Unable to base64 decode iv: {err}"))?; let salt = - base64::decode(salt).map_err(|err| anyhow!("Unable to base64 decode salt: {}", err))?; + base64::decode(salt).map_err(|err| anyhow!("Unable to base64 decode salt: {err}"))?; Ok((ciphertext, iv, salt)) } @@ -186,7 +186,7 @@ fn decrypt_password( let mut key = Key::default(); argon2 .hash_password_into(password.as_bytes(), salt, &mut key) - .map_err(|err| anyhow!("Unable to hash password: {}", err))?; + .map_err(|err| anyhow!("Unable to hash password: {err}"))?; // Create the Cipher let cipher = Aes256Gcm::new(&key); diff --git a/nym-wallet/package.json b/nym-wallet/package.json index f2f827c098..72a440d733 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.1.4", + "version": "1.1.6", "main": "index.js", "license": "MIT", "scripts": { diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index edbe2592b7..b5540924d0 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym_wallet" -version = "1.1.4" +version = "1.1.6" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index 9da3a1b73e..49d7acfdd4 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -167,7 +167,7 @@ impl Config { Some(global) } Err(err) => { - log::trace!("Not loading {:#?}: {}", file, err); + log::trace!("Not loading {:#?}: {err}", file); None } } @@ -182,7 +182,7 @@ impl Config { log::trace!("Loaded from file {:#?}", file); networks.insert(network.as_key(), config); } - Err(err) => log::trace!("Not loading {:#?}: {}", file, err), + Err(err) => log::trace!("Not loading {:#?}: {err}", file), }; } @@ -255,7 +255,7 @@ impl Config { } } - pub fn select_validator_api_url(&mut self, api_url: Url, network: WalletNetwork) { + pub fn select_nym_api_url(&mut self, api_url: Url, network: WalletNetwork) { if let Some(net) = self.networks.get_mut(&network.as_key()) { net.selected_api_url = Some(api_url); } else { @@ -275,7 +275,7 @@ impl Config { .and_then(|config| config.selected_nymd_url.clone()) } - pub fn get_selected_validator_api_url(&self, network: &WalletNetwork) -> Option { + pub fn get_selected_nym_api_url(&self, network: &WalletNetwork) -> Option { self.networks .get(&network.as_key()) .and_then(|config| config.selected_api_url.clone()) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index efef933c73..7f5dfe0812 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -4,7 +4,7 @@ use serde::{Serialize, Serializer}; use std::io; use std::num::ParseIntError; use thiserror::Error; -use validator_client::validator_api::error::ValidatorAPIError; +use validator_client::nym_api::error::NymAPIError; use validator_client::{nymd::error::NymdError, ValidatorClientError}; #[derive(Error, Debug)] @@ -41,9 +41,9 @@ pub enum BackendError { source: eyre::Report, }, #[error("{source}")] - ValidatorApiError { + NymApiError { #[from] - source: ValidatorAPIError, + source: NymAPIError, }, #[error("{source}")] KeyDerivationError { @@ -111,7 +111,7 @@ pub enum BackendError { WalletUnexpectedMnemonicAccount, #[error("Failed to derive address from mnemonic")] FailedToDeriveAddress, - #[error("{0}")] + #[error(transparent)] ValueParseError(#[from] ParseIntError), #[error("The provided coin has an unknown denomination - {0}")] UnknownCoinDenom(String), @@ -163,12 +163,10 @@ impl From for BackendError { impl From for BackendError { 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.into() - } + ValidatorClientError::NoAPIUrlAvailable => TypesError::NoNymApiUrlConfigured.into(), } } } diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d34a04f920..8705891c0a 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -10,9 +10,9 @@ use mixnet_contract_common::{Gateway, MixNode}; use crate::menu::AddDefaultSubmenus; use crate::operations::help; use crate::operations::mixnet; +use crate::operations::nym_api; use crate::operations::signatures; use crate::operations::simulate; -use crate::operations::validator_api; use crate::operations::vesting; use crate::state::WalletState; @@ -83,12 +83,14 @@ fn main() { mixnet::rewards::claim_delegator_reward, mixnet::rewards::claim_operator_reward, mixnet::rewards::claim_locked_and_unlocked_delegator_reward, + mixnet::rewards::get_current_rewarding_parameters, mixnet::send::send, + mixnet::bond::get_mixnode_uptime, network_config::add_validator, - network_config::get_validator_api_urls, + network_config::get_nym_api_urls, network_config::get_validator_nymd_urls, network_config::remove_validator, - network_config::select_validator_api_url, + network_config::select_nym_api_url, network_config::select_validator_nymd_url, network_config::update_validator_urls, state::load_config_from_files, @@ -99,13 +101,14 @@ fn main() { utils::get_old_and_incorrect_hardcoded_fee, utils::try_convert_pubkey_to_mix_id, utils::default_mixnode_cost_params, - validator_api::status::gateway_core_node_status, - validator_api::status::mixnode_core_node_status, - validator_api::status::mixnode_inclusion_probability, - validator_api::status::mixnode_reward_estimation, - validator_api::status::mixnode_stake_saturation, - validator_api::status::mixnode_status, - validator_api::status::gateway_report, + nym_api::status::compute_mixnode_reward_estimation, + nym_api::status::gateway_core_node_status, + nym_api::status::mixnode_core_node_status, + nym_api::status::mixnode_inclusion_probability, + nym_api::status::mixnode_reward_estimation, + nym_api::status::mixnode_stake_saturation, + nym_api::status::mixnode_status, + nym_api::status::gateway_report, vesting::rewards::vesting_claim_delegator_reward, vesting::rewards::vesting_claim_operator_reward, vesting::bond::vesting_bond_gateway, diff --git a/nym-wallet/src-tauri/src/network_config.rs b/nym-wallet/src-tauri/src/network_config.rs index 43e872b717..ce976b971d 100644 --- a/nym-wallet/src-tauri/src/network_config.rs +++ b/nym-wallet/src-tauri/src/network_config.rs @@ -17,7 +17,7 @@ pub async fn get_validator_nymd_urls( } #[tauri::command] -pub async fn get_validator_api_urls( +pub async fn get_nym_api_urls( network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result { @@ -41,13 +41,13 @@ pub async fn select_validator_nymd_url( } #[tauri::command] -pub async fn select_validator_api_url( +pub async fn select_nym_api_url( url: &str, network: WalletNetwork, state: tauri::State<'_, WalletState>, ) -> Result<(), BackendError> { log::debug!("Selecting new validator api_url for {network}: {url}"); - state.write().await.select_validator_api_url(url, network)?; + state.write().await.select_nym_api_url(url, network)?; Ok(()) } diff --git a/nym-wallet/src-tauri/src/operations/help/log.rs b/nym-wallet/src-tauri/src/operations/help/log.rs index c8ff873f6a..c4c2357304 100644 --- a/nym-wallet/src-tauri/src/operations/help/log.rs +++ b/nym-wallet/src-tauri/src/operations/help/log.rs @@ -5,8 +5,8 @@ use tauri::Manager; pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> { if let Some(current_log_window) = app_handle.windows().get("log") { log::info!("Closing log window..."); - if let Err(e) = current_log_window.close() { - log::error!("Unable to close log window: {:?}", e); + if let Err(err) = current_log_window.close() { + log::error!("Unable to close log window: {err}"); } return Ok(()); } @@ -17,13 +17,13 @@ pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), Backen .build() { Ok(window) => { - if let Err(e) = window.set_focus() { - log::error!("Unable to focus log window: {:?}", e); + if let Err(err) = window.set_focus() { + log::error!("Unable to focus log window: {err}"); } Ok(()) } - Err(e) => { - log::error!("Unable to create log window: {:?}", e); + Err(err) => { + log::error!("Unable to create log window: {err}"); Err(BackendError::NewWindowError) } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5811c007d1..9a3afafe6d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -128,7 +128,7 @@ async fn _connect_with_mnemonic( .map(|(network, urls)| (*network, urls.iter().next().unwrap().clone())) .collect(); - // Run connection tests on all nymd and validator-api endpoints + // Run connection tests on all nymd and nym-api endpoints let (nymd_urls, api_urls) = run_connection_test(untested_nymd_urls, untested_api_urls, &config).await; @@ -222,7 +222,7 @@ fn create_clients( }) }; - let api_url = if let Some(url) = config.get_selected_validator_api_url(&network) { + let api_url = if let Some(url) = config.get_selected_nym_api_url(&network) { log::debug!("Using selected api_url for {network}: {url}"); url.clone() } else { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index 567dc84290..1ccdc243ae 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -211,7 +211,7 @@ pub async fn get_mixnode_avg_uptime( let id = details.mix_id(); log::trace!(" >>> Get average uptime percentage: mix_id = {}", id); let avg_uptime_percent = client - .validator_api + .nym_api .get_mixnode_avg_uptime(id) .await .ok() @@ -341,3 +341,18 @@ pub async fn get_mix_node_description( .json() .await?) } + +#[tauri::command] +pub async fn get_mixnode_uptime( + mix_id: MixId, + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Get mixnode uptime"); + + let guard = state.read().await; + let client = guard.current_client()?; + let uptime = client.nym_api.get_mixnode_avg_uptime(mix_id).await?; + + log::info!(">>> Uptime response: {}", uptime.avg_uptime); + Ok(uptime.avg_uptime) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index dfbf46389b..8b8f615244 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -238,7 +238,7 @@ pub async fn get_all_mix_delegations( d.mix_id ); let avg_uptime_percent = client - .validator_api + .nym_api .get_mixnode_avg_uptime(d.mix_id) .await .ok() diff --git a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs index 6fb8893ee9..b4a048eeea 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/rewards.rs @@ -1,7 +1,7 @@ use crate::error::BackendError; use crate::state::WalletState; use crate::vesting::rewards::vesting_claim_delegator_reward; -use mixnet_contract_common::MixId; +use mixnet_contract_common::{MixId, RewardingParams}; use nym_types::transaction::TransactionExecuteResult; use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient}; use validator_client::nymd::Fee; @@ -95,3 +95,17 @@ pub async fn claim_locked_and_unlocked_delegator_reward( log::trace!("<<< {:?}", res); Ok(res) } + +#[tauri::command] +pub async fn get_current_rewarding_parameters( + state: tauri::State<'_, WalletState>, +) -> Result { + log::info!(">>> Get current rewarding params",); + + let guard = state.read().await; + let client = guard.current_client()?; + + let reward_params = client.nymd.get_rewarding_parameters().await?; + + Ok(reward_params) +} diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index 457971d8c8..c6c175ee12 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,6 +1,6 @@ pub mod help; pub mod mixnet; +pub mod nym_api; pub mod signatures; pub mod simulate; -pub mod validator_api; pub mod vesting; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/mod.rs b/nym-wallet/src-tauri/src/operations/nym_api/mod.rs similarity index 100% rename from nym-wallet/src-tauri/src/operations/validator_api/mod.rs rename to nym-wallet/src-tauri/src/operations/nym_api/mod.rs diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/nym_api/status.rs similarity index 67% rename from nym-wallet/src-tauri/src/operations/validator_api/status.rs rename to nym-wallet/src-tauri/src/operations/nym_api/status.rs index 16c83cfb67..873a1fa0a4 100644 --- a/nym-wallet/src-tauri/src/operations/validator_api/status.rs +++ b/nym-wallet/src-tauri/src/operations/nym_api/status.rs @@ -4,11 +4,11 @@ use crate::api_client; use crate::error::BackendError; use crate::state::WalletState; -use mixnet_contract_common::{IdentityKeyRef, MixId}; +use mixnet_contract_common::{reward_params::Performance, Coin, IdentityKeyRef, MixId, Percent}; use validator_client::models::{ - GatewayCoreStatusResponse, GatewayStatusReportResponse, InclusionProbabilityResponse, - MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, - StakeSaturationResponse, + ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse, + InclusionProbabilityResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse, + RewardEstimationResponse, StakeSaturationResponse, }; #[tauri::command] @@ -59,6 +59,29 @@ pub async fn mixnode_reward_estimation( .await?) } +#[tauri::command] +pub async fn compute_mixnode_reward_estimation( + mix_id: u32, + performance: Option, + pledge_amount: Option, + total_delegation: Option, + interval_operating_cost: Option, + profit_margin_percent: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let request_body = ComputeRewardEstParam { + performance, + active_in_rewarded_set: Some(true), + pledge_amount, + total_delegation, + interval_operating_cost, + profit_margin_percent, + }; + Ok(api_client!(state) + .compute_mixnode_reward_estimation(mix_id, &request_body) + .await?) +} + #[tauri::command] pub async fn mixnode_stake_saturation( mix_id: MixId, diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index e36dfe1b5c..319e645c57 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -300,7 +300,7 @@ impl WalletStateInner { }) } - /// Get the list of validator-api urls in the network config format, suitable for passing on to + /// Get the list of nym-api urls in the network config format, suitable for passing on to /// the UI pub fn get_api_urls( &self, @@ -425,14 +425,10 @@ impl WalletStateInner { Ok(()) } - pub fn select_validator_api_url( - &mut self, - url: &str, - network: Network, - ) -> Result<(), BackendError> { - self.config.select_validator_api_url(url.parse()?, network); + pub fn select_nym_api_url(&mut self, url: &str, network: Network) -> Result<(), BackendError> { + self.config.select_nym_api_url(url.parse()?, network); if let Ok(client) = self.client_mut(network) { - client.change_validator_api(url.parse()?); + client.change_nym_api(url.parse()?); } Ok(()) } @@ -489,7 +485,7 @@ macro_rules! nymd_client { #[macro_export] macro_rules! api_client { ($state:ident) => { - $state.read().await.current_client()?.validator_api + $state.read().await.current_client()?.nym_api }; } diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index e6c2dad6b9..722c0c1af3 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-wallet", - "version": "1.1.4" + "version": "1.1.6" }, "build": { "distDir": "../dist", diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index 24b2f93942..3022646798 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -15,7 +15,7 @@ export const AppBar = () => { const navigate = useNavigate(); return ( - + diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx index 6ff0151b29..ae32fdefba 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnode.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; @@ -11,6 +11,9 @@ import { Node as NodeIcon } from '../../svg-icons/node'; import { Cell, Header, NodeTable } from './NodeTable'; import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions'; import { NodeStats } from './NodeStats'; +import { getIntervalAsDate } from 'src/utils'; + +const textWhenNotName = 'This node has not yet set a name'; const headers: Header[] = [ { @@ -63,6 +66,7 @@ export const BondedMixnode = ({ network?: Network; onActionSelect: (action: TBondedMixnodeActions) => void; }) => { + const [nextEpoch, setNextEpoch] = useState(); const navigate = useNavigate(); const { name, @@ -78,6 +82,15 @@ export const BondedMixnode = ({ identityKey, host, } = mixnode; + + const getNextInterval = async () => { + try { + const { nextEpoch } = await getIntervalAsDate(); + setNextEpoch(nextEpoch); + } catch { + setNextEpoch(Error()); + } + }; const cells: Cell[] = [ { cell: `${stake.amount} ${stake.denom}`, @@ -121,6 +134,10 @@ export const BondedMixnode = ({ }, ]; + useEffect(() => { + getNextInterval(); + }, []); + return ( - {name && ( - - - {name} - - + {name?.includes(textWhenNotName) ? null : ( + + {name} + )} - + + + + + } Action={ - isMixnode(mixnode) && ( - - - - - - ) + + {isMixnode(mixnode) && ( + + + + + + )} + {nextEpoch instanceof Error ? null : ( + + Next epoch starts at {nextEpoch} + + )} + } > diff --git a/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx index 9a3c683b55..53439a8e8c 100644 --- a/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/ConfirmationModal.tsx @@ -14,6 +14,7 @@ export type ConfirmationDetailProps = { export const ConfirmationDetailsModal = ({ title, subtitle, + children, txUrl, status, onClose, @@ -23,6 +24,7 @@ export const ConfirmationDetailsModal = ({ onClose: () => void; sx?: SxProps; backdropProps?: object; + children?: React.ReactNode; }) => { if (status === 'error') { ; @@ -45,6 +47,7 @@ export const ConfirmationDetailsModal = ({ {title} {subtitle} + {children} {txUrl && } diff --git a/nym-wallet/src/components/Buy/Tutorial.tsx b/nym-wallet/src/components/Buy/Tutorial.tsx index 316f033347..8d403cb2e6 100644 --- a/nym-wallet/src/components/Buy/Tutorial.tsx +++ b/nym-wallet/src/components/Buy/Tutorial.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { Button, Stack, Typography, Grid, useMediaQuery, useTheme } from '@mui/material'; -import { Tune as TuneIcon, BorderColor as BorderColorIcon, Paid as PaidIcon } from '@mui/icons-material'; +import { Tune as TuneIcon, BorderColor as BorderColorIcon } from '@mui/icons-material'; import { CoinMark } from '@nymproject/react/coins/CoinMark'; import { NymCard, ClientAddress } from '../../components'; import { SignMessageModal } from './SignMessageModal'; import { PoweredByBity } from 'src/svg-icons'; +import { AppContext } from 'src/context'; // TODO retrieve this value from env const EXCHANGE_URL = 'https://buy.nymtech.net'; @@ -51,6 +52,7 @@ const TutorialStep = ({ ); export const Tutorial = () => { + const { clientDetails } = useContext(AppContext); const [showSignModal, setShowSignModal] = useState(false); const theme = useTheme(); const showBorder = useMediaQuery(theme.breakpoints.up('md')); @@ -123,7 +125,12 @@ export const Tutorial = () => { - diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index d800f9f487..1b4327621c 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -29,15 +29,18 @@ export const DelegationItem = ({ }) => { const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost; + const tooltipText = () => { + if (nodeIsUnbonded) { + return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.'; + } else if (item.uses_vesting_contract_tokens) { + return 'Delegation made with locked tockens'; + } else { + return ''; + } + }; + return ( - + {nodeIsUnbonded ? ( diff --git a/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx b/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx new file mode 100644 index 0000000000..d60f7886c7 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/InclusionProbability.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Typography } from '@mui/material'; +import { SelectionChance } from '@nymproject/types'; + +const colorMap: { [key in SelectionChance]: string } = { + Low: 'error.main', + Good: 'warning.main', + High: 'success.main', +}; + +const textMap: { [key in SelectionChance]: string } = { + Low: 'Low', + Good: 'Good', + High: 'High', +}; + +export const InclusionProbability = ({ probability }: { probability: SelectionChance }) => ( + {textMap[probability]} +); diff --git a/nym-wallet/src/components/RewardsPlayground/Inputs.tsx b/nym-wallet/src/components/RewardsPlayground/Inputs.tsx new file mode 100644 index 0000000000..1f1dbac935 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/Inputs.tsx @@ -0,0 +1,91 @@ +import React, { useCallback } from 'react'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Button, Grid, TextField, Typography } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { DefaultInputValues } from 'src/pages/bonding/node-settings/apy-playground'; +import { inputValidationSchema } from './inputsValidationSchema'; + +export type InputFields = { + label: string; + name: 'profitMargin' | 'uptime' | 'bond' | 'delegations' | 'operatorCost' | 'uptime'; + isPercentage?: boolean; +}[]; + +export type CalculateArgs = { + bond: string; + delegations: string; + uptime: string; + profitMargin: string; + operatorCost: string; +}; + +const inputFields: InputFields = [ + { label: 'Profit margin', name: 'profitMargin', isPercentage: true }, + { label: 'Operator cost', name: 'operatorCost' }, + { label: 'Bond', name: 'bond' }, + { label: 'Delegations', name: 'delegations' }, + { label: 'Uptime', name: 'uptime', isPercentage: true }, +]; + +export const Inputs = ({ + onCalculate, + defaultValues, +}: { + onCalculate: (args: CalculateArgs) => Promise; + defaultValues: DefaultInputValues; +}) => { + const handleCalculate = useCallback( + async (args: CalculateArgs) => { + onCalculate({ + bond: args.bond, + delegations: args.delegations, + uptime: args.uptime, + profitMargin: args.profitMargin, + operatorCost: args.operatorCost, + }); + }, + [onCalculate], + ); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(inputValidationSchema), + defaultValues, + }); + + return ( + + {inputFields.map((field) => ( + + {field.isPercentage ? '%' : 'NYM'}, + }} + InputLabelProps={{ shrink: true }} + /> + + ))}{' '} + + + + + ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx b/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx new file mode 100644 index 0000000000..fb8a239be0 --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/NodeDetail.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Card, CardContent, Divider, Stack, Typography } from '@mui/material'; +import { SelectionChance } from '@nymproject/types'; +import { InclusionProbability } from './InclusionProbability'; + +const computeSelectionProbability = (saturation: number): SelectionChance => { + if (saturation < 5) return 'Low'; + + if (saturation > 5 && saturation < 15) return 'Good'; + + return 'High'; +}; + +export const NodeDetails = ({ saturation }: { saturation?: string }) => { + if (!saturation) return null; + + return ( + + + + Stake saturation + {saturation || '- '}% + + + + Selection probability + + + + + ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx b/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx new file mode 100644 index 0000000000..19196c176d --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/ResultsTable.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { + Card, + CardContent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from '@mui/material'; + +export type Results = { + operator: { + daily: string; + monthly: string; + yearly: string; + }; + delegator: { + daily: string; + monthly: string; + yearly: string; + }; + total: { + daily: string; + monthly: string; + yearly: string; + }; +}; + +const tableHeader = [ + { title: 'Estimated rewards', bold: true }, + { title: 'Per day' }, + { title: 'Per month' }, + { title: 'Per year' }, +]; + +export const ResultsTable = ({ results }: { results: Results }) => { + const tableRows = [ + { title: 'Total node reward', ...results.total }, + { title: 'Operator rewards', ...results.operator }, + { title: 'Delegator rewards', ...results.delegator }, + ]; + + return ( + + + + + + + {tableHeader.map((header) => ( + + {header.title} + + ))} + + + + {tableRows.map((row) => ( + + {row.title} + {row.daily} + {row.monthly} + {row.yearly} + + ))} + +
+
+
+
+ ); +}; diff --git a/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts b/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts new file mode 100644 index 0000000000..ef5d9ecc0d --- /dev/null +++ b/nym-wallet/src/components/RewardsPlayground/inputsValidationSchema.ts @@ -0,0 +1,41 @@ +import * as Yup from 'yup'; +import { isGreaterThan, isLessThan } from 'src/utils'; + +export const inputValidationSchema = Yup.object().shape({ + profitMargin: Yup.string() + .required('profit margin is a required field') + .test('Is valid profit margin value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + + if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true; + return ctx.createError({ message: 'Profit margin must be a number from 0 and 100' }); + }), + uptime: Yup.string() + .required() + .test('Is valid uptime value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + if (stringValueToNumber && isGreaterThan(stringValueToNumber, 0) && isLessThan(stringValueToNumber, 101)) + return true; + return ctx.createError({ message: 'Uptime must be a number between 0 and 100' }); + }), + bond: Yup.string() + .required() + .test('Is valid bond value', (value, ctx) => { + if (Number(value)) return true; + return ctx.createError({ message: 'Bond must be a valid number' }); + }), + delegations: Yup.string() + .required() + .test('Is valid delegation value', (value, ctx) => { + if (Number(value)) return true; + return ctx.createError({ message: 'Delegations must be a valid number' }); + }), + operatorCost: Yup.string() + .required('operator cost is a required field') + .test('Is valid operator cost value', (value, ctx) => { + const stringValueToNumber = Math.round(Number(value)); + + if (isGreaterThan(stringValueToNumber, -1) && isLessThan(stringValueToNumber, 101)) return true; + return ctx.createError({ message: 'Operator cost must be a valid number' }); + }), +}); diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index 7225360456..94bf5271ca 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -24,7 +24,6 @@ import { vestingBondMixNode, vestingUnbondGateway, vestingUnbondMixnode, - getPendingEpochEvents, updateMixnodeCostParams as updateMixnodeCostParamsRequest, vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest, getNodeDescription as getNodeDescriptionRequest, @@ -36,6 +35,7 @@ import { getMixnodeAvgUptime, getMixnodeRewardEstimation, getGatewayReport, + getMixnodeUptime, } from '../requests'; import { useCheckOwnership } from '../hooks/useCheckOwnership'; import { AppContext } from './main'; @@ -71,10 +71,12 @@ export type TBondedMixnode = { verlocPort: number; version: string; isUnbonding: boolean; + uptime: number; }; export interface TBondedGateway { - name: string; + name?: string; + id: number; identityKey: string; ip: string; bond: DecCoin; @@ -154,14 +156,18 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod status: MixnodeStatus; stakeSaturation: string; estimatedRewards?: DecCoin; + uptime: number; } = { status: 'not_found', stakeSaturation: '0', + uptime: 0, }; try { const statusResponse = await getMixnodeStatus(mixId); + const uptime = await getMixnodeUptime(mixId); additionalDetails.status = statusResponse.status; + additionalDetails.uptime = uptime; } catch (e) { Console.log('getMixnodeStatus fails', e); } @@ -259,7 +265,8 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod rewarding_details, bond_information: { mix_id }, } = data; - const { status, stakeSaturation, estimatedRewards } = await getAdditionalMixnodeDetails(mix_id); + + const { status, stakeSaturation, estimatedRewards, uptime } = await getAdditionalMixnodeDetails(mix_id); const setProbabilities = await getSetProbabilities(mix_id); const nodeDescription = await getNodeDescription( bond_information.mix_node.host, @@ -267,6 +274,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod ); const routingScore = await getAvgUptime(); setBondedNode({ + id: data.bond_information.mix_id, name: nodeDescription?.name, mixId: mix_id, identityKey: bond_information.mix_node.identity_key, @@ -279,6 +287,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod delegators: rewarding_details.unique_delegations, proxy: bond_information.proxy, operatorRewards, + uptime, status, stakeSaturation, operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost), diff --git a/nym-wallet/src/context/mocks/bonding.tsx b/nym-wallet/src/context/mocks/bonding.tsx index 95e770850b..f38168ee28 100644 --- a/nym-wallet/src/context/mocks/bonding.tsx +++ b/nym-wallet/src/context/mocks/bonding.tsx @@ -7,8 +7,8 @@ import { mockSleep } from './utils'; const SLEEP_MS = 1000; const bondedMixnodeMock: TBondedMixnode = { - name: 'Monster node', mixId: 1, + name: 'Monster node', identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', stake: { denom: 'nym', amount: '1234' }, bond: { denom: 'nym', amount: '1234' }, @@ -28,9 +28,11 @@ const bondedMixnodeMock: TBondedMixnode = { verlocPort: 1790, version: '1.0.2', isUnbonding: false, + uptime: 1, }; const bondedGatewayMock: TBondedGateway = { + id: 1, name: 'Monster node', identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', ip: '112.43.234.57', diff --git a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx index da50bc2d2d..2a9ef67f13 100644 --- a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx @@ -10,13 +10,15 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { NymCard } from 'src/components'; import { PageLayout } from 'src/layouts'; import { Tabs } from 'src/components/Tabs'; -import { useBondingContext, BondingContextProvider } from 'src/context'; +import { useBondingContext, BondingContextProvider, TBondedMixnode } from 'src/context'; import { AppContext, urls } from 'src/context/main'; import { NodeGeneralSettings } from './settings-pages/general-settings'; import { NodeUnbondPage } from './settings-pages/NodeUnbondPage'; import { createNavItems } from './node-settings.constant'; import { isMixnode } from 'src/types'; +import { ApyPlayground } from './apy-playground'; +import { getIntervalAsDate } from 'src/utils'; export const NodeSettings = () => { const theme = useTheme(); @@ -25,7 +27,7 @@ export const NodeSettings = () => { const navigate = useNavigate(); const location = useLocation(); - const [confirmationDetails, setConfirmationDetails] = useState(); + const [confirmationDetails, setConfirmationDetails] = useState(); const [value, setValue] = React.useState('General'); const handleChange = (event: React.SyntheticEvent, tab: string) => { setValue(tab); @@ -39,9 +41,11 @@ export const NodeSettings = () => { const handleUnbond = async (fee?: FeeDetails) => { const tx = await unbond(fee); + const { nextEpoch } = await getIntervalAsDate(); setConfirmationDetails({ status: 'success', title: 'Unbond successful', + subtitle: `This operation will complete when the new epoch starts at: ${nextEpoch}`, txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, }); }; @@ -123,6 +127,7 @@ export const NodeSettings = () => { {value === 'Unbond' && bondedNode && ( )} + {value === 'Playground' && bondedNode && } {confirmationDetails && confirmationDetails.status === 'success' && ( { setConfirmationDetails(undefined); navigate('/bonding'); }} - /> + > + + You should NOT shutdown your {isMixnode(bondedNode) ? 'mix node' : 'gateway'} until the unbond process is + complete + + )} diff --git a/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx new file mode 100644 index 0000000000..5a3b5f4c6b --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx @@ -0,0 +1,139 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { Box, Card, CardContent, CardHeader, Grid, Typography } from '@mui/material'; +import { decimalToPercentage, percentToDecimal } from '@nymproject/types'; +import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable'; +import { getDelegationSummary } from 'src/requests'; +import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail'; +import { Inputs, CalculateArgs } from 'src/components/RewardsPlayground/Inputs'; +import { AppContext, TBondedMixnode } from 'src/context'; +import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils'; +import { useSnackbar } from 'notistack'; +import { LoadingModal } from 'src/components/Modals/LoadingModal'; + +export type DefaultInputValues = { + profitMargin: string; + uptime: string; + bond: string; + delegations: string; + operatorCost: string; +}; + +export const ApyPlayground = ({ bondedNode }: { bondedNode: TBondedMixnode }) => { + const { enqueueSnackbar } = useSnackbar(); + + const [results, setResults] = useState({ + total: { daily: '-', monthly: '-', yearly: '-' }, + operator: { daily: '-', monthly: '-', yearly: '-' }, + delegator: { daily: '-', monthly: '-', yearly: '-' }, + }); + + const [defaultInputValues, setDefaultInputValues] = useState(); + const [stakeSaturation, setStakeSaturation] = useState(); + const [isLoading, setIsLoading] = useState(true); + + const initialise = async (node: TBondedMixnode) => { + try { + const delegations = await getDelegationSummary(); + + const { estimation } = await computeEstimate({ + mixId: node.mixId, + uptime: node.uptime.toString(), + profitMargin: node.profitMargin, + operatorCost: node.operatorCost.amount, + totalDelegation: delegations.total_delegations.amount, + pledgeAmount: node.bond.amount, + }); + + setResults( + handleCalculatePeriodRewards({ + estimatedOperatorReward: estimation.operator, + estimatedDelegatorsReward: estimation.delegates, + }), + ); + + setStakeSaturation(node.stakeSaturation); + + setDefaultInputValues({ + profitMargin: node.profitMargin, + uptime: (node.uptime || 0).toString(), + bond: node.bond.amount || '', + delegations: delegations.total_delegations.amount, + operatorCost: node.operatorCost.amount, + }); + setIsLoading(false); + } catch (e) { + enqueueSnackbar(e as string, { variant: 'error' }); + } + }; + + useEffect(() => { + if (bondedNode) { + initialise(bondedNode); + } + }, []); + + if (isLoading) return ; + + const handleCalculateEstimate = async ({ bond, delegations, uptime, profitMargin, operatorCost }: CalculateArgs) => { + try { + const { estimation, reward_params } = await computeEstimate({ + mixId: bondedNode.mixId, + uptime: uptime, + profitMargin: profitMargin, + operatorCost: operatorCost, + totalDelegation: delegations, + pledgeAmount: bond, + }); + + const estimationResult = handleCalculatePeriodRewards({ + estimatedOperatorReward: estimation.operator, + estimatedDelegatorsReward: estimation.delegates, + }); + + const computedStakeSaturation = computeStakeSaturation( + bond, + delegations, + reward_params.interval.stake_saturation_point, + ); + + setStakeSaturation(computedStakeSaturation); + setResults(estimationResult); + } catch (e) { + console.log(e); + } + }; + + return ( + + + Playground + + + This is your parameters playground - change the parameters below to see the node specific estimations in the + table + + {defaultInputValues && ( + + + Estimation calculator + + } + /> + + + + + )} + + + + + + + + + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx b/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx new file mode 100644 index 0000000000..bc99898b45 --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/apy-playground/utils.tsx @@ -0,0 +1,66 @@ +import { decimalToPercentage, percentToDecimal } from '@nymproject/types'; +import { computeMixnodeRewardEstimation } from 'src/requests'; + +const SCALE_FACTOR = 1_000_000; + +export const computeStakeSaturation = (bond: string, delegations: string, stakeSaturationPoint: string) => { + const res = ((+bond + +delegations) * SCALE_FACTOR) / +stakeSaturationPoint; + return decimalToPercentage(res.toFixed(18).toString()); +}; + +export const computeEstimate = async ({ + mixId, + uptime, + pledgeAmount, + totalDelegation, + profitMargin, + operatorCost, +}: { + mixId: number; + uptime: string; + pledgeAmount: string; + totalDelegation: string; + profitMargin: string; + operatorCost: string; +}) => { + const computedEstimate = await computeMixnodeRewardEstimation({ + mixId: mixId, + performance: percentToDecimal(uptime), + pledgeAmount: Math.round(+pledgeAmount * SCALE_FACTOR), + totalDelegation: Math.round(+totalDelegation * SCALE_FACTOR), + profitMarginPercent: percentToDecimal(profitMargin), + intervalOperatingCost: { denom: 'unym', amount: Math.round(+operatorCost * SCALE_FACTOR).toString() }, + }); + + return computedEstimate; +}; + +export const handleCalculatePeriodRewards = ({ + estimatedOperatorReward, + estimatedDelegatorsReward, +}: { + estimatedOperatorReward: string; + estimatedDelegatorsReward: string; +}) => { + const dailyOperatorReward = (+estimatedOperatorReward / SCALE_FACTOR) * 24; // epoch_reward * 1 epoch_per_hour * 24 hours + const dailyDelegatorReward = (+estimatedDelegatorsReward / SCALE_FACTOR) * 24; + const dailyTotal = dailyOperatorReward + dailyDelegatorReward; + + return { + total: { + daily: dailyTotal.toFixed(3).toString(), + monthly: (dailyTotal * 30).toFixed(3).toString(), + yearly: (dailyTotal * 365).toFixed(3).toString(), + }, + operator: { + daily: dailyOperatorReward.toFixed(3).toString(), + monthly: (dailyOperatorReward * 30).toFixed(3).toString(), + yearly: (dailyOperatorReward * 365).toFixed(3).toString(), + }, + delegator: { + daily: dailyDelegatorReward.toFixed(3).toString(), + monthly: (dailyDelegatorReward * 30).toFixed(3).toString(), + yearly: (dailyDelegatorReward * 365).toFixed(3).toString(), + }, + }; +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx index f00409a182..0e8b6204fd 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx @@ -1,5 +1,5 @@ export const createNavItems = (isMixnode: boolean) => { const navItems = ['Unbond']; - if (isMixnode) return ['General', ...navItems]; + if (isMixnode) return ['General', 'Playground', ...navItems]; return navItems; }; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx index 7aa7407a48..b342f32d15 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx @@ -1,24 +1,12 @@ import React, { useContext, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; -import { - Button, - Divider, - Typography, - TextField, - InputAdornment, - Grid, - CircularProgress, - Box, - FormHelperText, -} from '@mui/material'; +import { Button, Divider, Typography, TextField, InputAdornment, Grid, Box, FormHelperText } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; -import { add, format, fromUnixTime } from 'date-fns'; import { isMixnode } from 'src/types'; import { - getCurrentInterval, getPendingIntervalEvents, simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams, @@ -29,6 +17,7 @@ import { TBondedMixnode } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; import { Console } from 'src/utils/console'; +import { getIntervalAsDate } from 'src/utils'; import { Alert } from 'src/components/Alert'; import { ChangeMixCostParams } from 'src/pages/bonding/types'; import { AppContext } from 'src/context'; @@ -39,7 +28,6 @@ import { LoadingModal } from 'src/components/Modals/LoadingModal'; export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const [intervalTime, setIntervalTime] = useState(); - const [nextEpoch, setNextEpoch] = useState(); const [pendingUpdates, setPendingUpdates] = useState(); const { clientDetails } = useContext(AppContext); const theme = useTheme(); @@ -63,27 +51,13 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode defaultValues, }); - const getIntervalAsDate = async () => { - const interval = await getCurrentInterval(); - const secondsToNextInterval = - Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); - - setIntervalTime( - format( - add(new Date(), { - seconds: secondsToNextInterval, - }), - 'MM/dd/yyyy HH:mm', - ), - ); - setNextEpoch( - format( - add(fromUnixTime(Number(interval.current_epoch_start_unix)), { - seconds: Number(interval.epoch_length_seconds), - }), - 'HH:mm', - ), - ); + const getCurrentInterval = async () => { + try { + const { nextInterval } = await getIntervalAsDate(); + setIntervalTime(nextInterval); + } catch { + console.log('cant retrieve next interval'); + } }; const getPendingEvents = async () => { @@ -107,7 +81,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }; useEffect(() => { - getIntervalAsDate(); + getCurrentInterval(); getPendingEvents(); }, []); @@ -137,7 +111,16 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }; return ( - + {fee && ( - - {`Next epoch ${nextEpoch}`} - {`Next interval: ${intervalTime}`} } @@ -177,7 +157,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode {isMixnode(bondedNode) && ( - + )} - + @@ -223,33 +203,31 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode Changes to cost will be applied in the next interval. - - - { - setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true }); - }} - validationError={errors.operatorCost?.amount?.message} - denom={clientDetails?.display_mix_denom || 'nym'} - initialValue={defaultValues.operatorCost.amount} - /> - {pendingUpdates && ( - - Your last change to{' '} - - {pendingUpdates.interval_operating_cost.amount}{' '} - {pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '} - - will be applied in the next interval - - )} - + + { + setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true }); + }} + validationError={errors.operatorCost?.amount?.message} + denom={clientDetails?.display_mix_denom || 'nym'} + initialValue={defaultValues.operatorCost.amount} + /> + {pendingUpdates && ( + + Your last change to{' '} + + {pendingUpdates.interval_operating_cost.amount}{' '} + {pendingUpdates?.interval_operating_cost.denom.toUpperCase()}{' '} + + will be applied in the next interval + + )} - + )} {!!delegations?.length && ( - - + {nextEpoch instanceof Error ? null : ( + + Next epoch starts at {nextEpoch} + + )} )} {delegationsComponent(delegations)} diff --git a/nym-wallet/src/requests/queries.ts b/nym-wallet/src/requests/queries.ts index 5fba51beec..640fc79abb 100644 --- a/nym-wallet/src/requests/queries.ts +++ b/nym-wallet/src/requests/queries.ts @@ -8,6 +8,7 @@ import { RewardEstimationResponse, WrappedDelegationEvent, PendingIntervalEvent, + Coin, } from '@nymproject/types'; import { Interval, TGatewayReport, TNodeDescription } from 'src/types'; import { invokeWrapper } from './wrapper'; @@ -51,3 +52,17 @@ export const getPendingIntervalEvents = async () => export const getGatewayReport = async (identity: string) => invokeWrapper('gateway_report', { identity }); + +export const computeMixnodeRewardEstimation = async (args: { + mixId: number; + performance: string; + pledgeAmount: number; + totalDelegation: number; + profitMarginPercent: string; + intervalOperatingCost: { denom: 'unym'; amount: string }; +}) => { + console.log(args); + + return invokeWrapper('compute_mixnode_reward_estimation', args); +}; +export const getMixnodeUptime = async (mixId: number) => invokeWrapper('get_mixnode_uptime', { mixId }); diff --git a/nym-wallet/src/requests/rewards.ts b/nym-wallet/src/requests/rewards.ts index 5c8c63e34e..36b90838e7 100644 --- a/nym-wallet/src/requests/rewards.ts +++ b/nym-wallet/src/requests/rewards.ts @@ -1,4 +1,4 @@ -import { Fee, FeeDetails, TransactionExecuteResult } from '@nymproject/types'; +import { Fee, FeeDetails, RewardingParams, TransactionExecuteResult } from '@nymproject/types'; import { invokeWrapper } from './wrapper'; export const claimOperatorReward = async (fee?: Fee) => @@ -9,3 +9,6 @@ export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) => mixId, fee: fee?.fee, }); + +export const getCurrentRewardingParameter = async () => + invokeWrapper('get_current_rewarding_parameters', {}); diff --git a/nym-wallet/src/types/rust/AppEnv.ts b/nym-wallet/src/types/rust/AppEnv.ts index 3f47419469..861ecd9ef5 100644 --- a/nym-wallet/src/types/rust/AppEnv.ts +++ b/nym-wallet/src/types/rust/AppEnv.ts @@ -1,2 +1,5 @@ - -export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, } \ No newline at end of file +export interface AppEnv { + ADMIN_ADDRESS: string | null; + SHOW_TERMINAL: string | null; + ENABLE_QA_MODE: string | null; +} diff --git a/nym-wallet/src/types/rust/Network.ts b/nym-wallet/src/types/rust/Network.ts index fd31bccafa..f8615cd992 100644 --- a/nym-wallet/src/types/rust/Network.ts +++ b/nym-wallet/src/types/rust/Network.ts @@ -1,2 +1 @@ - -export type Network = "QA" | "SANDBOX" | "MAINNET"; \ No newline at end of file +export type Network = 'QA' | 'SANDBOX' | 'MAINNET'; diff --git a/nym-wallet/src/types/rust/StateParams.ts b/nym-wallet/src/types/rust/StateParams.ts index 85c362a3b2..c9420f50dc 100644 --- a/nym-wallet/src/types/rust/StateParams.ts +++ b/nym-wallet/src/types/rust/StateParams.ts @@ -1,3 +1,7 @@ -import type { DecCoin } from "../../../../ts-packages/types/src/types/rust/DecCoin"; +import type { DecCoin } from '../../../../ts-packages/types/src/types/rust/DecCoin'; -export interface TauriContractStateParams { minimum_mixnode_pledge: DecCoin, minimum_gateway_pledge: DecCoin, minimum_mixnode_delegation: DecCoin | null, } \ No newline at end of file +export interface TauriContractStateParams { + minimum_mixnode_pledge: DecCoin; + minimum_gateway_pledge: DecCoin; + minimum_mixnode_delegation: DecCoin | null; +} diff --git a/nym-wallet/src/types/rust/ValidatorUrl.ts b/nym-wallet/src/types/rust/ValidatorUrl.ts index 9c74679bac..801e045a00 100644 --- a/nym-wallet/src/types/rust/ValidatorUrl.ts +++ b/nym-wallet/src/types/rust/ValidatorUrl.ts @@ -1,2 +1,4 @@ - -export interface ValidatorUrl { url: string, name: string | null, } \ No newline at end of file +export interface ValidatorUrl { + url: string; + name: string | null; +} diff --git a/nym-wallet/src/types/rust/ValidatorUrls.ts b/nym-wallet/src/types/rust/ValidatorUrls.ts index 3a104150b1..98ca799fb5 100644 --- a/nym-wallet/src/types/rust/ValidatorUrls.ts +++ b/nym-wallet/src/types/rust/ValidatorUrls.ts @@ -1,3 +1,5 @@ -import type { ValidatorUrl } from "./ValidatorUrl"; +import type { ValidatorUrl } from './ValidatorUrl'; -export interface ValidatorUrls { urls: Array, } \ No newline at end of file +export interface ValidatorUrls { + urls: Array; +} diff --git a/nym-wallet/src/utils/index.ts b/nym-wallet/src/utils/index.ts index 49d56c6f72..936a89e57d 100644 --- a/nym-wallet/src/utils/index.ts +++ b/nym-wallet/src/utils/index.ts @@ -2,11 +2,20 @@ import { appWindow } from '@tauri-apps/api/window'; import bs58 from 'bs58'; import Big from 'big.js'; import { valid } from 'semver'; +import { add, format, fromUnixTime } from 'date-fns'; import { isValidRawCoin, DecCoin, MixNodeCostParams } from '@nymproject/types'; import { TPoolOption } from 'src/components'; -import { getDefaultMixnodeCostParams, getLockedCoins, getSpendableCoins, userBalance } from '../requests'; +import { + getCurrentInterval, + getDefaultMixnodeCostParams, + getLockedCoins, + getSpendableCoins, + userBalance, +} from '../requests'; import { Console } from './console'; +export * from './nextEpoch'; + export const validateKey = (key: string, bytesLength: number): boolean => { // it must be a valid base58 key try { @@ -198,3 +207,25 @@ export const unymToNym = (unym: string | Big, dp = 4) => { } return nym; }; + +export const getIntervalAsDate = async () => { + const interval = await getCurrentInterval(); + const secondsToNextInterval = + Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); + + const nextInterval = format( + add(new Date(), { + seconds: secondsToNextInterval, + }), + 'dd/MM/yyyy, HH:mm', + ); + + const nextEpoch = format( + add(fromUnixTime(Number(interval.current_epoch_start_unix)), { + seconds: Number(interval.epoch_length_seconds), + }), + 'HH:mm', + ); + + return { nextEpoch, nextInterval }; +}; diff --git a/nym-wallet/src/utils/nextEpoch.ts b/nym-wallet/src/utils/nextEpoch.ts new file mode 100644 index 0000000000..db80a22280 --- /dev/null +++ b/nym-wallet/src/utils/nextEpoch.ts @@ -0,0 +1,23 @@ +import { getCurrentInterval } from 'src/requests'; +import { add, format, fromUnixTime } from 'date-fns'; + +export const getIntervalAsDate = async () => { + const interval = await getCurrentInterval(); + const secondsToNextInterval = + Number(interval.epochs_in_interval - interval.current_epoch_id) * Number(interval.epoch_length_seconds); + + const intervalTime = format( + add(new Date(), { + seconds: secondsToNextInterval, + }), + 'dd/MM/yyyy, HH:mm', + ); + const nextEpoch = format( + add(fromUnixTime(Number(interval.current_epoch_start_unix)), { + seconds: Number(interval.epoch_length_seconds), + }), + 'HH:mm', + ); + + return { intervalTime, nextEpoch }; +}; \ No newline at end of file diff --git a/sdk/typescript/examples/plain-html/src/index.ts b/sdk/typescript/examples/plain-html/src/index.ts index 16f53c380c..d279586b72 100644 --- a/sdk/typescript/examples/plain-html/src/index.ts +++ b/sdk/typescript/examples/plain-html/src/index.ts @@ -15,11 +15,11 @@ async function main() { } // // mixnet v1 - // const validatorApiUrl = 'https://validator.nymtech.net/api'; + // const nymApiUrl = 'https://validator.nymtech.net/api'; // const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; // mixnet v2 - const validatorApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; + const nymApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL'; // subscribe to connect event, so that we can show the client's address @@ -46,7 +46,7 @@ async function main() { // start up the client await nym.client.start({ clientId: 'My awesome client', - validatorApiUrl, + nymApiUrl, preferredGatewayIdentityKey, }); } diff --git a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx index 6f6f4c2039..73c028d062 100644 --- a/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx +++ b/sdk/typescript/examples/react-webpack-with-theme-example/src/App.tsx @@ -95,16 +95,16 @@ export const Content: React.FC = () => { React.useEffect(() => { if (isReady) { // // mixnet v1 - // const validatorApiUrl = 'https://validator.nymtech.net/api'; + // const nymApiUrl = 'https://validator.nymtech.net/api'; // const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'; // mixnet v2 - const validatorApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; + const nymApiUrl = 'https://qwerty-validator-api.qa.nymte.ch/api'; // "http://localhost:8081"; const preferredGatewayIdentityKey = undefined; // '36vfvEyBzo5cWEFbnP7fqgY39kFw9PQhvwzbispeNaxL'; connect({ clientId: 'Example Client', - validatorApiUrl, + nymApiUrl, preferredGatewayIdentityKey, }); } diff --git a/sdk/typescript/packages/sdk/README.md b/sdk/typescript/packages/sdk/README.md index e5f2fd51c5..af847d969a 100644 --- a/sdk/typescript/packages/sdk/README.md +++ b/sdk/typescript/packages/sdk/README.md @@ -18,7 +18,7 @@ import { createNymMixnetClient } from '@nymproject/sdk'; const main = async () => { const nym = await createNymMixnetClient(); - const validatorApiUrl = 'https://validator.nymtech.net/api'; + const nymApiUrl = 'https://validator.nymtech.net/api'; // show message payload content when received nym.events.subscribeToTextMessageReceivedEvent((e) => { @@ -28,7 +28,7 @@ const main = async () => { // start the client and connect to a gateway await nym.client.start({ clientId: 'My awesome client', - validatorApiUrl, + nymApiUrl, }); // send a message to yourself diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts index 36aacd5adb..498e1b40de 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/types.ts @@ -21,13 +21,13 @@ export interface NymClientConfig { /** * The URL of a validator API to query for the network topology. */ - validatorApiUrl: string; + nymApiUrl: string; /** * Optional. The identity key of the preferred gateway to connect to. */ preferredGatewayIdentityKey?: string; - + /** * Optional. The listener websocket of the preferred gateway to connect to. */ diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts index cb1c8a32e4..87d44bbb4b 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts @@ -130,19 +130,19 @@ wasm_bindgen(wasmUrl) const startHandler = async (config: NymClientConfig) => { // fetch the gateway details (randomly chosen if no preferred gateway is set) const gatewayEndpoint = await wasm_bindgen.get_gateway( - config.validatorApiUrl, + config.nymApiUrl, config.preferredGatewayIdentityKey, ); - + // set a different gatewayListener in order to avoid workaround ws over https error if (config.gatewayListener) - gatewayEndpoint.gateway_listener = config.gatewayListener; - + gatewayEndpoint.gateway_listener = config.gatewayListener; + // create the client, passing handlers for events wrapper.init( new wasm_bindgen.Config( config.clientId, - config.validatorApiUrl, + config.nymApiUrl, gatewayEndpoint, config.debug || wasm_bindgen.default_debug(), ), diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index d693ed4422..04f360d7dd 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.4" +version = "1.1.5" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2021" rust-version = "1.65" diff --git a/service-providers/network-requester/src/allowed_hosts.rs b/service-providers/network-requester/src/allowed_hosts.rs index 3746d9a706..0350ab1f7c 100644 --- a/service-providers/network-requester/src/allowed_hosts.rs +++ b/service-providers/network-requester/src/allowed_hosts.rs @@ -39,7 +39,7 @@ impl OutboundRequestFilter { ) -> OutboundRequestFilter { let domain_list = match Self::fetch_domain_list() { Ok(list) => list, - Err(e) => panic!("Couldn't fetch domain list for request filtering, do you have an internet connection?: {:?}", e), + Err(err) => panic!("Couldn't fetch domain list for request filtering, do you have an internet connection?: {err}"), }; OutboundRequestFilter { allowed_hosts, diff --git a/service-providers/network-requester/src/connection.rs b/service-providers/network-requester/src/connection.rs index f4ffaca992..3457375737 100644 --- a/service-providers/network-requester/src/connection.rs +++ b/service-providers/network-requester/src/connection.rs @@ -7,7 +7,7 @@ use proxy_helpers::connection_controller::ConnectionReceiver; use proxy_helpers::proxy_runner::{MixProxySender, ProxyRunner}; use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Response}; use std::io; -use task::ShutdownListener; +use task::TaskClient; use tokio::net::TcpStream; /// A TCP connection between the Socks5 service provider, which makes @@ -42,7 +42,7 @@ impl Connection { mix_receiver: ConnectionReceiver, mix_sender: MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { let stream = self.conn.take().unwrap(); let remote_source_address = "???".to_string(); // we don't know ip address of requester diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 34fb42e7c0..42ded9fbd0 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -28,7 +28,7 @@ use socks5_requests::{ use statistics_common::collector::StatisticsSender; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; -use task::ShutdownListener; +use task::TaskClient; use tokio_tungstenite::tungstenite::protocol::Message; use websocket_requests::{requests::ClientRequest, responses::ServerResponse}; @@ -234,7 +234,7 @@ impl ServiceProvider { continue; } ServerResponse::Error(err) => { - panic!("received error from native client! - {}", err) + panic!("received error from native client! - {err}") } _ => unimplemented!("probably should never be reached?"), }; @@ -250,7 +250,7 @@ impl ServiceProvider { controller_sender: ControllerSender, mix_input_sender: MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { let mut conn = match Connection::new(conn_id, remote_addr.clone(), return_address.clone()).await { @@ -313,7 +313,7 @@ impl ServiceProvider { lane_queue_lengths: LaneQueueLengths, sender_tag: Option, connect_req: Box, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { let return_address = match ReturnAddress::new(connect_req.return_address, sender_tag) { Some(address) => address, @@ -379,7 +379,7 @@ impl ServiceProvider { mix_input_sender: &MixProxySender<(Socks5Message, ReturnAddress)>, lane_queue_lengths: LaneQueueLengths, stats_collector: Option, - shutdown: ShutdownListener, + shutdown: TaskClient, ) { let deserialized_msg = match Socks5Message::try_from_bytes(&message.message) { Ok(msg) => msg, @@ -445,7 +445,7 @@ impl ServiceProvider { tokio::sync::mpsc::channel::<(Socks5Message, ReturnAddress)>(1); // Used to notify tasks to shutdown. Not all tasks fully supports this (yet). - let shutdown = task::ShutdownNotifier::default(); + let shutdown = task::TaskManager::default(); // Channel for announcing client connection state by the controller. // The `mixnet_response_listener` will use this to either report closed connection to the diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 00b76f524e..251b1a34a9 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.4" +version = "1.1.5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/service-providers/network-statistics/src/api/error.rs b/service-providers/network-statistics/src/api/error.rs index d2b190840e..bebafc2fde 100644 --- a/service-providers/network-statistics/src/api/error.rs +++ b/service-providers/network-statistics/src/api/error.rs @@ -12,10 +12,10 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum NetworkStatisticsAPIError { - #[error("{0}")] + #[error(transparent)] RocketError(#[from] Box), - #[error("{0}")] + #[error(transparent)] StorageError(#[from] NetworkStatisticsStorageError), } diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index ddc21464ff..5813e903bb 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.4" +version = "1.1.5" authors = ["Nym Technologies SA"] edition = "2021" diff --git a/tools/nym-cli/src/main.rs b/tools/nym-cli/src/main.rs index ce81cd6a56..306d383c34 100644 --- a/tools/nym-cli/src/main.rs +++ b/tools/nym-cli/src/main.rs @@ -36,7 +36,7 @@ pub(crate) struct Cli { #[clap( help = "Overrides the validator API URL provided either as an environment variable API_VALIDATOR or in a config file" )] - pub(crate) validator_api_url: Option, + pub(crate) nym_api_url: Option, #[clap(long, global = true)] #[clap( @@ -77,7 +77,7 @@ pub(crate) enum Commands { async fn execute(cli: Cli) -> anyhow::Result<()> { let args = ClientArgs { nymd_url: cli.nymd_url, - validator_api_url: cli.validator_api_url, + nym_api_url: cli.nym_api_url, mnemonic: cli.mnemonic, mixnet_contract_address: cli.mixnet_contract_address, vesting_contract_address: cli.vesting_contract_address, diff --git a/tools/nym-cli/src/validator/mixnet/delegators/mod.rs b/tools/nym-cli/src/validator/mixnet/delegators/mod.rs index ed1ac2f211..3e40d2d26a 100644 --- a/tools/nym-cli/src/validator/mixnet/delegators/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/delegators/mod.rs @@ -3,7 +3,7 @@ use network_defaults::NymNetworkDetails; use nym_cli_commands::context::{ - create_signing_client, create_signing_client_with_validator_api, ClientArgs, + create_signing_client, create_signing_client_with_nym_api, ClientArgs, }; pub(crate) mod rewards; @@ -24,7 +24,7 @@ pub(crate) async fn execute( nym_cli_commands::validator::mixnet::delegators::undelegate_from_mixnode::undelegate_from_mixnode(args, create_signing_client(global_args, network_details)?).await } nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::List(args) => { - nym_cli_commands::validator::mixnet::delegators::query_for_delegations::execute(args, create_signing_client_with_validator_api(global_args, network_details)?).await + nym_cli_commands::validator::mixnet::delegators::query_for_delegations::execute(args, create_signing_client_with_nym_api(global_args, network_details)?).await } _ => unreachable!(), } diff --git a/tools/nym-cli/src/validator/mixnet/query/mod.rs b/tools/nym-cli/src/validator/mixnet/query/mod.rs index 21afb8c833..ea41ebd7d6 100644 --- a/tools/nym-cli/src/validator/mixnet/query/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/query/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use network_defaults::NymNetworkDetails; -use nym_cli_commands::context::create_query_client_with_validator_api; +use nym_cli_commands::context::create_query_client_with_nym_api; pub(crate) async fn execute( query: nym_cli_commands::validator::mixnet::query::MixnetQuery, @@ -12,14 +12,14 @@ pub(crate) async fn execute( nym_cli_commands::validator::mixnet::query::MixnetQueryCommands::Mixnodes(args) => { nym_cli_commands::validator::mixnet::query::query_all_mixnodes::query( args, - &create_query_client_with_validator_api(network_details)?, + &create_query_client_with_nym_api(network_details)?, ) .await } nym_cli_commands::validator::mixnet::query::MixnetQueryCommands::Gateways(args) => { nym_cli_commands::validator::mixnet::query::query_all_gateways::query( args, - &create_query_client_with_validator_api(network_details)?, + &create_query_client_with_nym_api(network_details)?, ) .await } diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index e6c3fa879e..c327083a04 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -13,7 +13,7 @@ walkdir = "2" validator-client = { path = "../../common/client-libs/validator-client", features = [ "nymd-client", "generate-ts" ] } -validator-api-requests = { path = "../../validator-api/validator-api-requests", features = ["generate-ts"] } +nym-api-requests = { path = "../../nym-api/nym-api-requests", features = ["generate-ts"] } mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = [ "generate-ts" ] } vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", features = [ "generate-ts" ] } nym-types = { path = "../../common/types", features = [ "generate-ts" ] } diff --git a/tools/ts-rs-cli/src/main.rs b/tools/ts-rs-cli/src/main.rs index 42217d4d8b..762dd4b03f 100644 --- a/tools/ts-rs-cli/src/main.rs +++ b/tools/ts-rs-cli/src/main.rs @@ -3,6 +3,11 @@ use mixnet_contract_common::{ Interval as ContractInterval, IntervalRewardParams, IntervalRewardingParamsUpdate, MixNode, MixNodeConfigUpdate, RewardedSetNodeStatus, RewardingParams, UnbondedMixnode, }; +use nym_api_requests::models::{ + GatewayCoreStatusResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse, + MixnodeStatus, MixnodeStatusResponse, RewardEstimationResponse, SelectionChance, + StakeSaturationResponse, +}; use nym_types::account::{Account, AccountEntry, AccountWithMnemonic, Balance}; use nym_types::currency::{CurrencyDenom, DecCoin}; use nym_types::delegation::{ @@ -27,11 +32,6 @@ use nym_wallet_types::network::Network; use nym_wallet_types::network_config::{Validator, ValidatorUrl, ValidatorUrls}; use std::path::Path; use ts_rs::TS; -use validator_api_requests::models::{ - GatewayCoreStatusResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse, - MixnodeStatus, MixnodeStatusResponse, RewardEstimationResponse, SelectionChance, - StakeSaturationResponse, -}; use vesting_contract_common::Period; use walkdir::WalkDir; @@ -112,7 +112,7 @@ fn main() { do_export!(VestingPeriod); do_export!(WrappedDelegationEvent); - // validator-api-requests + // nym-api-requests do_export!(MixnodeCoreStatusResponse); do_export!(GatewayCoreStatusResponse); do_export!(InclusionProbabilityResponse); diff --git a/ts-packages/mui-theme/src/theme/theme.ts b/ts-packages/mui-theme/src/theme/theme.ts index a12e706e9e..bb4dcae533 100644 --- a/ts-packages/mui-theme/src/theme/theme.ts +++ b/ts-packages/mui-theme/src/theme/theme.ts @@ -84,6 +84,7 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => { ].join(','), fontSize: 14, fontWeightRegular: 500, + fontWeightMedium: 600, button: { textTransform: 'none', fontWeight: '600', diff --git a/ts-packages/types/src/types/rust/Account.ts b/ts-packages/types/src/types/rust/Account.ts index ea0586828f..da0afb666e 100644 --- a/ts-packages/types/src/types/rust/Account.ts +++ b/ts-packages/types/src/types/rust/Account.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { CurrencyDenom } from './CurrencyDenom'; export interface Account { diff --git a/ts-packages/types/src/types/rust/AccountEntry.ts b/ts-packages/types/src/types/rust/AccountEntry.ts index f6ad1b53a9..472a9befcd 100644 --- a/ts-packages/types/src/types/rust/AccountEntry.ts +++ b/ts-packages/types/src/types/rust/AccountEntry.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface AccountEntry { id: string; address: string; diff --git a/ts-packages/types/src/types/rust/AccountWithMnemonic.ts b/ts-packages/types/src/types/rust/AccountWithMnemonic.ts index 6b3757bdab..edbf9b9de6 100644 --- a/ts-packages/types/src/types/rust/AccountWithMnemonic.ts +++ b/ts-packages/types/src/types/rust/AccountWithMnemonic.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Account } from './Account'; export interface AccountWithMnemonic { diff --git a/ts-packages/types/src/types/rust/Balance.ts b/ts-packages/types/src/types/rust/Balance.ts index ba6be26651..fe11917327 100644 --- a/ts-packages/types/src/types/rust/Balance.ts +++ b/ts-packages/types/src/types/rust/Balance.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface Balance { diff --git a/ts-packages/types/src/types/rust/Coin.ts b/ts-packages/types/src/types/rust/Coin.ts index dda00291d7..493c2e6b41 100644 --- a/ts-packages/types/src/types/rust/Coin.ts +++ b/ts-packages/types/src/types/rust/Coin.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface Coin { denom: string; amount: bigint; diff --git a/ts-packages/types/src/types/rust/CosmosFee.ts b/ts-packages/types/src/types/rust/CosmosFee.ts index 54c0f689bc..d77c4d3dd8 100644 --- a/ts-packages/types/src/types/rust/CosmosFee.ts +++ b/ts-packages/types/src/types/rust/CosmosFee.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Coin } from './Coin'; export interface CosmosFee { diff --git a/ts-packages/types/src/types/rust/CurrencyDenom.ts b/ts-packages/types/src/types/rust/CurrencyDenom.ts index 04760b4fd7..3e7696c28d 100644 --- a/ts-packages/types/src/types/rust/CurrencyDenom.ts +++ b/ts-packages/types/src/types/rust/CurrencyDenom.ts @@ -1 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export type CurrencyDenom = 'unknown' | 'nym' | 'nymt' | 'nyx' | 'nyxt'; diff --git a/ts-packages/types/src/types/rust/DecCoin.ts b/ts-packages/types/src/types/rust/DecCoin.ts index d2b1b4dd69..9411bf358b 100644 --- a/ts-packages/types/src/types/rust/DecCoin.ts +++ b/ts-packages/types/src/types/rust/DecCoin.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { CurrencyDenom } from './CurrencyDenom'; export type DecCoin = { denom: CurrencyDenom; amount: string }; diff --git a/ts-packages/types/src/types/rust/Delegation.ts b/ts-packages/types/src/types/rust/Delegation.ts index 18d6df201f..58de13e6cc 100644 --- a/ts-packages/types/src/types/rust/Delegation.ts +++ b/ts-packages/types/src/types/rust/Delegation.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface Delegation { diff --git a/ts-packages/types/src/types/rust/DelegationEvent.ts b/ts-packages/types/src/types/rust/DelegationEvent.ts index d9e2c0a21d..30ff21943f 100644 --- a/ts-packages/types/src/types/rust/DelegationEvent.ts +++ b/ts-packages/types/src/types/rust/DelegationEvent.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { DelegationEventKind } from './DelegationEventKind'; diff --git a/ts-packages/types/src/types/rust/DelegationEventKind.ts b/ts-packages/types/src/types/rust/DelegationEventKind.ts index a1db5fac12..a242220102 100644 --- a/ts-packages/types/src/types/rust/DelegationEventKind.ts +++ b/ts-packages/types/src/types/rust/DelegationEventKind.ts @@ -1 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export type DelegationEventKind = 'Delegate' | 'Undelegate'; diff --git a/ts-packages/types/src/types/rust/DelegationResult.ts b/ts-packages/types/src/types/rust/DelegationResult.ts index f6d29d9435..d8f8a77ce3 100644 --- a/ts-packages/types/src/types/rust/DelegationResult.ts +++ b/ts-packages/types/src/types/rust/DelegationResult.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface DelegationResult { diff --git a/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts b/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts index 0cb9346c59..21e85d513b 100644 --- a/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts +++ b/ts-packages/types/src/types/rust/DelegationSummaryResponse.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { DelegationWithEverything } from './DelegationWithEverything'; diff --git a/ts-packages/types/src/types/rust/DelegationWithEverything.ts b/ts-packages/types/src/types/rust/DelegationWithEverything.ts index 0d3423a133..ecf949c5ae 100644 --- a/ts-packages/types/src/types/rust/DelegationWithEverything.ts +++ b/ts-packages/types/src/types/rust/DelegationWithEverything.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { DelegationEvent } from './DelegationEvent'; import type { MixNodeCostParams } from './MixNodeCostParams'; diff --git a/ts-packages/types/src/types/rust/Fee.ts b/ts-packages/types/src/types/rust/Fee.ts index 5a7a0af56e..1e3f747e92 100644 --- a/ts-packages/types/src/types/rust/Fee.ts +++ b/ts-packages/types/src/types/rust/Fee.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { CosmosFee } from './CosmosFee'; export type Fee = { Manual: CosmosFee } | { Auto: number | null }; diff --git a/ts-packages/types/src/types/rust/FeeDetails.ts b/ts-packages/types/src/types/rust/FeeDetails.ts index 60bcd4cae5..7db283dbc1 100644 --- a/ts-packages/types/src/types/rust/FeeDetails.ts +++ b/ts-packages/types/src/types/rust/FeeDetails.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { Fee } from './Fee'; diff --git a/ts-packages/types/src/types/rust/Gas.ts b/ts-packages/types/src/types/rust/Gas.ts index e207dcb1e1..2b93e47509 100644 --- a/ts-packages/types/src/types/rust/Gas.ts +++ b/ts-packages/types/src/types/rust/Gas.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface Gas { gas_units: bigint; } diff --git a/ts-packages/types/src/types/rust/GasInfo.ts b/ts-packages/types/src/types/rust/GasInfo.ts index fd5b21e582..3e34323018 100644 --- a/ts-packages/types/src/types/rust/GasInfo.ts +++ b/ts-packages/types/src/types/rust/GasInfo.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Gas } from './Gas'; export interface GasInfo { diff --git a/ts-packages/types/src/types/rust/Gateway.ts b/ts-packages/types/src/types/rust/Gateway.ts index 1dbba0e5ed..4c449b578d 100644 --- a/ts-packages/types/src/types/rust/Gateway.ts +++ b/ts-packages/types/src/types/rust/Gateway.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface Gateway { host: string; mix_port: number; diff --git a/ts-packages/types/src/types/rust/GatewayBond.ts b/ts-packages/types/src/types/rust/GatewayBond.ts index 277994efac..405c1cab92 100644 --- a/ts-packages/types/src/types/rust/GatewayBond.ts +++ b/ts-packages/types/src/types/rust/GatewayBond.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { Gateway } from './Gateway'; diff --git a/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts b/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts index 56d81bde41..3f80b794a0 100644 --- a/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts +++ b/ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { SelectionChance } from './SelectionChance'; export interface InclusionProbabilityResponse { diff --git a/ts-packages/types/src/types/rust/Interval.ts b/ts-packages/types/src/types/rust/Interval.ts index b47687ae22..7a7fa7bb4d 100644 --- a/ts-packages/types/src/types/rust/Interval.ts +++ b/ts-packages/types/src/types/rust/Interval.ts @@ -3,6 +3,6 @@ export interface Interval { epochs_in_interval: number; current_epoch_start: string; current_epoch_id: number; - epoch_length: string; + epoch_length: { secs: number; nanos: number }; total_elapsed_epochs: number; } diff --git a/ts-packages/types/src/types/rust/MixNodeBond.ts b/ts-packages/types/src/types/rust/MixNodeBond.ts index 37059b9b61..173ce9599d 100644 --- a/ts-packages/types/src/types/rust/MixNodeBond.ts +++ b/ts-packages/types/src/types/rust/MixNodeBond.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { MixNode } from './Mixnode'; diff --git a/ts-packages/types/src/types/rust/Mixnode.ts b/ts-packages/types/src/types/rust/Mixnode.ts index 0deaa5b919..8dddce4eb2 100644 --- a/ts-packages/types/src/types/rust/Mixnode.ts +++ b/ts-packages/types/src/types/rust/Mixnode.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface MixNode { host: string; mix_port: number; diff --git a/ts-packages/types/src/types/rust/MixnodeStatus.ts b/ts-packages/types/src/types/rust/MixnodeStatus.ts index da89c7e8a7..6421988dce 100644 --- a/ts-packages/types/src/types/rust/MixnodeStatus.ts +++ b/ts-packages/types/src/types/rust/MixnodeStatus.ts @@ -1 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export type MixnodeStatus = 'active' | 'standby' | 'inactive' | 'not_found'; diff --git a/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts b/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts index 2225b26df3..2dd6bb6f54 100644 --- a/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts +++ b/ts-packages/types/src/types/rust/MixnodeStatusResponse.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { MixnodeStatus } from './MixnodeStatus'; export interface MixnodeStatusResponse { diff --git a/ts-packages/types/src/types/rust/OriginalVestingResponse.ts b/ts-packages/types/src/types/rust/OriginalVestingResponse.ts index 8d781d5ad0..ea10cd24af 100644 --- a/ts-packages/types/src/types/rust/OriginalVestingResponse.ts +++ b/ts-packages/types/src/types/rust/OriginalVestingResponse.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface OriginalVestingResponse { diff --git a/ts-packages/types/src/types/rust/Period.ts b/ts-packages/types/src/types/rust/Period.ts index 05d9890d5f..76a8876b31 100644 --- a/ts-packages/types/src/types/rust/Period.ts +++ b/ts-packages/types/src/types/rust/Period.ts @@ -1 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export type Period = 'Before' | { In: number } | 'After'; diff --git a/ts-packages/types/src/types/rust/PledgeData.ts b/ts-packages/types/src/types/rust/PledgeData.ts index 9fb19f9aaf..76adc23018 100644 --- a/ts-packages/types/src/types/rust/PledgeData.ts +++ b/ts-packages/types/src/types/rust/PledgeData.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface PledgeData { diff --git a/ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts b/ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts index 8ddd13d047..5a8da8dbea 100644 --- a/ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts +++ b/ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts @@ -1 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export type RewardedSetNodeStatus = 'Active' | 'Standby'; diff --git a/ts-packages/types/src/types/rust/RpcTransactionResponse.ts b/ts-packages/types/src/types/rust/RpcTransactionResponse.ts index 2463e3dd20..2f9c3bdc8a 100644 --- a/ts-packages/types/src/types/rust/RpcTransactionResponse.ts +++ b/ts-packages/types/src/types/rust/RpcTransactionResponse.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { Gas } from './Gas'; diff --git a/ts-packages/types/src/types/rust/SendTxResult.ts b/ts-packages/types/src/types/rust/SendTxResult.ts index b44de3324d..02fb0b5b0c 100644 --- a/ts-packages/types/src/types/rust/SendTxResult.ts +++ b/ts-packages/types/src/types/rust/SendTxResult.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { Gas } from './Gas'; import type { TransactionDetails } from './TransactionDetails'; diff --git a/ts-packages/types/src/types/rust/StakeSaturationResponse.ts b/ts-packages/types/src/types/rust/StakeSaturationResponse.ts index 038d261c46..6be2ae0e0e 100644 --- a/ts-packages/types/src/types/rust/StakeSaturationResponse.ts +++ b/ts-packages/types/src/types/rust/StakeSaturationResponse.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface StakeSaturationResponse { saturation: string; uncapped_saturation: string; diff --git a/ts-packages/types/src/types/rust/TransactionDetails.ts b/ts-packages/types/src/types/rust/TransactionDetails.ts index 8de1cf9ec3..043db71473 100644 --- a/ts-packages/types/src/types/rust/TransactionDetails.ts +++ b/ts-packages/types/src/types/rust/TransactionDetails.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; export interface TransactionDetails { diff --git a/ts-packages/types/src/types/rust/TransactionExecuteResult.ts b/ts-packages/types/src/types/rust/TransactionExecuteResult.ts index e6d93e8448..ce06484dd1 100644 --- a/ts-packages/types/src/types/rust/TransactionExecuteResult.ts +++ b/ts-packages/types/src/types/rust/TransactionExecuteResult.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { GasInfo } from './GasInfo'; diff --git a/ts-packages/types/src/types/rust/VestingAccountInfo.ts b/ts-packages/types/src/types/rust/VestingAccountInfo.ts index d317d045d1..538e941edb 100644 --- a/ts-packages/types/src/types/rust/VestingAccountInfo.ts +++ b/ts-packages/types/src/types/rust/VestingAccountInfo.ts @@ -1,3 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DecCoin } from './DecCoin'; import type { VestingPeriod } from './VestingPeriod'; diff --git a/ts-packages/types/src/types/rust/VestingPeriod.ts b/ts-packages/types/src/types/rust/VestingPeriod.ts index de1089005f..dae6901c10 100644 --- a/ts-packages/types/src/types/rust/VestingPeriod.ts +++ b/ts-packages/types/src/types/rust/VestingPeriod.ts @@ -1,3 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + export interface VestingPeriod { start_time: bigint; period_seconds: bigint; diff --git a/ts-packages/types/src/utils/decimal/index.ts b/ts-packages/types/src/utils/decimal/index.ts index 2f58196372..62807f25d6 100644 --- a/ts-packages/types/src/utils/decimal/index.ts +++ b/ts-packages/types/src/utils/decimal/index.ts @@ -10,5 +10,8 @@ export const stringToDecimal = (raw: string): Decimal => Decimal.fromUserInput(r export const decimalToPercentage = (raw: string) => Math.round(Decimal.fromUserInput(raw, 18).toFloatApproximation() * 100).toString(); +export const percentToDecimal = (raw: string) => + (Decimal.fromUserInput(raw, 18).toFloatApproximation() / 100).toString(); + export const decimalToFloatApproximation = (raw: string): number => Decimal.fromUserInput(raw, 18).toFloatApproximation();