Compare commits

..

99 Commits

Author SHA1 Message Date
tommy 58ef536507 format 2022-10-03 15:28:48 +02:00
tommy af1c2cfa2b push up for base points to update all tests 2022-10-03 15:24:15 +02:00
Pierre Dommerc c582d6dcba config(wallet): use new mixnet contract address for qa (#1656) 2022-10-03 13:38:56 +02:00
Jędrzej Stuczyński 49b3a5aa90 Made config.toml values in validator-api take precedence over mainnet defaults (#1645)
* Made config.toml values in validator-api take precedence over mainnet defaults

* Updated mixnode and gateway configs with similar priority adjustments

* Changelog
2022-09-23 16:59:42 +01:00
Jędrzej Stuczyński 879ce3f2d5 Feature/field renaming (#1654)
* Replaced serde renames to aliases

Ideally I would have removed all serde macros, but then it would have broken existing QA deployments - perhaps we should do it later

* Renamed 'node_id' in Delegation to 'mix_id'

* Further renamings of 'node_id' to 'mix_id' in various places
2022-09-23 15:01:39 +01:00
Pierre Dommerc 996f0bf732 feature(explorer-api): rewrite geoip2 location service (#1651) 2022-09-23 14:53:56 +02:00
Jędrzej Stuczyński b289a3570a Prefixing all mixnet contract events with a v2_ prefix (#1652) 2022-09-23 12:33:29 +01:00
Gala 1b689edb43 Merge pull request #1653 from nymtech/ne-stak-sat-filter
NE: fix upper saturation value used on filters
2022-09-23 11:37:47 +02:00
Jon Häggblad 95c5b70eb7 Remove the old deprecated parts of network-defaults (#1646)
* network-defaults: remove all the old crap and simplity

* validator-client: remove commented out code
2022-09-23 11:32:23 +02:00
Jędrzej Stuczyński a5b4504b0a Emitting information about prior delegation data before rewarding (#1650)
* Emitting information about prior delegation data before rewarding

* Cargo fmt
2022-09-23 09:32:37 +01:00
Gala 995a61b7ea fix upper saturation value use on filters 2022-09-23 10:20:51 +02:00
Drazen Urch 0adf4df094 Fig and shell completions (#1638)
* Fig and shell completions lib

* Complete all the things
2022-09-22 17:26:37 +02:00
Gala 8c877d64d6 Merge pull request #1649 from nymtech/fix-validator-url-access
NE: Fix validators url
2022-09-22 15:32:34 +02:00
Gala 9ae1f046c4 avoid destructuring 2022-09-22 15:13:33 +02:00
Gala f401266d1b Merge pull request #1644 from nymtech/ne-transition-v1-v2
NE: Adding a workaround for v1 to v2 transition
2022-09-22 10:47:32 +02:00
Gala 1878b50752 Merge branch 'develop' into ne-transition-v1-v2 2022-09-22 10:34:16 +02:00
Gala 8bd7b69bf9 Merge pull request #1597 from nymtech/305-ui-fixes-inputs
Wallet: address Figma differences on modals, dialogs and inputs
2022-09-22 10:33:28 +02:00
Gala cf2ede1040 adding a temporaly solution for transition v1 to v2 2022-09-21 17:57:19 +02:00
Gala af1bf57f24 anothe conflict fix 2022-09-21 17:41:31 +02:00
Gala 0de6a0f1ca Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-21 17:41:07 +02:00
Gala 03974f9cb3 Merge pull request #1628 from nymtech/409-ne-filters-info
NE & Wallet: NE TableToolbar ui and Wallet new display when no delegations
2022-09-21 16:52:21 +02:00
Jędrzej Stuczyński d322f5e91b Removed the concept of exiting validator API after X consecutive failures (#1643) 2022-09-21 15:20:33 +01:00
Gala 0dee6d9db7 spacing between title and content on pages 2022-09-21 16:05:03 +02:00
Gala 05bd6d6a9a signup mnemonic change 2022-09-21 16:04:06 +02:00
Jędrzej Stuczyński c43dbf6f4d Feature/remove deprecated routes (#1642)
* Removed deprecated routes in validator-api

* Removed deprecated routes in explorer-api
2022-09-21 14:54:51 +01:00
Dave Hrycyszyn 848ace1e0b Using the pre-built package in the chat demo 2022-09-21 13:28:41 +01:00
Mark Sinclair f98d9d89bc GitHub Actions - add input to manual dispatch
Adds `RUST_FLAGS="--cfg tokio_unstable"` when the input is true to add the tokio console to the validator api
2022-09-21 12:24:09 +01:00
Jon Häggblad b9015c1321 Update to latest set of selection chance buckets (#1626)
* Update to latest set of selection chance buckets

* Fixup after rebase

* Lock file update

* storybook update

* update storybook

Co-authored-by: Gala <calero.vg@gmail.com>
2022-09-21 12:38:42 +02:00
Fouad 59b0fe2f94 change input placeholders to labels (#1575)
* change input placeholders to labels

* use back button on 'show mnemonic' modal
2022-09-21 11:28:30 +01:00
Gala bf98c1b369 Merge branch 'develop' into 409-ne-filters-info 2022-09-21 12:01:01 +02:00
Gala d7e3b2c6f2 update branch 2022-09-21 11:57:54 +02:00
Gala 7302b64be7 Merge pull request #1617 from nymtech/309-figma-diff-mnemonic
Wallet: Figma diff, change mnemonic textfield
2022-09-21 11:55:15 +02:00
Gala d5365a7602 Merge pull request #1524 from nymtech/317-forms-menus-titles-ui
317 forms menus titles UI
2022-09-21 11:54:53 +02:00
Jędrzej Stuczyński 524d563077 Added additional log statement to show config file save location 2022-09-21 10:12:53 +01:00
Jon Häggblad e44ddc419c clippy: fix warnings in beta toolchain (#1639) 2022-09-21 09:25:22 +02:00
Jon Häggblad be20e17ebb ci: add libudev-dev to nightly linux build 2022-09-21 08:52:41 +02:00
Dave Hrycyszyn 7b76beab76 Switch from deprecated substr() to slice() 2022-09-20 16:00:18 +01:00
Bogdan-Ștefan Neacşu 9ed013b418 Add library to communicate with ledger (#1640)
* Add library to communicate with ledger

* Update changelog

* Install libudev-dev on linux
2022-09-20 17:57:14 +03:00
Dave Hrycyszyn 33ae43b86d Removing commented code 2022-09-20 15:44:56 +01:00
Dave Hrycyszyn 2c1ad1388d removing surb noise display 2022-09-20 15:35:53 +01:00
Jędrzej Stuczyński 136666d759 Feature/rewarding revamp (#1472)
* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* commented out all code

* Bunch of work in progres, but working simulator

* Removing redundant fields + increased precision to 9decimal places

* deserialization of Percent with value validation

* wip

* Further moving things around + mixnode bonding

* Mixnode unbonding

* Starting restoration on contract state

* Revamping interval

* More work on epoch/interval

* progress on mixnode rewarding

* Moved MixNodeRewarding to rewards storage

* wip on delegations

* Removed concept of periods and historical records and moved cummulative reward ratio directly to delegation

* more wip delegation

* Full delegation flow

* mixnode config updates

* Mixnode cost function updates

* Work on moving mixnode unbonding to post-epoch actions

* Unbonding

* Processing undelegation

* changing cost params

* Uncommented existing gateways features

without much changes so far, however, things like unbonding should probably also go to epoch queue

* ExecuteMsg cleanup

* unit tests for withdrawing rewards against known values

* Transactions for withdrawing rewards

* Transactions for updating various parameters

* First round of post-tx cleanup

* Moved all storage keys to constants.rs

* Using correct initial gateway pledge amount

* Renamed sybil_resistance_percent to sybil_resistance with percent being implicit from the typ

* Starting with contract queries

* Keeping minimal details of unbonded mixnoces

* Checking for owner address rather than rewarding validator when updating rewarding params

* Mixnode-related queries

* Gateway-related queries

* Query for paged unbonded mixnodes

* Delegations queries

* Query for current interval details

* Removed 'fixed' dependency from the mixnet common

* wip on implementing rewards-related queries

* Pending rewards queries

* Query for node stake saturation

* Queries for currently pending events

* Rewarded set query

* Moved ContractState to common types

since it's being returned as a result of one of the queries on the mixnet contract and thus it needs to be accessible outside the contract itself

* Cleaend up storage initialisation

* started restoring unit tests

* Removed attached 1ucoin for cross-contract execute msgs

* wip

* query for rewarding details of a mix node

* Changes for mixnodes and gateways

* Furher progress on v2 changelog(-ish) description

* wip

* first version of the description

* mixnode bonding queries tests and fixes

* ibid for storage

* MixnodeEventType enum + created events for missing mixnode txs

* tests for adding new mixnode

* Additional mixnode-related tests + bug fixes

* Display for Percent

* Bunch of tests for try_reward_mixnode

* More tests and fixes

* ibid

* tests for updating rewarding params + important bug fix

* Started removing unused imports

* rewarding queries tests + undelegation bugfix

* A lot of todo()-ing and commenting out unimplemented code

* implements https://github.com/nymtech/team-core/issues/113

* Delegation tests + fixes

* Emiting events by top level interval txs + incorporating limit

* question

* Missing events emissions

* removed some code duplication

* wip

* pending delegation tests

* Vesting contract update

* More tests (and fixes) for pending events txs

* Restored gateway tx tests

* Another cleanup iteration

* removed redundant comment

* Unit tests, fixes and simplifcations for interval-related txs

* Unit tests for helper functions

* Interval queries unit tests

* Test for correct contract initialisation

* Another round of cleanup

* Work on mixnet_query_client trait

* mixnet_signing_client trait

* Removed redundant methods

* Slowly restoring validator client functionality

* Added deprecated query for mix details by identity

* wip restoration of validator-api

* Work on deprecating validator API routes

* Further validator-api routes

* Restored rest of status api routes

* Resolved all todos in ValidatorApiStorage

There's still bunch left in StorageManager though

* Changed NodeId from u64 to u32

* Updating sql code

* Network monitor internals

* Changed behaviour of full_epoch_id and updated epoch operations

* Fixed sql queries

* [most likely] finished updating rest of the validator API

* Post rebasing fixes

* Feature/rewarding revamp explorer api changes (#1511)

* Changed cache to allow for non-string keys

* Helper method for best-effort conversion of pubkey to nodeid

* Updated validator-api client routes

* Updated routes to use mix-id indexing

* Introduction of deprecated routes callable by identity key

* Fixed mixnode compatibility by changing read node details fields (#1512)

* Fixed bond to topology conversion for client compatibility (#1513)

* Updated 'verify_gateway_owner' to use correct nymd_client method for obtaining gateway details (#1515)

* Updated constructor for ValidatorCacheInner

* Fixed wasm client topology construction

* Run cargo fmt on the entire codebase

* Feature/rewarding revamp wallet backend changes (#1529)

* Updated mixnode-related ts types

* Updated nym-wallet-types

* Updated 'get_contract_settings' and commented out code of other tauri commands

* 'update_contract_settings'

* 'bond_gateway'

* unbond_gateway'

* Utility commands for the transition period

* 'bond_mixnode'

* 'unbond_mixnode'

* Ability to update mixnode cost paramaters

* Mixnode config update

* Updated mixnode_bond_details

It also returns a different underlying type now

* Updated 'gateway_bond_details'

* Obtaining pending operator rewards

* Improved way of obtaining number of mixnode delegators

* simplified error handling in 'fetch_mix_node_description'

* mixnode and gateway ownership queries

* updated get_number_of_mixnode_delegators to use mix_id since we have the conversion utils helper

* mixnode delegation

* undelegating

* Obtaining pending delegator rewards

* Command for obtaining current interval details

* Queries to handle paging for pending events

* Additional level of indirection to pending events to incorporate event id into response

* Wallet compatible pending event types

* Commands fo obtaining pending events

* Re-implemented pending delegation events

* Further work on delegation

* Removed unused imports

* Commands for withdrawing rewards

* Admin-related simulations

* mixnet-related simulation commands

* Validator-api related routes

* Bond-related vesting operations

* Vesting simulations

* Vesting handler for UpdateMixnodeCostParams

* Vesting reward claiming

* Vesting queries

* claim_locked_and_unlocked_delegator_reward

* The massive delegation query

* cleanup

* updated typescript requests

* sorted the new type exports in ts-rs-cli

* Regenerated typescript types

* temporarily ignoring unreachable code in vesting migration

* Updated missed test fixture

* Fixed missing coconut-specific import

* cargo fmt

* Exporting reward-related types

* utility to convert stringified decimal to cosmjs Decimal

* deriving Eq alongside PartialEq

* wip - typescript fixes

* using default operating cost when bonding mixnode

* Using default operating cost when updating mixnode cost params

* most delegation fixes

* Wrapping delegation with node identity

* Added MultiIndex on owner and identity key to unbonded mixnodes

* Support for queries for unbonded nodes by owner or by identity key

* Cargo fmt + ts types update

* feature locking unused imports

* fix(nym-wallet): typing and error (#1548)

* post-rebase fixes

* Changed storage key for new delegations map in vesting contract

* fix(wallet): typing issues (#1562)

* fix(wallet): error UI feedback (#1565)

* clean(wallet): remove useless files (with flamethrowers 🔥) (#1567)

* Changed default_mixnode_cost_params to allow accepting f32 instead

* Revert "Changed default_mixnode_cost_params to allow accepting f32 instead"

This reverts commit fb62a0014f.

* Fixed APY calculation for 0 pledge value

* Don't send rewarding transactions for empty rewarded set

* Fixed mixnode rewarding in validator API

* fix(nym-wallet): profit margin (#1574)

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Removed todo!() from vesting contract migration since its going to be dealt with differently

* fix(nym-wallet): stake saturation and delegations (#1578)

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* fix(nym-wallet): get rid of delegation history

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* Correctly assigning Delegate event type to PendingEpochEventData::Delegate

* Replaced 'history' in with 'pending_events' in DelegationWithEverything

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): stake saturation percentage

* fix(nym-wallet): get rid of delegation history

* Updated typescript side of things

* fix(nym-wallet): welcome back pending events and delegation menu

* fix(nym-wallet): fix clippy

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>

* Updated vesting contract migration to update the mixnet contract address

* feat(wallet): add confirmation/warning modal for unbonding

* Post rebasing fixes

* Removed deprecation on GetMixnodeDetailsByIdentity

* Fixed nym-cli

* Removed needless borrow

* Updated colorMap and textMap

Co-authored-by: durch <durch@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: Pierre Dommerc <dommerc.pierre@gmail.com>
2022-09-20 14:44:57 +01:00
Gala 4648967e93 fix build and refactor from CR 2022-09-20 12:43:51 +02:00
Gala 824e980647 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-20 11:49:33 +02:00
Jon Häggblad ecbf5296a5 validator-api: guard against funny numbers in apy calc (#1636) 2022-09-19 14:37:09 +02:00
Jon Häggblad f3454409f8 Upgrade nym-connect to tauri 1.1.1 (#1635) 2022-09-19 11:34:33 +02:00
Gala 7d2e90b69f Merge pull request #1625 from nymtech/407-ne-filters-granularity
NE: 407 filters granularity
2022-09-19 11:28:26 +02:00
Gala bb2732bcc6 Merge pull request #1619 from nymtech/413-ne-content
NE: Display raw bond instead of self %
2022-09-19 11:16:16 +02:00
Gala d196993993 fix build 2022-09-19 11:08:16 +02:00
Gala b4fe8af890 refactor from CR 2022-09-19 11:04:26 +02:00
Gala 1c8ab2395d Merge branch 'develop' into 409-ne-filters-info 2022-09-19 10:24:18 +02:00
Jon Häggblad 065fe812ae Rename crate to nym-wallet-recovery-cli (#1633)
* Rename to nym-wallet-recovery-cli

* Rename crate name too

* Rename workspace member too

* Cargo.lock
2022-09-19 10:22:37 +02:00
Gala 02e75ea5cd display different content when user doesn't have delegations 2022-09-16 12:24:32 +02:00
Jon Häggblad 2aa18fb77c nym-wallet: add log window (#1618)
* Add a second entry point to the webpack config for the logging window

* tauri operations to show a log window

* LogViewer react component

* Upgrade tauri and use default tauri app menu for MacOS and add `Help` menu with `Show log` entry to show the logging window

* wip

* Proof of concept

* Fix format inside debug with ferm

* Put new menubar and log behind env variable flag

* Remove unused deps

* rustfmt

* Add changelog note

* Fix up imports

* Remove old code

* Improve log viewer

* Remove old code

* Add color to output, even if tauri hides it

* Remove redundant level from tauri log msg

* Since menu bar visible by default, change feature flag name

* Fix up webpack config so correct chunks get injected into entry points and remove inline CSS causing CSP issue

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-09-16 09:38:52 +01:00
Jon Häggblad 8a0e7fb9d6 validator-api: add missing clap argument (#1629) 2022-09-15 22:31:31 +02:00
Dave Hrycyszyn 13c2ca4a78 Noted a correct npm publish command 2022-09-15 16:28:56 +01:00
Dave Hrycyszyn 6af84535fa Formatting 2022-09-15 16:27:44 +01:00
Mark Sinclair c8699cbe8d wasm-client: fix up dependencies and feature flags so that wasm-pack build works (#1585)
* wasm-client: fix up dependencies and feature flags so that wasm-pack build works

* Update CHANGELOG
2022-09-15 16:11:49 +01:00
Drazen Urch 974163da97 Reduce iterations when delegator last_claimed_height is 0 (#1609)
* Reduce iterations when delegator last_claimed_height is 0

* Fix typo
2022-09-15 17:10:35 +02:00
Raphaël Walther 7290e479db Notification test 2022-09-15 17:01:49 +02:00
Raphaël Walther c7b728318c Notification test 2022-09-15 16:57:25 +02:00
Dave Hrycyszyn 5c6d31bcb5 README fix 2022-09-15 15:48:58 +01:00
Dave Hrycyszyn 3823292ba8 Bumping version number to 1.0.0 2022-09-15 15:48:42 +01:00
Bogdan-Ștefan Neacşu 3eeda4a421 Apply fix for socket_state too (#1627) 2022-09-15 17:35:41 +03:00
Gala ebb3f6eebb fixing the build 2022-09-15 16:06:14 +02:00
Gala 2c15d22e1e Merge branch 'develop' into 409-ne-filters-info 2022-09-15 15:55:58 +02:00
Gala f1dca2c9a8 styling the mobile view 2022-09-15 14:50:33 +02:00
Gala d039c25b55 create a custom label for stake sat 2022-09-15 14:27:52 +02:00
Drazen Urch 16ef1c547b Remove eth feature, and BBBC related code (#1612)
* Remove eth feature, and BBBC related code

* Burn some more, especially clients
2022-09-15 13:58:44 +02:00
Dave Hrycyszyn e804b014a8 Cargo lock excluding webassembly client 2022-09-15 12:25:36 +01:00
Dave Hrycyszyn e406a05694 Re-excluding clients/webassembly which breaks cargo build --all 2022-09-15 12:24:04 +01:00
Gala 057b3456a7 more granulatity on filters 2022-09-15 12:49:27 +02:00
Dave Hrycyszyn 5ac124e159 Adding the webassembly client to the main build
This gives type hinting in editors. I can no longer remember why we
didn't set it up like this a long time ago, but it builds and tests
fine. Feel free to revert if it causes any problems.
2022-09-15 11:33:16 +01:00
Gala dee2c50b50 ne toolbar changes 2022-09-14 16:04:39 +02:00
Gala a394c9b59a PR requested changes 2022-09-14 14:48:30 +02:00
Gala 17768bab0b Merge branch 'develop' into 317-forms-menus-titles-ui 2022-09-14 14:11:10 +02:00
Gala 7816b4c839 some refactor 2022-09-14 13:58:04 +02:00
Gala e7ed48e55e trying to fix the build 2022-09-14 13:35:22 +02:00
Gala 63855f6ca4 display pledge_amount instead of self on mix nodes page 2022-09-14 13:13:08 +02:00
Gala 27d566dd47 some styling 2022-09-14 11:46:06 +02:00
Gala bfa0144594 text change 2022-09-14 11:38:10 +02:00
Gala 8924f9642f wip 2022-09-14 11:35:22 +02:00
Gala 58541defad Merge branch 'develop' into 309-figma-diff-mnemonic 2022-09-14 11:29:55 +02:00
Gala c513014913 taking advantage to deal with some other differences 2022-09-14 11:27:41 +02:00
Gala 5985ba5182 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-14 10:08:17 +02:00
Gala 1af1370f23 display raw bond instead of self % 2022-09-13 17:00:37 +02:00
Gala ade15d3c60 change mnemonic textfield 2022-09-13 14:53:04 +02:00
Gala 1ab6bce821 Merge branch 'develop' into 305-ui-fixes-inputs 2022-09-07 11:27:57 +02:00
Gala 70624e9062 modals and dialogs border in dark mode 2022-09-06 16:40:50 +02:00
Gala 2407285121 fixing paddings on modals 2022-09-06 14:33:41 +02:00
Gala db3171ea09 ModalListItem fix font size 2022-09-06 13:58:54 +02:00
Gala 2aaaa0deb7 globally no colon at the end of ModalListItem 2022-09-06 13:55:20 +02:00
Gala d410277d14 change all placeholders on textfields into labels 2022-09-06 13:41:35 +02:00
Gala 6de7d060e3 keep same margin on between pages 2022-08-30 17:00:48 +02:00
Gala 66b5eb13b0 fixing nymcard title size and margin in delegations page 2022-08-30 14:32:15 +02:00
Gala 4b37d4f050 Merge branch 'develop' into 317-forms-menus-titles-ui 2022-08-30 12:50:11 +02:00
Gala 37d501f16d fix delegate more focus state 2022-08-11 16:20:05 +02:00
Gala 1e76169178 fixing spacing in modals 2022-08-11 14:57:37 +02:00
Gala 7406eeff14 ui top bar 2022-08-11 14:11:57 +02:00
Gala bdabe31fc9 side navigation 2022-08-11 14:03:02 +02:00
218 changed files with 5085 additions and 3836 deletions
+11
View File
@@ -2,6 +2,12 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -25,6 +31,11 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
+5
View File
@@ -11,6 +11,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
@@ -23,6 +27,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+40 -1
View File
@@ -681,6 +681,15 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "completions"
version = "0.1.0"
dependencies = [
"clap 3.2.8",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -919,6 +928,7 @@ dependencies = [
"cfg-if 0.1.10",
"clap 3.2.8",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -1659,6 +1669,7 @@ dependencies = [
"isocountry",
"itertools",
"log",
"maxminddb",
"mixnet-contract-common",
"network-defaults",
"okapi",
@@ -2632,6 +2643,15 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
[[package]]
name = "ipnetwork"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355"
dependencies = [
"serde",
]
[[package]]
name = "ipnetwork"
version = "0.20.0"
@@ -2905,6 +2925,18 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "maxminddb"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe2ba61113f9f7a9f0e87c519682d39c43a6f3f79c2cc42c3ba3dda83b1fa334"
dependencies = [
"ipnetwork 0.18.0",
"log",
"memchr",
"serde",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
@@ -3236,6 +3268,7 @@ dependencies = [
"clap 3.2.8",
"client-core",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -3275,6 +3308,7 @@ dependencies = [
"clap 3.2.8",
"coconut-interface",
"colored",
"completions",
"config",
"credentials",
"crypto",
@@ -3317,6 +3351,7 @@ dependencies = [
"bs58",
"clap 3.2.8",
"colored",
"completions",
"config",
"crypto",
"cupid",
@@ -3355,9 +3390,10 @@ version = "1.0.2"
dependencies = [
"async-trait",
"clap 3.2.8",
"completions",
"dirs",
"futures",
"ipnetwork",
"ipnetwork 0.20.0",
"log",
"network-defaults",
"nymsphinx",
@@ -3400,6 +3436,7 @@ dependencies = [
"clap 3.2.8",
"client-core",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -3520,7 +3557,9 @@ dependencies = [
"config",
"cosmrs",
"cosmwasm-std",
"hex-literal",
"mixnet-contract-common",
"network-defaults",
"nym-types",
"serde",
"serde_json",
+1
View File
@@ -63,6 +63,7 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
+2 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
@@ -19,6 +19,7 @@ tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "m
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
+7
View File
@@ -3,6 +3,7 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
@@ -28,6 +29,12 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
+6
View File
@@ -12,6 +12,8 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
@@ -52,10 +54,14 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+2 -1
View File
@@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
@@ -34,6 +34,7 @@ tokio-tungstenite = "0.14" # websocket
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
+12
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
pub(crate) mod init;
pub(crate) mod run;
@@ -62,6 +64,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+2 -1
View File
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -27,6 +27,7 @@ url = "2.2"
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
+12
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
@@ -63,6 +65,12 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -76,10 +84,14 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+2 -2
View File
@@ -34,6 +34,6 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "file:../pkg"
"@nymproject/nym-client-wasm": "1.0.0"
}
}
}
@@ -11,11 +11,9 @@ use validator_api_requests::coconut::{
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
DeprecatedRewardEstimationResponse, DeprecatedUptimeResponse, GatewayCoreStatusResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
use validator_api_requests::Deprecated;
#[cfg(feature = "nymd-client")]
use crate::nymd::traits::MixnetQueryClient;
@@ -706,69 +704,4 @@ impl ApiClient {
.verify_bandwidth_credential(request_body)
.await?)
}
// =================================================
// DEPRECATED ROUTES
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
pub async fn deprecated_get_mixnode_core_status_count_by_identity(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<Deprecated<MixnodeCoreStatusResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_core_status_count_by_identity(identity, since)
.await?)
}
pub async fn deprecated_get_mixnode_status_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<MixnodeStatusResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_status_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_reward_estimation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedRewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_reward_estimation_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_stake_saturation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<StakeSaturationResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_stake_saturation_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_avg_uptime_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedUptimeResponse, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_avg_uptime_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_avg_uptimes_by_identity(
&self,
) -> Result<Vec<DeprecatedUptimeResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_avg_uptimes_by_identity()
.await?)
}
}
@@ -1,12 +1,12 @@
use crate::nymd::error::NymdError;
use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use network_defaults::NymNetworkDetails;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
@@ -18,12 +18,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
@@ -46,16 +46,16 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
let config = ClientConfig::try_from_nym_network_details(&network.details())
let config = ClientConfig::try_from_nym_network_details(&network)
.expect("failed to create valid nymd client config");
if let Ok(mut client) = NymdClient::<QueryNymdClient>::connect(config, url.as_str()) {
@@ -81,7 +81,7 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<Network, Vec<(Url, bool)>> {
) -> HashMap<NymNetworkDetails, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
@@ -93,7 +93,7 @@ fn extract_and_collect_results_into_map(
}
async fn test_nymd_connection(
network: Network,
network: NymNetworkDetails,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
@@ -105,56 +105,47 @@ async fn test_nymd_connection(
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red());
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"Checking: nymd_url: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
log::debug!("Checking: nymd_url: {url}: {}", "success".green());
true
}
Err(e) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red());
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
async fn test_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &ApiClient,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
@@ -162,21 +153,15 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
log::debug!("Checking: api_url: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
Err(e) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
false
}
};
@@ -184,8 +169,8 @@ async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) ->
}
enum ClientForConnectionTest {
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
Nymd(NymNetworkDetails, Url, Box<NymdClient<QueryNymdClient>>),
Api(NymNetworkDetails, Url, ApiClient),
}
impl ClientForConnectionTest {
@@ -218,12 +203,12 @@ impl fmt::Display for UrlType {
#[derive(Debug)]
enum ConnectionResult {
Nymd(Network, Url, bool),
Api(Network, Url, bool),
Nymd(NymNetworkDetails, Url, bool),
Api(NymNetworkDetails, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&Network, &Url, &bool) {
fn result(&self) -> (&NymNetworkDetails, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
@@ -240,11 +225,8 @@ impl ConnectionResult {
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (network, url, result) = self.result();
let (_network, url, result) = self.result();
let url_type = self.url_type();
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
write!(f, "{url}: {url_type}: connection is successful: {result}")
}
}
@@ -149,7 +149,7 @@ impl NymdClient<SigningNymdClient> {
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U: Clone>(
config: Config,
network: config::defaults::all::Network,
network: config::defaults::NymNetworkDetails,
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
@@ -157,7 +157,7 @@ impl NymdClient<SigningNymdClient> {
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let denom = network.base_mix_denom();
let denom = network.chain_details.mix_denom.base;
let client_address = signer
.try_derive_accounts()?
.into_iter()
@@ -207,8 +207,9 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use network_defaults::NymNetworkDetails;
use super::*;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
@@ -218,7 +219,9 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = MAINNET.bech32_prefix();
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
@@ -12,12 +12,10 @@ use validator_api_requests::coconut::{
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
ComputeRewardEstParam, DeprecatedRewardEstimationResponse, DeprecatedUptimeResponse,
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use validator_api_requests::Deprecated;
pub mod error;
pub mod routes;
@@ -250,25 +248,6 @@ impl Client {
.await
}
pub async fn compute_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::COMPUTE_REWARD_ESTIMATION,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
@@ -398,151 +377,6 @@ impl Client {
)
.await
}
// =================================================
// DEPRECATED ROUTES
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
pub async fn deprecated_get_mixnode_core_status_count_by_identity(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<Deprecated<MixnodeCoreStatusResponse>, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
CORE_STATUS_COUNT,
],
NO_PARAMS,
)
.await
}
}
pub async fn deprecated_get_mixnode_status_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<MixnodeStatusResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::STATUS,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_reward_estimation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedRewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_stake_saturation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<StakeSaturationResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::STAKE_SATURATION,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_inclusion_probability_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<InclusionProbabilityResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_avg_uptime_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedUptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_avg_uptimes_by_identity(
&self,
) -> Result<Vec<DeprecatedUptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::DEPRECATED,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -6,7 +6,6 @@ use network_defaults::VALIDATOR_API_VERSION;
pub const API_VERSION: &str = VALIDATOR_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DEPRECATED: &str = "deprecated";
pub const DETAILED: &str = "detailed";
pub const ACTIVE: &str = "active";
@@ -30,7 +29,6 @@ pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
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";
@@ -63,7 +63,7 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.height as u32, client).await,
delegation.node_id.to_string(),
delegation.mix_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
delegation
.proxy
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "completions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "3.2", features = ["derive"] }
clap_complete = "3.2"
clap_complete_fig = "3.2"
+58
View File
@@ -0,0 +1,58 @@
use clap::builder::Command;
use clap::clap_derive::ArgEnum;
use clap::Args;
use clap_complete::generator::generate;
use clap_complete::Shell as ClapShell;
use std::io;
pub fn fig_generate(command: &mut Command, name: &str) {
clap_complete::generate(
clap_complete_fig::Fig,
command,
name,
&mut std::io::stdout(),
)
}
#[derive(ArgEnum, Copy, Clone)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
#[derive(Args, Copy, Clone)]
pub struct ArgShell {
#[clap(arg_enum, value_name = "SHELL")]
shell: Shell,
}
impl ArgShell {
pub fn generate(&self, command: &mut Command, name: &str) {
self.shell.generate(command, name)
}
}
impl Shell {
pub fn generate(&self, command: &mut Command, name: &str) {
match &self {
Self::Bash => {
generate(ClapShell::Bash, command, name, &mut io::stdout());
}
Self::Elvish => {
generate(ClapShell::Elvish, command, name, &mut io::stdout());
}
Self::Fish => {
generate(ClapShell::Fish, command, name, &mut io::stdout());
}
Self::PowerShell => {
generate(ClapShell::PowerShell, command, name, &mut io::stdout());
}
Self::Zsh => {
generate(ClapShell::Zsh, command, name, &mut io::stdout());
}
}
}
}
@@ -34,12 +34,13 @@ pub struct Delegation {
pub owner: Addr,
/// Id of the MixNode that this delegation was performed against.
pub node_id: NodeId,
#[serde(alias = "node_id")]
pub mix_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
#[serde(rename = "crr")]
#[serde(alias = "crr")]
pub cumulative_reward_ratio: Decimal,
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
@@ -55,7 +56,7 @@ pub struct Delegation {
impl Delegation {
pub fn new(
owner: Addr,
node_id: NodeId,
mix_id: NodeId,
cumulative_reward_ratio: Decimal,
amount: Coin,
height: u64,
@@ -63,7 +64,7 @@ impl Delegation {
) -> Self {
Delegation {
owner,
node_id,
mix_id,
cumulative_reward_ratio,
amount,
height,
@@ -72,20 +73,20 @@ impl Delegation {
}
pub fn generate_storage_key(
node_id: NodeId,
mix_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
(node_id, generate_owner_storage_subkey(owner_address, proxy))
(mix_id, generate_owner_storage_subkey(owner_address, proxy))
}
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
node_id: NodeId,
mix_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(node_id, owner_proxy_subkey)
(mix_id, owner_proxy_subkey)
}
pub fn dec_amount(&self) -> Decimal {
@@ -99,7 +100,7 @@ impl Delegation {
}
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.node_id, &self.owner, self.proxy.as_ref())
Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref())
}
}
@@ -31,8 +31,8 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Mixnode ({id}) does not exist")]
MixNodeBondNotFound { id: NodeId },
#[error("Mixnode ({mix_id}) does not exist")]
MixNodeBondNotFound { mix_id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
@@ -83,23 +83,23 @@ pub enum MixnetContractError {
epoch_end: i64,
},
#[error("Mixnode {node_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
node_id: NodeId,
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {node_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
node_id: NodeId,
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {node_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { node_id: NodeId },
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { mix_id: NodeId },
#[error("Mixnode {node_id} has already unbonded")]
MixnodeHasUnbonded { node_id: NodeId },
#[error("Mixnode {mix_id} has already unbonded")]
MixnodeHasUnbonded { mix_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
@@ -134,6 +134,6 @@ pub enum MixnetContractError {
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {node_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { node_id: NodeId },
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { mix_id: NodeId },
}
@@ -6,7 +6,9 @@ use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
pub use contracts_common::events::*;
use cosmwasm_std::{Addr, Coin, Event};
use cosmwasm_std::{Addr, Coin, Decimal, Event};
pub const EVENT_VERSION_PREFIX: &str = "v2_";
pub enum MixnetEventType {
MixnodeBonding,
@@ -47,7 +49,7 @@ impl From<MixnetEventType> for String {
impl ToString for MixnetEventType {
fn to_string(&self) -> String {
match self {
let event_name = match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
@@ -78,8 +80,9 @@ impl ToString for MixnetEventType {
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
}
.into()
};
format!("{}{}", EVENT_VERSION_PREFIX, event_name)
}
}
@@ -96,7 +99,7 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
// bonding/unbonding
pub const NODE_ID_KEY: &str = "node_id";
pub const MIX_ID_KEY: &str = "mix_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
@@ -125,6 +128,8 @@ pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates";
pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation";
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
@@ -197,7 +202,7 @@ pub fn new_withdraw_operator_reward_event(
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(
@@ -264,7 +269,7 @@ pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: No
Event::new(MixnetEventType::Undelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_undelegation_event(
@@ -275,7 +280,7 @@ pub fn new_pending_undelegation_event(
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
@@ -309,12 +314,12 @@ pub fn new_mixnode_bonding_event(
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
mix_id: NodeId,
assigned_layer: Layer,
) -> Event {
// coin implements Display trait and we use that implementation here
Event::new(MixnetEventType::MixnodeBonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
@@ -322,55 +327,55 @@ pub fn new_mixnode_bonding_event(
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(NODE_ID_KEY, node_id.to_string())
pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
pub fn new_mixnode_config_update_event(
node_id: NodeId,
mix_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
node_id: NodeId,
mix_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_mixnode_cost_params_update_event(
node_id: NodeId,
mix_id: NodeId,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
@@ -426,41 +431,41 @@ pub fn new_settings_update_event(
event
}
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_rewarding_event(
interval: Interval,
node_id: NodeId,
mix_id: NodeId,
reward_distribution: RewardDistribution,
prior_delegates: Decimal,
prior_unit_delegation: Decimal,
) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
// TODO: to calculate Timmy's (delegator) reward at this time
// emit:
// - unit delegation BEFORE rewarding (to determine Timmy's state before rewarding happened)
// - total delegation BEFORE rewarding
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
.add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
@@ -78,34 +78,34 @@ impl MixNodeDetails {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
/// Information provided by the operator that influence the cost function.
#[serde(rename = "cp")]
#[serde(alias = "cp")]
pub cost_params: MixNodeCostParams,
/// Total pledge and compounded reward earned by the node operator.
#[serde(rename = "op")]
#[serde(alias = "op")]
pub operator: Decimal,
/// Total delegation and compounded reward earned by all node delegators.
#[serde(rename = "dg")]
#[serde(alias = "dg")]
pub delegates: Decimal,
/// Cumulative reward earned by the "unit delegation" since the block 0.
#[serde(rename = "tur")]
#[serde(alias = "tur")]
pub total_unit_reward: Decimal,
/// Value of the theoretical "unit delegation" that has delegated to this mixnode at block 0.
#[serde(rename = "ud")]
#[serde(alias = "ud")]
pub unit_delegation: Decimal,
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
/// to reward it multiple times in the same epoch.
#[serde(rename = "le")]
#[serde(alias = "le")]
pub last_rewarded_epoch: EpochId,
// technically we don't need that field to determine reward magnitude or anything
// but it saves on extra queries to determine if we're removing the final delegation
// (so that we could zero the field correctly)
#[serde(rename = "uqd")]
#[serde(alias = "uqd")]
pub unique_delegations: u32,
}
-251
View File
@@ -1,251 +0,0 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
DefaultNetworkDetails, DenomDetailsOwned, NymNetworkDetails, ValidatorDetails,
MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, str::FromStr};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NetworkDefaultsError {
#[error("The provided network was invalid")]
MalformedNetworkProvided(String),
}
// the reason for allowing it is that this is just a temporary solution
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum Network {
QA,
SANDBOX,
MAINNET,
CUSTOM { details: NymNetworkDetails },
}
impl Network {
pub fn new_custom(details: NymNetworkDetails) -> Self {
Network::CUSTOM { details }
}
pub fn details(&self) -> NymNetworkDetails {
match self {
Self::QA => (&*QA_DEFAULTS).into(),
Self::SANDBOX => (&*SANDBOX_DEFAULTS).into(),
Self::MAINNET => (&*MAINNET_DEFAULTS).into(),
// I dislike the clone here, but for compatibility reasons we cannot define other networks with `NymNetworkDetails` directly yet
Self::CUSTOM { details } => details.clone(),
}
}
pub fn bech32_prefix(&self) -> String {
self.details().chain_details.bech32_account_prefix
}
pub fn mix_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.mix_denom
}
pub fn stake_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.stake_denom
}
pub fn base_mix_denom(&self) -> String {
self.details().chain_details.mix_denom.base
}
pub fn base_stake_denom(&self) -> String {
self.details().chain_details.stake_denom.base
}
pub fn mixnet_contract_address(&self) -> Option<String> {
self.details().contracts.mixnet_contract_address
}
pub fn vesting_contract_address(&self) -> Option<String> {
self.details().contracts.vesting_contract_address
}
pub fn bandwidth_claim_contract_address(&self) -> Option<String> {
self.details().contracts.bandwidth_claim_contract_address
}
pub fn coconut_bandwidth_contract_address(&self) -> Option<String> {
self.details().contracts.coconut_bandwidth_contract_address
}
pub fn multisig_contract_address(&self) -> Option<String> {
self.details().contracts.multisig_contract_address
}
pub fn validators(&self) -> Vec<ValidatorDetails> {
self.details().endpoints
}
// only used in mixnet contract tests, but I don't want to be messing with that code now
pub fn rewarding_validator_address(&self) -> &str {
match self {
Network::QA => crate::qa::REWARDING_VALIDATOR_ADDRESS,
Network::SANDBOX => crate::sandbox::REWARDING_VALIDATOR_ADDRESS,
Network::MAINNET => crate::mainnet::REWARDING_VALIDATOR_ADDRESS,
Network::CUSTOM { .. } => {
panic!("rewarding validator address is unavailable for a custom network")
}
}
}
// this should be handled differently, but I don't want to break compatibility
pub fn statistics_service_url(&self) -> &str {
match self {
Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::CUSTOM { .. } => {
panic!("statistics service url is unavailable for a custom network")
}
}
}
}
impl FromStr for Network {
type Err = NetworkDefaultsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qa" => Ok(Network::QA),
"sandbox" => Ok(Network::SANDBOX),
"mainnet" => Ok(Network::MAINNET),
_ => Err(NetworkDefaultsError::MalformedNetworkProvided(
s.to_string(),
)),
}
}
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Network::QA => f.write_str("QA"),
Network::SANDBOX => f.write_str("Sandbox"),
Network::MAINNET => f.write_str("Mainnet"),
Network::CUSTOM { .. } => f.write_str("Custom"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct NetworkDetails {
bech32_prefix: String,
mix_denom: DenomDetailsOwned,
stake_denom: DenomDetailsOwned,
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
impl From<&DefaultNetworkDetails> for NetworkDetails {
fn from(details: &DefaultNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
mixnet_contract_address: details.mixnet_contract_address.into(),
vesting_contract_address: details.vesting_contract_address.into(),
bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(),
statistics_service_url: details.statistics_service_url.into(),
validators: details.validators.clone(),
}
}
}
// this also has to exist for compatibility reasons since I don't want to be touching the wallet now
impl From<NymNetworkDetails> for NetworkDetails {
fn from(details: NymNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.chain_details.bech32_account_prefix,
mix_denom: details.chain_details.mix_denom,
stake_denom: details.chain_details.stake_denom,
mixnet_contract_address: details
.contracts
.mixnet_contract_address
.unwrap_or_default(),
vesting_contract_address: details
.contracts
.vesting_contract_address
.unwrap_or_default(),
bandwidth_claim_contract_address: details
.contracts
.bandwidth_claim_contract_address
.unwrap_or_default(),
statistics_service_url: "".to_string(),
validators: details.endpoints,
}
}
}
impl NetworkDetails {
pub fn base_mix_denom(&self) -> &str {
&self.mix_denom.base
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct SupportedNetworks {
networks: HashMap<Network, NetworkDetails>,
}
impl SupportedNetworks {
pub fn new(support: Vec<Network>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| {
let details = n.details().into();
(n, details)
})
.collect(),
}
}
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bech32_prefix.as_str())
}
pub fn base_mix_denom(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.base_mix_denom())
}
pub fn mixnet_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.mixnet_contract_address.as_str())
}
pub fn vesting_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.vesting_contract_address.as_str())
}
pub fn bandwidth_claim_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bandwidth_claim_contract_address.as_str())
}
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(&network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
+41 -100
View File
@@ -1,15 +1,11 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, path::PathBuf};
use std::{env::var, ops::Not, path::PathBuf};
use url::Url;
pub mod all;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
@@ -103,159 +99,99 @@ impl NymNetworkDetails {
}
pub fn new_mainnet() -> Self {
(&*MAINNET_DEFAULTS).into()
fn parse_optional_str(raw: &str) -> Option<String> {
raw.is_empty().not().then(|| raw.into())
}
// Consider caching this process (lazy static)
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
mix_denom: mainnet::MIX_DENOM.into(),
stake_denom: mainnet::STAKE_DENOM.into(),
},
endpoints: mainnet::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
},
}
}
#[must_use]
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
}
#[must_use]
pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self {
self.chain_details.mix_denom = mix_denom;
self
}
#[must_use]
pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self {
self.chain_details.stake_denom = stake_denom;
self
}
#[must_use]
pub fn with_base_mix_denom<S: Into<String>>(mut self, base_mix_denom: S) -> Self {
self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into());
self
}
#[must_use]
pub fn with_base_stake_denom<S: Into<String>>(mut self, base_stake_denom: S) -> Self {
self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into());
self
}
#[must_use]
pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
self.endpoints.push(endpoint);
self
}
#[must_use]
pub fn with_mixnet_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.mixnet_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_vesting_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.vesting_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_bandwidth_claim_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.bandwidth_claim_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_coconut_bandwidth_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.coconut_bandwidth_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
}
// This conversion only exists for convenience reasons until
// we can completely phase out `DefaultNetworkDetails`
impl<'a> From<&'a DefaultNetworkDetails> for NymNetworkDetails {
fn from(details: &'a DefaultNetworkDetails) -> Self {
fn parse_optional_str(raw: &str) -> Option<String> {
if raw.is_empty() {
None
} else {
Some(raw.into())
}
}
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
},
endpoints: details.validators.clone(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(details.mixnet_contract_address),
vesting_contract_address: parse_optional_str(details.vesting_contract_address),
bandwidth_claim_contract_address: parse_optional_str(
details.bandwidth_claim_contract_address,
),
coconut_bandwidth_contract_address: parse_optional_str(
details.coconut_bandwidth_contract_address,
),
multisig_contract_address: parse_optional_str(details.multisig_contract_address),
},
}
}
}
// Since these are lazily constructed, we can afford to switch some of them to stronger types in the
// future. If we do this, and also get rid of the references we could potentially unify with
// `NetworkDetails`.
pub struct DefaultNetworkDetails {
bech32_prefix: &'static str,
mix_denom: DenomDetails,
stake_denom: DenomDetails,
mixnet_contract_address: &'static str,
vesting_contract_address: &'static str,
bandwidth_claim_contract_address: &'static str,
coconut_bandwidth_contract_address: &'static str,
multisig_contract_address: &'static str,
#[allow(dead_code)]
rewarding_validator_address: &'static str,
statistics_service_url: &'static str,
validators: Vec<ValidatorDetails>,
}
static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: mainnet::BECH32_PREFIX,
mix_denom: mainnet::MIX_DENOM,
stake_denom: mainnet::STAKE_DENOM,
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: mainnet::validators(),
});
static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: sandbox::BECH32_PREFIX,
mix_denom: sandbox::MIX_DENOM,
stake_denom: sandbox::STAKE_DENOM,
mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: sandbox::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: sandbox::validators(),
});
static QA_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: qa::BECH32_PREFIX,
mix_denom: qa::MIX_DENOM,
stake_denom: qa::STAKE_DENOM,
mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: qa::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: qa::validators(),
});
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct DenomDetails {
pub base: &'static str,
@@ -349,12 +285,17 @@ pub fn setup_env(config_env_file: Option<PathBuf>) {
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
// overwrite them
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
// if we haven't explicitly defined any of the constants, fallback to defaults
crate::mainnet::export_to_env_if_not_set()
}
// Name of the event triggered by the eth contract. If the event name is changed,
+82 -17
View File
@@ -31,42 +31,107 @@ pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
}
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
fn set_var_to_default(var: &str, value: &str) {
std::env::set_var(var, value);
std::env::set_var(format!("{}{}", var, DEFAULT_SUFFIX), "1")
}
fn set_var_conditionally_to_default(var: &str, value: &str) {
if std::env::var(var).is_err() {
set_var_to_default(var, value)
}
}
pub fn uses_default(var: &str) -> bool {
std::env::var(format!("{}{}", var, DEFAULT_SUFFIX)).is_ok()
}
pub fn read_var_if_not_default(var: &str) -> Option<String> {
if uses_default(var) {
None
} else {
std::env::var(var).ok()
}
}
pub fn export_to_env() {
std::env::set_var(var_names::CONFIGURED, "true");
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
std::env::set_var(
set_var_to_default(var_names::CONFIGURED, "true");
set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_to_default(
var_names::DENOMS_EXPONENT,
STAKE_DENOM.display_exponent.to_string(),
&STAKE_DENOM.display_exponent.to_string(),
);
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
std::env::set_var(
set_var_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_to_default(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
std::env::set_var(
set_var_to_default(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
set_var_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
}
pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::CONFIGURED, "true");
set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_conditionally_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_conditionally_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_conditionally_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_conditionally_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_conditionally_to_default(
var_names::DENOMS_EXPONENT,
&STAKE_DENOM.display_exponent.to_string(),
);
set_var_conditionally_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_conditionally_to_default(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
set_var_conditionally_to_default(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
set_var_conditionally_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_conditionally_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
Some("https://qa-validator-api.nymtech.net/api"),
)]
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
+39 -11
View File
@@ -1,6 +1,5 @@
use crate::error::TypesError;
use config::defaults::all::Network;
use config::defaults::{DenomDetails, DenomDetailsOwned};
use config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails};
use cosmwasm_std::Fraction;
use cosmwasm_std::{Decimal, Uint128};
use schemars::JsonSchema;
@@ -56,10 +55,16 @@ pub type Denom = String;
pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
impl RegisteredCoins {
pub fn default_denoms(network: Network) -> Self {
pub fn default_denoms(network: &NymNetworkDetails) -> Self {
let mut network_coins = HashMap::new();
network_coins.insert(network.mix_denom().base, network.mix_denom().into());
network_coins.insert(network.stake_denom().base, network.stake_denom().into());
network_coins.insert(
network.chain_details.mix_denom.base.clone(),
network.chain_details.mix_denom.clone().into(),
);
network_coins.insert(
network.chain_details.stake_denom.base.clone(),
network.chain_details.stake_denom.clone().into(),
);
RegisteredCoins(network_coins)
}
@@ -513,7 +518,7 @@ mod test {
#[test]
fn converting_to_display() {
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
@@ -527,16 +532,29 @@ mod test {
];
for (raw, expected) in values {
let coin = Coin::new(raw, Network::MAINNET.mix_denom().base);
let coin = Coin::new(
raw,
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.base
.clone(),
);
let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
assert_eq!(Network::MAINNET.mix_denom().display, display.denom);
assert_eq!(
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.display,
display.denom
);
assert_eq!(expected, display.amount.to_string());
}
}
#[test]
fn converting_to_base() {
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
@@ -551,11 +569,21 @@ mod test {
for (expected, raw_display) in values {
let coin = DecCoin {
denom: Network::MAINNET.mix_denom().display,
denom: NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.display
.clone(),
amount: raw_display.parse().unwrap(),
};
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
assert_eq!(Network::MAINNET.mix_denom().base, base.denom);
assert_eq!(
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.base,
base.denom
);
assert_eq!(expected, base.amount);
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ impl Delegation {
) -> Result<Self, TypesError> {
Ok(Delegation {
owner: delegation.owner.to_string(),
mix_id: delegation.node_id,
mix_id: delegation.mix_id,
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
height: delegation.height,
proxy: delegation.proxy.map(|d| d.to_string()),
+2
View File
@@ -72,6 +72,8 @@ pub enum TypesError {
UnknownCoinDenom(String),
#[error("Provided event is not a delegation event")]
NotADelegationEvent,
#[error("Unknown network - {0}")]
UnknownNetwork(String),
}
impl Serialize for TypesError {
+3
View File
@@ -3,10 +3,12 @@
### Added
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
### Changed
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
- network-requester: updated CLI to use `clap` macros ([#1638])
### Fixed
@@ -18,6 +20,7 @@
[#1569]: https://github.com/nymtech/nym/pull/1569
[#1569]: https://github.com/nymtech/nym/pull/1571
[#1613]: https://github.com/nymtech/nym/pull/1613
[#1638]: https://github.com/nymtech/nym/pull/1638
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) fn undelegate(
) -> Result<Coin, MixnetContractError> {
let tokens = mix_rewarding.undelegate(&delegation)?;
rewards_storage::MIXNODE_REWARDING.save(store, delegation.node_id, &mix_rewarding)?;
rewards_storage::MIXNODE_REWARDING.save(store, delegation.mix_id, &mix_rewarding)?;
storage::delegations().replace(store, delegation.storage_key(), None, Some(&delegation))?;
Ok(tokens)
+13 -13
View File
@@ -74,7 +74,7 @@ pub(crate) fn query_delegator_delegations_paged(
let start_next_after = delegations
.last()
.map(|del| (del.node_id, del.proxy_storage_key()));
.map(|del| (del.mix_id, del.proxy_storage_key()));
Ok(PagedDelegatorDelegationsResponse::new(
delegations,
@@ -284,19 +284,19 @@ mod tests {
let res1 = query_mixnode_delegations_paged(test.deps(), mix_id1, None, None).unwrap();
assert_eq!(res1.delegations.len(), 10);
assert!(res1.delegations.into_iter().all(|d| d.node_id == mix_id1));
assert!(res1.delegations.into_iter().all(|d| d.mix_id == mix_id1));
let res2 = query_mixnode_delegations_paged(test.deps(), mix_id2, None, None).unwrap();
assert_eq!(res2.delegations.len(), 14);
assert!(res2.delegations.into_iter().all(|d| d.node_id == mix_id2));
assert!(res2.delegations.into_iter().all(|d| d.mix_id == mix_id2));
let res3 = query_mixnode_delegations_paged(test.deps(), mix_id3, None, None).unwrap();
assert_eq!(res3.delegations.len(), 10);
assert!(res3.delegations.into_iter().all(|d| d.node_id == mix_id3));
assert!(res3.delegations.into_iter().all(|d| d.mix_id == mix_id3));
let res4 = query_mixnode_delegations_paged(test.deps(), mix_id4, None, None).unwrap();
assert_eq!(res4.delegations.len(), 10);
assert!(res4.delegations.into_iter().all(|d| d.node_id == mix_id4));
assert!(res4.delegations.into_iter().all(|d| d.mix_id == mix_id4));
}
}
@@ -610,7 +610,7 @@ mod tests {
assert_eq!(1, page1.delegations.len());
assert!(
page1.delegations[0].owner.as_str() == delegator1
&& page1.delegations[0].node_id == mix_id1
&& page1.delegations[0].mix_id == mix_id1
);
test.add_immediate_delegation(delegator1, 1000u32, mix_id2);
@@ -621,11 +621,11 @@ mod tests {
assert_eq!(2, page1.delegations.len());
assert!(
page1.delegations[0].owner.as_str() == delegator1
&& page1.delegations[0].node_id == mix_id1
&& page1.delegations[0].mix_id == mix_id1
);
assert!(
page1.delegations[1].owner.as_str() == delegator1
&& page1.delegations[1].node_id == mix_id2
&& page1.delegations[1].mix_id == mix_id2
);
test.add_immediate_delegation(delegator2, 1000u32, mix_id1);
@@ -636,11 +636,11 @@ mod tests {
assert_eq!(2, another_page1.delegations.len());
assert!(
another_page1.delegations[0].owner.as_str() == delegator1
&& another_page1.delegations[0].node_id == mix_id1
&& another_page1.delegations[0].mix_id == mix_id1
);
assert!(
another_page1.delegations[1].owner.as_str() == delegator2
&& another_page1.delegations[1].node_id == mix_id1
&& another_page1.delegations[1].mix_id == mix_id1
);
// retrieving the next page should start after the last key on this page
@@ -652,7 +652,7 @@ mod tests {
assert_eq!(1, page2.delegations.len());
assert!(
page2.delegations[0].owner.as_str() == delegator1
&& page2.delegations[0].node_id == mix_id2
&& page2.delegations[0].mix_id == mix_id2
);
// save another one
@@ -665,11 +665,11 @@ mod tests {
assert_eq!(2, page2.delegations.len());
assert!(
page2.delegations[0].owner.as_str() == delegator1
&& page2.delegations[0].node_id == mix_id2
&& page2.delegations[0].mix_id == mix_id2
);
assert!(
page2.delegations[1].owner.as_str() == delegator2
&& page2.delegations[1].node_id == mix_id2
&& page2.delegations[1].mix_id == mix_id2
);
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, Delega
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.node_id,
|d| d.mix_id,
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
@@ -49,9 +49,9 @@ pub(crate) fn _try_delegate_to_mixnode(
// check if the target node actually exists and is still bonded
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
None => return Err(MixnetContractError::MixNodeBondNotFound { id: mix_id }),
None => return Err(MixnetContractError::MixNodeBondNotFound { mix_id }),
Some(bond) if bond.is_unbonding => {
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id })
}
_ => (),
}
@@ -144,7 +144,7 @@ mod tests {
let res = try_delegate_to_mixnode(test.deps_mut(), sender, 42);
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound { id: 42 })
Err(MixnetContractError::MixNodeBondNotFound { mix_id: 42 })
)
}
@@ -240,7 +240,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
node_id: mix_id_unbonding
mix_id: mix_id_unbonding
})
);
@@ -248,7 +248,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound {
id: mix_id_unbonded
mix_id: mix_id_unbonded
})
);
@@ -256,7 +256,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound {
id: mix_id_unbonded_leftover
mix_id: mix_id_unbonded_leftover
})
);
}
@@ -180,7 +180,7 @@ fn update_rewarded_set(
let mut tmp_set = BTreeSet::new();
for node_id in &new_rewarded_set {
if !tmp_set.insert(node_id) {
return Err(MixnetContractError::DuplicateRewardedSetNode { node_id: *node_id });
return Err(MixnetContractError::DuplicateRewardedSetNode { mix_id: *node_id });
}
}
@@ -1015,7 +1015,7 @@ mod tests {
.unwrap_err();
assert_eq!(
err,
MixnetContractError::DuplicateRewardedSetNode { node_id: 1 }
MixnetContractError::DuplicateRewardedSetNode { mix_id: 1 }
);
let nodes_with_duplicate = vec![1, 2, 3, 5, 4, 5];
let err = update_rewarded_set(
@@ -1026,7 +1026,7 @@ mod tests {
.unwrap_err();
assert_eq!(
err,
MixnetContractError::DuplicateRewardedSetNode { node_id: 5 }
MixnetContractError::DuplicateRewardedSetNode { mix_id: 5 }
);
}
+2 -2
View File
@@ -28,9 +28,9 @@ pub(crate) fn must_get_mixnode_bond_by_owner(
pub(crate) fn get_mixnode_details_by_id(
store: &dyn Storage,
node_id: NodeId,
mix_id: NodeId,
) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, node_id)? {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.id)?;
+4 -13
View File
@@ -453,10 +453,7 @@ pub mod tests {
// but fails if repeated (since the node is already in the "unbonding" state)(
let res = try_remove_mixnode(deps.as_mut(), info);
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
@@ -522,10 +519,7 @@ pub mod tests {
// but we cannot perform any updates whilst the mixnode is already unbonding
try_remove_mixnode(deps.as_mut(), info.clone()).unwrap();
let res = try_update_mixnode_config(deps.as_mut(), info, update);
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
@@ -586,7 +580,7 @@ pub mod tests {
assert_eq!(1, event.0);
assert_eq!(
PendingIntervalEventData::ChangeMixCostParams {
mix_id: mix_id,
mix_id,
new_costs: update.clone()
},
event.1
@@ -604,10 +598,7 @@ pub mod tests {
// but we cannot perform any updates whilst the mixnode is already unbonding
try_remove_mixnode(deps.as_mut(), info.clone()).unwrap();
let res = try_update_mixnode_cost_params(deps.as_mut(), info, update);
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
+1 -1
View File
@@ -57,7 +57,7 @@ pub(crate) fn withdraw_delegator_reward(
delegation: Delegation,
mut mix_rewarding: MixNodeRewarding,
) -> Result<Coin, MixnetContractError> {
let mix_id = delegation.node_id;
let mix_id = delegation.mix_id;
let mut updated_delegation = delegation.clone();
let reward = mix_rewarding.withdraw_delegator_reward(&mut updated_delegation)?;
+24 -22
View File
@@ -33,7 +33,7 @@ pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
node_id: NodeId,
mix_id: NodeId,
node_performance: Performance,
) -> Result<Response, MixnetContractError> {
ensure_is_authorized(info.sender, deps.storage)?;
@@ -50,34 +50,34 @@ pub(crate) fn try_reward_mixnode(
// there's a chance of this failing to load the details if the mixnode unbonded before rewards
// were distributed and all of its delegators are also gone
let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, node_id)? {
let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)? {
Some(mix_rewarding) if mix_rewarding.still_bonded() => mix_rewarding,
// don't fail if the node has unbonded as we don't want to fail the underlying transaction
_ => {
return Ok(
Response::new().add_event(new_not_found_mix_operator_rewarding_event(
interval, node_id,
)),
);
return Ok(Response::new()
.add_event(new_not_found_mix_operator_rewarding_event(interval, mix_id)));
}
};
let prior_delegates = mix_rewarding.delegates;
let prior_unit_delegation = mix_rewarding.unit_delegation;
// check if this node has already been rewarded for the current epoch.
// unlike the previous check, this one should be a hard error since this cannot be
// influenced by users actions
let absolute_epoch_id = interval.current_epoch_absolute_id();
if absolute_epoch_id == mix_rewarding.last_rewarded_epoch {
return Err(MixnetContractError::MixnodeAlreadyRewarded {
node_id,
mix_id,
absolute_epoch_id,
});
}
// again a hard error since the rewarding validator should have known not to reward this node
let node_status = interval_storage::REWARDED_SET
.load(deps.storage, node_id)
.load(deps.storage, mix_id)
.map_err(|_| MixnetContractError::MixnodeNotInRewardedSet {
node_id,
mix_id,
absolute_epoch_id,
})?;
@@ -85,10 +85,10 @@ pub(crate) fn try_reward_mixnode(
// however, we still need to update last_rewarded_epoch field
if node_performance.is_zero() {
mix_rewarding.last_rewarded_epoch = absolute_epoch_id;
storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?;
storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?;
return Ok(
Response::new().add_event(new_zero_uptime_mix_operator_rewarding_event(
interval, node_id,
interval, mix_id,
)),
);
}
@@ -106,13 +106,15 @@ pub(crate) fn try_reward_mixnode(
mix_rewarding.distribute_rewards(reward_distribution, absolute_epoch_id);
// persist changes happened to the storage
storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?;
storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?;
storage::reward_accounting(deps.storage, node_reward)?;
Ok(Response::new().add_event(new_mix_rewarding_event(
interval,
node_id,
mix_id,
reward_distribution,
prior_delegates,
prior_unit_delegation,
)))
}
@@ -228,9 +230,9 @@ pub(crate) fn _try_withdraw_delegator_reward(
// (in that case the expected path of getting your tokens back is via undelegation)
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
Some(mix_bond) if mix_bond.is_unbonding => {
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id })
}
None => return Err(MixnetContractError::MixnodeHasUnbonded { node_id: mix_id }),
None => return Err(MixnetContractError::MixnodeHasUnbonded { mix_id }),
_ => (),
};
@@ -523,7 +525,7 @@ pub mod tests {
assert!(res_standby.is_ok());
assert!(matches!(
res_inactive,
Err(MixnetContractError::MixnodeNotInRewardedSet { node_id, .. }) if node_id == inactive_mix_id
Err(MixnetContractError::MixnodeNotInRewardedSet { mix_id, .. }) if mix_id == inactive_mix_id
));
}
@@ -552,7 +554,7 @@ pub mod tests {
let res = try_reward_mixnode(test.deps_mut(), env, sender.clone(), mix_id, performance);
assert!(matches!(
res,
Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id
Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id
));
// in the following epoch we're good again
@@ -601,7 +603,7 @@ pub mod tests {
);
assert!(matches!(
res,
Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id
Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id
));
// but in the next epoch, as always, we're good again
@@ -912,7 +914,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
node_id: mix_id_unbonding
mix_id: mix_id_unbonding
})
);
@@ -921,7 +923,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeHasUnbonded {
node_id: mix_id_unbonded_leftover
mix_id: mix_id_unbonded_leftover
})
);
}
@@ -1193,7 +1195,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
node_id: mix_id_unbonding
mix_id: mix_id_unbonding
})
);
+1 -1
View File
@@ -152,7 +152,7 @@ pub(crate) fn ensure_proxy_match(
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
if bond.is_unbonding {
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: bond.id });
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id: bond.id });
}
Ok(())
}
+2 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
chrono = { version = "0.4.19", features = ["serde"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
humantime-serde = "1.0"
isocountry = "0.3.2"
itertools = "0.10.3"
@@ -23,6 +23,7 @@ serde = "1.0.126"
serde_json = "1.0.66"
thiserror = "1.0.29"
tokio = {version = "1.19.1", features = ["full"] }
maxminddb = "0.23.0"
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
network-defaults = { path = "../common/network-defaults" }
+7 -1
View File
@@ -11,10 +11,16 @@ Features:
## Running
Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://app.ipbase.com/.
Supply the environment variable `GEOIP_DATABASE_PATH` with a path
that points to a GeoIP2 database file in binary format.
Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt.
Setup nginx to inject the request IP to the header `X-Real-IP`.
Use https://github.com/maxmind/geoipupdate to automatically
provide and update the GeoIP2 database file.
# TODO / Known Issues
## TODO
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// 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<Route>, OpenApi) {
openapi_get_routes_spec![settings: terms]
}
#[openapi(tag = "terms")]
#[get("/")]
pub(crate) async fn terms(
_state: &State<ExplorerApiStateContext>,
location: Location,
) -> Result<Json<String>, status::Forbidden<String>> {
if location.iso_alpha2 == "US" {
return Err(status::Forbidden(Some("US government sucks".to_string())));
}
Ok(Json("Nym Terms & Conditions: Welcome".to_string()))
}
+1
View File
@@ -0,0 +1 @@
pub(crate) mod http;
@@ -1,12 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mix_nodes::location::{GeoLocation, Location};
use crate::mix_nodes::location::Location;
use crate::state::ExplorerApiStateContext;
use log::{info, warn};
use reqwest::Error as ReqwestError;
use task::ShutdownListener;
use thiserror::Error;
pub(crate) struct GeoLocateTask {
state: ExplorerApiStateContext,
@@ -19,13 +17,6 @@ impl GeoLocateTask {
}
pub(crate) fn start(mut self) {
if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() {
error!(
"Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started."
);
return;
}
info!("Spawning mix node locator task runner...");
tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
@@ -53,6 +44,8 @@ impl GeoLocateTask {
.await
.unwrap_or_default();
let geo_ip = self.state.inner.geo_ip.0.clone();
for (i, cache_item) in mixnode_bonds.values().enumerate() {
if self
.state
@@ -65,99 +58,50 @@ impl GeoLocateTask {
continue;
}
// the mix node has not been located or is the cache time has expired
match locate(&cache_item.mix_node().host).await {
Ok(geo_location) => {
let location = Location::new(geo_location);
match geo_ip.query(&cache_item.mix_node().host) {
Ok(opt) => match opt {
Some(location) => {
let location = Location::new(location);
trace!(
"{} mix nodes already located. Ip {} is located in {:#?}",
i,
cache_item.mix_node().host,
location.three_letter_iso_country_code,
);
if i > 0 && (i % 100) == 0 {
info!(
"Located {} mixnodes...",
i + 1,
trace!(
"{} mix nodes already located. Ip {} is located in {:#?}",
i,
cache_item.mix_node().host,
location.three_letter_iso_country_code,
);
if i > 0 && (i % 100) == 0 {
info!("Located {} mixnodes...", i + 1,);
}
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), Some(location))
.await;
// one node has been located, so return out of the loop
return;
}
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), Some(location))
.await;
// one node has been located, so return out of the loop
return;
}
Err(e) => match e {
LocateError::ReqwestError(e) => warn!(
"❌ Oh no! Location for {} failed {}",
cache_item.mix_node().host, e
),
LocateError::NotFound(e) => {
warn!(
"❌ Location for {} not found. Response body: {}",
cache_item.mix_node().host, e
);
None => {
warn!("❌ Location for {} not found.", cache_item.mix_node().host);
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), None)
.await;
},
LocateError::RateLimited(e) => warn!(
"❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}",
cache_item.mix_node().host, e
),
}
},
}
Err(e) => {
warn!(
"❌ Oh no! Location for {} failed. Error: {:#?}",
cache_item.mix_node().host,
e
);
}
};
}
trace!("All mix nodes located");
}
}
#[derive(Debug, Error)]
enum LocateError {
#[error("Oops, we have made too many requests and are being rate limited. Request body: {0}")]
RateLimited(String),
#[error("Geolocation not found. Request body: {0}")]
NotFound(String),
#[error(transparent)]
ReqwestError(#[from] ReqwestError),
}
async fn locate(ip: &str) -> Result<GeoLocation, LocateError> {
let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY")
.expect("Env var GEO_IP_SERVICE_API_KEY is not set");
let uri = format!("{}/?apikey={}&ip={}", crate::GEO_IP_SERVICE, api_key, ip);
match reqwest::get(uri.clone()).await {
Ok(response) => {
if response.status() == 429 {
return Err(LocateError::RateLimited(
response
.text()
.await
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
));
}
if response.status() == 404 {
return Err(LocateError::NotFound(
response
.text()
.await
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
));
}
let location = response.json::<GeoLocation>().await?;
Ok(location)
}
Err(e) => Err(LocateError::ReqwestError(e)),
}
}
Binary file not shown.
+112
View File
@@ -0,0 +1,112 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use isocountry::CountryCode;
use log::warn;
use maxminddb::{geoip2::Country, MaxMindDBError, Reader};
use std::{net::IpAddr, str::FromStr, sync::Arc};
const DEFAULT_DATABASE_PATH: &str = "./src/geo_ip/GeoLite2-Country.mmdb";
#[derive(Debug)]
pub enum GeoIpError {
NoValidIP,
InternalError,
}
// The current State implementation does not allow to fail on state
// creation, ie. returning Result<>. To avoid to use unwrap family,
// as a workaround, wrap the state inside an Option<>
// If Reader::open_readfile fails for some reason db will will be set to None
// and an error will be logged.
pub(crate) struct GeoIp {
pub(crate) db: Option<Reader<Vec<u8>>>,
}
#[derive(Clone)]
pub(crate) struct ThreadsafeGeoIp(pub Arc<GeoIp>);
pub(crate) struct Location {
/// two-letter country code (ISO 3166-1 alpha-2)
pub(crate) iso_alpha2: String,
/// three-letter country code (ISO 3166-1 alpha-3)
pub(crate) iso_alpha3: String,
/// English country short name (ISO 3166-1)
pub(crate) name: String,
}
impl GeoIp {
pub fn new() -> Self {
let db_path = std::env::var("GEOIP_DATABASE_PATH").unwrap_or_else(|e| {
warn!(
"Env variable GEOIP_DATABASE_PATH is not set: {} - Fallback to {}",
e, DEFAULT_DATABASE_PATH
);
DEFAULT_DATABASE_PATH.to_string()
});
let reader = Reader::open_readfile(&db_path)
.map_err(|e| {
error!("Fail to open GeoLite2 database file {}: {}", db_path, e);
})
.ok();
GeoIp { db: reader }
}
pub fn query(&self, address: &str) -> Result<Option<Location>, GeoIpError> {
let ip: IpAddr = FromStr::from_str(address).map_err(|e| {
error!("Fail to create IpAddr from {}: {}", &address, e);
GeoIpError::NoValidIP
})?;
let result = self
.db
.as_ref()
.ok_or_else(|| {
error!("No registered GeoIP database");
GeoIpError::InternalError
})?
.lookup::<Country>(ip);
match &result {
Ok(v) => Ok(Some(
Location::try_from(v).map_err(|_| GeoIpError::InternalError)?,
)),
Err(e) => match e {
MaxMindDBError::AddressNotFoundError(_) => Ok(None),
_ => Err(GeoIpError::InternalError),
},
}
}
}
impl<'a> TryFrom<&Country<'a>> for Location {
type Error = String;
fn try_from(country: &Country) -> Result<Self, Self::Error> {
let data = country.country.as_ref().ok_or_else(|| {
warn!("No Country data found");
"No Country data found"
})?;
let iso_alpha2 = String::from(data.iso_code.ok_or_else(|| {
warn!("No iso alpha-2 code found in Country data {:#?}", data);
"No iso alpha-2 code found in Country data"
})?);
let iso_codes = CountryCode::for_alpha2(&iso_alpha2).map_err(|e| {
let message = format!(
"Fail to get iso codes from iso alpha-2 country code {}: {}",
&iso_alpha2, e
);
warn!("{}", &message);
message
})?;
Ok(Location {
iso_alpha2,
iso_alpha3: String::from(iso_codes.alpha3()),
name: String::from(iso_codes.name()),
})
}
}
impl ThreadsafeGeoIp {
pub fn new() -> Self {
ThreadsafeGeoIp(Arc::new(GeoIp::new()))
}
}
+1
View File
@@ -0,0 +1 @@
pub(crate) mod location;
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::geo_ip::location::{GeoIpError, Location};
use crate::state::ExplorerApiStateContext;
use rocket::http::Status;
use rocket::request::FromRequest;
use rocket::request::Outcome;
use rocket::Request;
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
const IP_HEADER: &str = "X-Real-IP";
#[derive(Debug)]
pub enum LocationError {
NoIP,
LocationNotFound,
InternalError,
}
fn find_location(request: &Request<'_>) -> Result<Location, (Status, LocationError)> {
let ip = request
.headers()
.get_one(IP_HEADER)
.map(|f| f.to_string())
.ok_or_else(|| {
error!("Header not found, {}", IP_HEADER);
(Status::Forbidden, LocationError::NoIP)
})?;
let geo_ip = &request
.rocket()
.state::<ExplorerApiStateContext>()
.ok_or((Status::InternalServerError, LocationError::InternalError))? // should never fail
.inner
.geo_ip;
let location = geo_ip
.0
.clone()
.query(&ip)
.map_err(|e| match e {
GeoIpError::NoValidIP => (Status::Forbidden, LocationError::NoIP),
GeoIpError::InternalError => {
(Status::InternalServerError, LocationError::InternalError)
}
})?
.ok_or_else(|| {
warn!("Fail to find a matching location for {}", ip);
(Status::Forbidden, LocationError::LocationNotFound)
})?;
Ok(location)
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Location {
type Error = LocationError;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
match find_location(request) {
Ok(loc) => Outcome::Success(loc),
Err(e) => Outcome::Failure(e),
}
}
}
impl<'a> OpenApiFromRequest<'a> for Location {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None)
}
}
+1
View File
@@ -0,0 +1 @@
pub(crate) mod location;
+2
View File
@@ -5,6 +5,7 @@ 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;
@@ -56,6 +57,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
"/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
+3 -1
View File
@@ -8,11 +8,14 @@ use log::info;
use network_defaults::setup_env;
use task::ShutdownNotifier;
mod buy_terms;
pub(crate) mod cache;
mod client;
pub(crate) mod commands;
mod country_statistics;
mod gateways;
mod geo_ip;
mod guards;
mod helpers;
mod http;
mod mix_node;
@@ -23,7 +26,6 @@ mod state;
mod tasks;
mod validators;
const GEO_IP_SERVICE: &str = "https://api.ipbase.com/json";
const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes
#[tokio::main]
-133
View File
@@ -8,7 +8,6 @@ use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats;
use crate::mix_node::models::{
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations,
};
use crate::mix_nodes;
use crate::state::ExplorerApiStateContext;
use mixnet_contract_common::{Delegation, NodeId};
use reqwest::Error as ReqwestError;
@@ -27,15 +26,6 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>,
get_description,
get_stats,
get_economic_dynamics_stats,
// =================================================
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
deprecated_get_delegations_by_identity,
deprecated_get_delegations_summed_by_identity,
deprecated_get_by_identity,
deprecated_get_description_by_identity,
deprecated_get_stats_by_identity,
deprecated_get_economic_dynamics_stats_by_identity,
]
}
@@ -204,126 +194,3 @@ pub(crate) async fn get_economic_dynamics_stats(
}
}
}
// =================================================
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
#[openapi(tag = "mix_nodes")]
#[get("/deprecated/<pubkey>")]
pub(crate) async fn deprecated_get_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Result<Json<PrettyDetailedMixNodeBond>, NotFound<String>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Err(NotFound("Mixnode not found".into()));
}
};
get_by_id(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/delegations")]
pub(crate) async fn deprecated_get_delegations_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<Delegation>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Json(Vec::new());
}
};
get_delegations(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/delegations/summed")]
pub(crate) async fn deprecated_get_delegations_summed_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<SummedDelegations>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Json(Vec::new());
}
};
get_delegations_summed(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/description")]
pub(crate) async fn deprecated_get_description_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<NodeDescription>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_description(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/stats")]
pub(crate) async fn deprecated_get_stats_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<NodeStats>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_stats(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/economic-dynamics-stats")]
pub(crate) async fn deprecated_get_economic_dynamics_stats_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<EconomicDynamicsStats>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_economic_dynamics_stats(mix_id, state).await
}
+2 -2
View File
@@ -173,10 +173,10 @@ fn get_common_owner(delegations: &[Delegation]) -> Option<Addr> {
}
fn get_common_mix_id(delegations: &[Delegation]) -> Option<NodeId> {
let mix_id = delegations.iter().next()?.node_id;
let mix_id = delegations.iter().next()?.mix_id;
if delegations
.iter()
.any(|delegation| delegation.node_id != mix_id)
.any(|delegation| delegation.mix_id != mix_id)
{
log::warn!("Unexpected different node identities when summing delegations");
return None;
-16
View File
@@ -1,16 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::state::ExplorerApiStateContext;
use mixnet_contract_common::NodeId;
pub(crate) async fn best_effort_pubkey_to_mix_id(
state: &ExplorerApiStateContext,
pub_key: &str,
) -> Option<NodeId> {
state
.inner
.get_mix_node_by_pubkey(pub_key)
.await
.map(|node| node.mix_id())
}
+5 -10
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code;
use crate::geo_ip::location;
use mixnet_contract_common::NodeId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -36,19 +36,14 @@ 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) lat: f32,
pub(crate) lng: f32,
}
impl Location {
pub(crate) fn new(geo_location: GeoLocation) -> Self {
let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location);
pub(crate) fn new(location: location::Location) -> Self {
Location {
country_name: geo_location.country_name,
two_letter_iso_country_code: geo_location.country_code,
three_letter_iso_country_code,
lat: geo_location.latitude,
lng: geo_location.longitude,
country_name: location.name,
two_letter_iso_country_code: location.iso_alpha2,
three_letter_iso_country_code: location.iso_alpha3,
}
}
}
-2
View File
@@ -3,11 +3,9 @@
use std::time::Duration;
pub(crate) mod helpers;
pub(crate) mod http;
pub(crate) mod location;
pub(crate) mod models;
pub(crate) mod utils;
pub(crate) const CACHE_REFRESH_RATE: Duration = Duration::from_secs(30);
pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(60);
+4
View File
@@ -7,6 +7,7 @@ use mixnet_contract_common::{IdentityKeyRef, NodeId};
use serde::{Deserialize, Serialize};
use crate::client::ThreadsafeValidatorClient;
use crate::geo_ip::location::ThreadsafeGeoIp;
use validator_client::models::MixNodeBondAnnotated;
use crate::country_statistics::country_nodes_distribution::{
@@ -30,6 +31,7 @@ pub struct ExplorerApiState {
pub(crate) mixnodes: ThreadsafeMixNodesCache,
pub(crate) ping: ThreadsafePingCache,
pub(crate) validators: ThreadsafeValidatorCache,
pub(crate) geo_ip: ThreadsafeGeoIp,
// TODO: discuss with @MS whether this is an appropriate spot for it
pub(crate) validator_client: ThreadsafeValidatorClient,
@@ -87,6 +89,7 @@ impl ExplorerApiStateContext {
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
geo_ip: ThreadsafeGeoIp::new(),
}
} else {
warn!(
@@ -102,6 +105,7 @@ impl ExplorerApiStateContext {
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
geo_ip: ThreadsafeGeoIp::new(),
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
// 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 { VALIDATOR_URL } = process.env;
export const VALIDATOR_BASE_URL = process.env.VALIDATOR_URL;
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
// specific API routes
@@ -10,7 +10,7 @@ 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 VALIDATORS_API = `${VALIDATOR_URL}/validators`;
export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`;
export const BLOCK_API = `${VALIDATOR_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.
+16 -7
View File
@@ -60,6 +60,7 @@ export const Filters = () => {
const [showFilters, setShowFilters] = useState(false);
const [isFiltered, setIsFiltered] = useState(false);
const [filters, setFilters] = React.useState<TFilters>();
const [upperSaturationValue, setUpperSaturationValue] = React.useState<number>(100);
const baseFilters = useRef<TFilters>();
const prevFilters = useRef<TFilters>();
@@ -67,11 +68,10 @@ export const Filters = () => {
const handleToggleShowFilters = () => setShowFilters(!showFilters);
const initialiseFilters = useCallback(async () => {
let upperSaturationValue;
const allMixnodes = await Api.fetchMixnodes();
if (allMixnodes) {
upperSaturationValue = Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1);
const initFilters = generateFilterSchema(upperSaturationValue);
setUpperSaturationValue(Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1));
const initFilters = generateFilterSchema();
baseFilters.current = initFilters;
prevFilters.current = initFilters;
setFilters(initFilters);
@@ -79,8 +79,18 @@ export const Filters = () => {
}, []);
const handleOnChange = (id: EnumFilterKey, newValue: number[]) => {
if (id === 'stakeSaturation' && newValue[1] === 100) {
newValue.splice(1, 1, upperSaturationValue);
}
setFilters((ftrs) => {
if (ftrs) return { ...ftrs, [id]: { ...ftrs[id], value: newValue } };
if (ftrs)
return {
...ftrs,
[id]: {
...ftrs[id],
value: newValue,
},
};
return undefined;
});
};
@@ -142,11 +152,10 @@ export const Filters = () => {
</Snackbar>
<Button
size="large"
variant="text"
color="inherit"
variant="contained"
endIcon={<Tune />}
onClick={handleToggleShowFilters}
sx={{ textTransform: 'none' }}
sx={{ textTransform: 'none', width: isMobile ? '100%' : 'inherit' }}
>
Advanced filters
</Button>
@@ -1,6 +1,6 @@
import { EnumFilterKey, TFilters } from '../../typeDefs/filters';
export const generateFilterSchema = (upperSaturationValue?: number) => ({
export const generateFilterSchema = () => ({
profitMargin: {
label: 'Profit margin (%)',
id: EnumFilterKey.profitMargin,
@@ -25,13 +25,12 @@ export const generateFilterSchema = (upperSaturationValue?: number) => ({
stakeSaturation: {
label: 'Stake saturation (%)',
id: EnumFilterKey.stakeSaturation,
value: [0, upperSaturationValue || 100],
value: [0, 100],
isSmooth: true,
marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, upperSaturationValue || 100].map((value) => ({
marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) => ({
value: value < 100 ? value : 100,
label: value < 100 ? value : '>100',
})),
max: 100,
tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards",
},
routingScore: {
@@ -5,10 +5,16 @@ import { EconomicsInfoRowWithIndex } from './types';
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => {
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (inclusionProbability) {
case 'VeryLow':
return 'Very Low';
case 'VeryHigh':
return 'Very High';
case 'High':
case 'Good':
case 'Low':
case 'Moderate':
return inclusionProbability;
default:
return '-';
@@ -19,12 +19,16 @@ const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
return theme.palette.warning.main;
}
if (field === 'selectionChance') {
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (fieldValue) {
case 'High':
case 'VeryHigh':
return theme.palette.nym.networkExplorer.selectionChance.overModerate;
case 'Good':
case 'Moderate':
return theme.palette.nym.networkExplorer.selectionChance.moderate;
case 'Low':
case 'VeryLow':
return theme.palette.nym.networkExplorer.selectionChance.underModerate;
default:
return theme.palette.nym.wallet.fee;
@@ -46,7 +46,7 @@ export const MixNodeStatusDropdown: React.FC<MixNodeStatusDropdownProps> = ({ st
}
}}
sx={{
width: isMobile ? 'auto' : 200,
width: isMobile ? '50%' : 200,
...sx,
}}
>
+39 -28
View File
@@ -30,39 +30,40 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
width: '100%',
marginBottom: 2,
display: 'flex',
flexDirection: isMobile ? 'column-reverse' : 'row',
flexDirection: isMobile ? 'column' : 'row',
justifyContent: 'space-between',
}}
>
<Box sx={{ display: 'flex', alignItems: 'middle' }}>
{childrenBefore}
<Select
labelId="simple-select-label"
id="simple-select"
value={pageSize}
onChange={onChangePageSize}
sx={{
width: isMobile ? 100 : 200,
}}
>
<MenuItem value={10} data-testid="ten">
10
</MenuItem>
<MenuItem value={30} data-testid="thirty">
30
</MenuItem>
<MenuItem value={50} data-testid="fifty">
50
</MenuItem>
<MenuItem value={100} data-testid="hundred">
100
</MenuItem>
</Select>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', alignItems: 'middle' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
{childrenBefore}
<Select
labelId="simple-select-label"
id="simple-select"
value={pageSize}
onChange={onChangePageSize}
sx={{
width: isMobile ? '50%' : 200,
marginRight: isMobile ? 0 : 2,
}}
>
<MenuItem value={10} data-testid="ten">
10
</MenuItem>
<MenuItem value={30} data-testid="thirty">
30
</MenuItem>
<MenuItem value={50} data-testid="fifty">
50
</MenuItem>
<MenuItem value={100} data-testid="hundred">
100
</MenuItem>
</Select>
</Box>
<TextField
sx={{
width: isMobile ? '100%' : 350,
width: isMobile ? '100%' : 200,
marginBottom: isMobile ? 2 : 0,
}}
value={searchTerm}
@@ -70,6 +71,16 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
placeholder="search"
onChange={(event) => onChangeSearch(event.target.value)}
/>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'end',
gap: 1,
marginTop: isMobile ? 2 : 0,
}}
>
{withFilters && <Filters />}
{childrenAfter}
</Box>
+2 -1
View File
@@ -215,7 +215,8 @@ export type UptimeStoryResponse = {
export type MixNodeEconomicDynamicsStatsResponse = {
stake_saturation: number;
active_set_inclusion_probability: 'High' | 'Good' | 'Low';
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
active_set_inclusion_probability: 'High' | 'Good' | 'Low' | 'VeryLow' | 'Moderate' | 'VeryHigh';
reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
estimated_total_node_reward: number;
estimated_operator_reward: number;
+2 -1
View File
@@ -19,7 +19,7 @@ anyhow = "1.0.53"
async-trait = { version = "0.1.51" }
bip39 = "1.0.1"
bs58 = "0.4.0"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
colored = "2.0"
dashmap = "4.0"
dirs = "4.0"
@@ -55,6 +55,7 @@ coconut-interface = { path = "../common/coconut-interface", optional = true }
credentials = { path = "../common/credentials" }
config = { path = "../common/config" }
crypto = { path = "../common/crypto" }
completions = { path = "../common/completions" }
gateway-requests = { path = "gateway-requests" }
mixnet-client = { path = "../common/client-libs/mixnet-client" }
mixnode-common = { path = "../common/mixnode-common" }
+26 -11
View File
@@ -4,10 +4,13 @@
use std::{process, str::FromStr};
use crate::{config::Config, Cli};
use clap::CommandFactory;
use clap::Subcommand;
use colored::Colorize;
use completions::{fig_generate, ArgShell};
use config::parse_validators;
use crypto::bech32_address_validation;
use network_defaults::mainnet::read_var_if_not_default;
use network_defaults::var_names::{
API_VALIDATOR, BECH32_PREFIX, CONFIGURED, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS,
};
@@ -34,6 +37,12 @@ pub(crate) enum Commands {
/// Try to upgrade the gateway
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -55,12 +64,16 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: Cli) {
let bin_name = "nym-gateway";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::NodeDetails(m) => node_details::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Sign(m) => sign::execute(m),
Commands::Upgrade(m) => upgrade::execute(m).await,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
}
}
@@ -97,27 +110,29 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
.expect("the provided statistics service url is invalid!"),
);
} else if std::env::var(CONFIGURED).is_ok() {
let raw_url = std::env::var(STATISTICS_SERVICE_DOMAIN_ADDRESS)
.expect("statistics service url not set");
config = config.with_custom_statistics_service_url(
raw_url
.parse()
.expect("the provided statistics service url is invalid"),
)
if let Some(raw_url) = read_var_if_not_default(STATISTICS_SERVICE_DOMAIN_ADDRESS) {
config = config.with_custom_statistics_service_url(
raw_url
.parse()
.expect("the provided statistics service url is invalid"),
);
}
}
if let Some(raw_validators) = args.validator_apis {
config = config.with_custom_validator_apis(parse_validators(&raw_validators));
} else if std::env::var(CONFIGURED).is_ok() {
let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set");
config = config.with_custom_validator_apis(parse_validators(&raw_validators))
if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) {
config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators))
}
}
if let Some(ref raw_validators) = args.validators {
config = config.with_custom_validator_nymd(parse_validators(raw_validators));
} else if std::env::var(CONFIGURED).is_ok() {
let raw_validators = std::env::var(NYMD_VALIDATOR).expect("nymd validator not set");
config = config.with_custom_validator_nymd(parse_validators(&raw_validators))
if let Some(raw_validators) = read_var_if_not_default(NYMD_VALIDATOR) {
config = config.with_custom_validator_nymd(::config::parse_validators(&raw_validators))
}
}
if let Some(wallet_address) = args.wallet_address {
+2 -1
View File
@@ -18,7 +18,7 @@ rust-version = "1.58.1"
[dependencies]
anyhow = "1.0.40"
bs58 = "0.4.0"
clap = { version = "3.0.10", features = ["cargo", "derive"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
colored = "2.0"
cupid = "0.6.1"
dirs = "4.0"
@@ -40,6 +40,7 @@ url = { version = "2.2", features = ["serde"] }
## internal
config = { path="../common/config" }
crypto = { path="../common/crypto" }
completions = { path="../common/completions" }
mixnet-client = { path="../common/client-libs/mixnet-client" }
mixnode-common = { path="../common/mixnode-common" }
nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" }
+16 -2
View File
@@ -4,8 +4,11 @@
use std::process;
use crate::{config::Config, Cli};
use clap::CommandFactory;
use clap::Subcommand;
use colored::Colorize;
use completions::{fig_generate, ArgShell};
use config::defaults::mainnet::read_var_if_not_default;
use config::{
defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED},
parse_validators,
@@ -38,6 +41,12 @@ pub(crate) enum Commands {
/// Show details of this mixnode
NodeDetails(node_details::NodeDetails),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -53,6 +62,8 @@ struct OverrideConfig {
}
pub(crate) async fn execute(args: Cli) {
let bin_name = "nym-mixnode";
match &args.command {
Commands::Describe(m) => describe::execute(m),
Commands::Init(m) => init::execute(m),
@@ -60,6 +71,8 @@ pub(crate) async fn execute(args: Cli) {
Commands::Sign(m) => sign::execute(m),
Commands::Upgrade(m) => upgrade::execute(m),
Commands::NodeDetails(m) => node_details::execute(m),
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
}
}
@@ -85,8 +98,9 @@ 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));
} else if std::env::var(CONFIGURED).is_ok() {
let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set");
config = config.with_custom_validator_apis(parse_validators(&raw_validators))
if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) {
config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators))
}
}
if let Some(ref announce_host) = args.announce_host {
+29
View File
@@ -571,6 +571,25 @@ dependencies = [
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_fig"
version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9"
dependencies = [
"clap",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "3.2.18"
@@ -708,6 +727,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "completions"
version = "0.1.0"
dependencies = [
"clap",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -3347,6 +3375,7 @@ version = "1.0.2"
dependencies = [
"clap",
"client-core",
"completions",
"config",
"credential-storage",
"crypto",
+2
View File
@@ -2785,7 +2785,9 @@ dependencies = [
"config",
"cosmrs",
"cosmwasm-std",
"hex-literal",
"mixnet-contract-common",
"network-defaults",
"nym-types",
"serde",
"serde_json",
@@ -11,7 +11,7 @@ anyhow = "1.0"
argon2 = "0.4"
base64 = "0.13"
bip39 = "1.0"
clap = { version = "3.2.20", features = ["derive"] }
clap = { version = "3.2", features = ["derive"] }
log = "0.4"
pretty_env_logger = "0.4"
serde_json = "1.0.0"
+2
View File
@@ -5,6 +5,7 @@ edition = "2021"
rust-version = "1.58"
[dependencies]
hex-literal = "0.3.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
@@ -14,6 +15,7 @@ cosmwasm-std = "1.0.0-beta8"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
config = { path = "../../common/config" }
network-defaults = { path = "../../common/network-defaults" }
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
+133 -16
View File
@@ -1,11 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::all::Network as ConfigNetwork;
use config::defaults::{mainnet, qa, sandbox, DenomDetails};
use nym_types::currency::DecCoin;
use config::defaults::{mainnet, DenomDetails, NymNetworkDetails};
use nym_types::{currency::DecCoin, error::TypesError};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{fmt, ops::Not, str::FromStr};
use strum::EnumIter;
#[allow(clippy::upper_case_acronyms)]
@@ -67,23 +66,141 @@ impl fmt::Display for Network {
}
}
impl From<ConfigNetwork> for Network {
fn from(network: ConfigNetwork) -> Self {
impl From<Network> for NymNetworkDetails {
fn from(network: Network) -> Self {
match network {
ConfigNetwork::QA => Network::QA,
ConfigNetwork::SANDBOX => Network::SANDBOX,
ConfigNetwork::MAINNET => Network::MAINNET,
ConfigNetwork::CUSTOM { .. } => panic!("custom network is not supported"),
Network::QA => qa::network_details(),
Network::SANDBOX => sandbox::network_details(),
Network::MAINNET => NymNetworkDetails::new_mainnet(),
}
}
}
impl From<Network> for ConfigNetwork {
fn from(network: Network) -> Self {
match network {
Network::QA => ConfigNetwork::QA,
Network::SANDBOX => ConfigNetwork::SANDBOX,
Network::MAINNET => ConfigNetwork::MAINNET,
impl FromStr for Network {
type Err = TypesError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qa" => Ok(Network::QA),
"sandbox" => Ok(Network::SANDBOX),
"mainnet" => Ok(Network::MAINNET),
_ => Err(TypesError::UnknownNetwork(s.to_string())),
}
}
}
fn parse_optional_str(raw: &str) -> Option<String> {
raw.is_empty().not().then(|| raw.into())
}
mod sandbox {
use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails};
use super::parse_optional_str;
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6);
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str =
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
//pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str =
//"nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
pub(crate) fn network_details() -> network_defaults::NymNetworkDetails {
network_defaults::NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: BECH32_PREFIX.to_string(),
mix_denom: MIX_DENOM.into(),
stake_denom: STAKE_DENOM.into(),
},
endpoints: validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
},
}
}
}
mod qa {
use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails};
use super::parse_optional_str;
pub(crate) const BECH32_PREFIX: &str = "n";
pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
//pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
Some("https://qa-validator-api.nymtech.net/api"),
)]
}
pub(crate) fn network_details() -> network_defaults::NymNetworkDetails {
network_defaults::NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: BECH32_PREFIX.to_string(),
mix_denom: MIX_DENOM.into(),
stake_denom: STAKE_DENOM.into(),
},
endpoints: validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
},
}
}
}
-1
View File
@@ -46,7 +46,6 @@
"react-error-boundary": "^3.1.3",
"react-hook-form": "^7.14.2",
"react-router-dom": "6",
"react-to-print": "^2.14.7",
"semver": "^6.3.0",
"string-to-color": "^2.2.2",
"use-clipboard-copy": "^0.2.0",
+90 -7
View File
@@ -13,8 +13,7 @@ use strum::IntoEnumIterator;
use url::Url;
use validator_client::nymd::AccountId as CosmosAccountId;
use config::defaults::all::Network;
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
use config::defaults::{DenomDetailsOwned, NymNetworkDetails, ValidatorDetails};
use nym_wallet_types::network::Network as WalletNetwork;
use nym_wallet_types::network_config;
@@ -138,7 +137,7 @@ impl Config {
// One file per network
for (network, config) in &self.networks {
let network = match Network::from_str(network).map(Into::into) {
let network = match WalletNetwork::from_str(network) {
Ok(network) => network,
Err(err) => {
log::warn!("Unexpected name for network configuration, not saving: {err}");
@@ -198,7 +197,7 @@ impl Config {
&self,
network: WalletNetwork,
) -> impl Iterator<Item = ValidatorConfigEntry> + '_ {
self.base.networks.validators(network.into()).map(|v| {
self.base.networks.validators(&network).map(|v| {
v.clone()
.try_into()
.expect("The hardcoded validators are assumed to be valid urls")
@@ -218,7 +217,7 @@ impl Config {
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.mixnet_contract_address(network.into())
.mixnet_contract_address(&network)
.expect("No mixnet contract address found in config")
.parse()
.expect("Wrong format for mixnet contract address")
@@ -227,7 +226,7 @@ impl Config {
pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.vesting_contract_address(network.into())
.vesting_contract_address(&network)
.expect("No vesting contract address found in config")
.parse()
.expect("Wrong format for vesting contract address")
@@ -236,7 +235,7 @@ impl Config {
pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.bandwidth_claim_contract_address(network.into())
.bandwidth_claim_contract_address(&network)
.expect("No bandwidth claim contract address found in config")
.parse()
.expect("Wrong format for bandwidth claim contract address")
@@ -421,6 +420,90 @@ impl fmt::Display for OptionalValidators {
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
struct SupportedNetworks {
networks: HashMap<WalletNetwork, NetworkDetails>,
}
impl SupportedNetworks {
fn new(support: Vec<WalletNetwork>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| {
let details = NetworkDetails::from(NymNetworkDetails::from(n));
(n, details)
})
.collect(),
}
}
fn mixnet_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.mixnet_contract_address.as_str())
}
fn vesting_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.vesting_contract_address.as_str())
}
fn bandwidth_claim_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.bandwidth_claim_contract_address.as_str())
}
fn validators(&self, network: &WalletNetwork) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
// Simplified variant of NymNetworkDetails for serialization to config file
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
struct NetworkDetails {
bech32_prefix: String,
mix_denom: DenomDetailsOwned,
stake_denom: DenomDetailsOwned,
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
// Possibly a bit naff, but WalletNetwork is converted into the more general NymNetworkDetails, which here
// is converted to the format specific for serialization to config
impl From<NymNetworkDetails> for NetworkDetails {
fn from(details: NymNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.chain_details.bech32_account_prefix,
mix_denom: details.chain_details.mix_denom,
stake_denom: details.chain_details.stake_denom,
mixnet_contract_address: details
.contracts
.mixnet_contract_address
.unwrap_or_default(),
vesting_contract_address: details
.contracts
.vesting_contract_address
.unwrap_or_default(),
bandwidth_claim_contract_address: details
.contracts
.bandwidth_claim_contract_address
.unwrap_or_default(),
statistics_service_url: "".to_string(),
validators: details.endpoints,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
-1
View File
@@ -97,7 +97,6 @@ fn main() {
validator_api::status::mixnode_core_node_status,
validator_api::status::mixnode_inclusion_probability,
validator_api::status::mixnode_reward_estimation,
validator_api::status::compute_mixnode_reward_estimation,
validator_api::status::mixnode_stake_saturation,
validator_api::status::mixnode_status,
vesting::rewards::vesting_claim_delegator_reward,
@@ -4,8 +4,7 @@ use crate::network_config;
use crate::state::{WalletAccountIds, WalletState};
use crate::wallet_storage::{self, DEFAULT_LOGIN_ID};
use bip39::{Language, Mnemonic};
use config::defaults::all::Network;
use config::defaults::COSMOS_DERIVATION_PATH;
use config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
use cosmrs::bip32::DerivationPath;
use itertools::Itertools;
use nym_types::account::{Account, AccountEntry, Balance};
@@ -175,8 +174,8 @@ async fn run_connection_test(
untested_api_urls: HashMap<WalletNetwork, Vec<Url>>,
config: &Config,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
) {
let mixnet_contract_address = WalletNetwork::iter()
.map(|network| (network.into(), config.get_mixnet_contract_address(network)))
@@ -199,8 +198,8 @@ async fn run_connection_test(
}
fn create_clients(
nymd_urls: &HashMap<Network, Vec<(Url, bool)>>,
api_urls: &HashMap<Network, Vec<(Url, bool)>>,
nymd_urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
api_urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
default_nymd_urls: &HashMap<WalletNetwork, Url>,
default_api_urls: &HashMap<WalletNetwork, Url>,
config: &Config,
@@ -239,8 +238,8 @@ fn create_clients(
log::info!("Connecting to: nymd_url: {nymd_url} for {network}");
log::info!("Connecting to: api_url: {api_url} for {network}");
let network_details = Network::from(network)
.details()
let network_details = NymNetworkDetails::from(network)
.clone()
.with_mixnet_contract(Some(config.get_mixnet_contract_address(network).as_ref()))
.with_vesting_contract(Some(config.get_vesting_contract_address(network).as_ref()))
.with_bandwidth_claim_contract(Some(
@@ -260,7 +259,7 @@ fn create_clients(
}
fn select_random_responding_url(
urls: &HashMap<Network, Vec<(Url, bool)>>,
urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
network: WalletNetwork,
) -> Option<Url> {
urls.get(&network.into()).and_then(|urls| {
@@ -273,7 +272,7 @@ fn select_random_responding_url(
}
fn select_first_responding_url(
urls: &HashMap<Network, Vec<(Url, bool)>>,
urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
network: WalletNetwork,
//config: &Config,
) -> Option<Url> {
@@ -426,8 +425,8 @@ pub async fn add_account_for_password(
let address = {
let state = state.read().await;
let network: Network = state.current_network().into();
derive_address(mnemonic, &network.bech32_prefix())?.to_string()
let network: NymNetworkDetails = state.current_network().into();
derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string()
};
// Re-read all the acccounts from the wallet to reset the state, rather than updating it
@@ -466,10 +465,14 @@ async fn set_state_with_all_accounts(
let mnemonic = account.mnemonic();
let addresses: HashMap<WalletNetwork, cosmrs::AccountId> = WalletNetwork::iter()
.map(|network| {
let config_network: Network = network.into();
let config_network: NymNetworkDetails = network.into();
(
network,
derive_address(mnemonic.clone(), &config_network.bech32_prefix()).unwrap(),
derive_address(
mnemonic.clone(),
&config_network.chain_details.bech32_account_prefix,
)
.unwrap(),
)
})
.collect();
@@ -4,11 +4,10 @@
use crate::api_client;
use crate::error::BackendError;
use crate::state::WalletState;
use mixnet_contract_common::{reward_params::Performance, IdentityKeyRef, NodeId};
use mixnet_contract_common::{IdentityKeyRef, NodeId};
use validator_client::models::{
ComputeRewardEstParam, GatewayCoreStatusResponse, InclusionProbabilityResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse,
};
#[tauri::command]
@@ -51,26 +50,6 @@ pub async fn mixnode_reward_estimation(
.await?)
}
#[tauri::command]
pub async fn compute_mixnode_reward_estimation(
identity: &str,
performance: Option<Performance>,
active_in_rewarded_set: Option<bool>,
pledge_amount: Option<u64>,
total_delegation: Option<u64>,
state: tauri::State<'_, WalletState>,
) -> Result<RewardEstimationResponse, BackendError> {
let request_body = ComputeRewardEstParam {
performance,
active_in_rewarded_set,
pledge_amount,
total_delegation,
};
Ok(api_client!(state)
.compute_mixnode_reward_estimation(identity, &request_body)
.await?)
}
#[tauri::command]
pub async fn mixnode_stake_saturation(
mix_id: NodeId,
+3 -1
View File
@@ -1,6 +1,7 @@
use crate::config;
use crate::error::BackendError;
use crate::simulate::SimulateResult;
use ::config::defaults::NymNetworkDetails;
use cosmwasm_std::Decimal;
use itertools::Itertools;
use log::warn;
@@ -215,8 +216,9 @@ impl WalletStateInner {
}
pub fn register_default_denoms(&mut self, network: Network) {
let details = NymNetworkDetails::from(network);
self.registered_coins
.insert(network, RegisteredCoins::default_denoms(network.into()));
.insert(network, RegisteredCoins::default_denoms(&details));
}
pub fn set_network(&mut self, network: Network) {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

@@ -1,6 +1,7 @@
import React from 'react';
import { Alert, Box, Paper, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material';
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
const passwordCreationSteps = [
'Log out',
@@ -10,34 +11,46 @@ const passwordCreationSteps = [
'Now you can create multiple accounts',
];
export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => (
<Dialog open={show} onClose={handleClose} fullWidth PaperComponent={Paper} PaperProps={{ elevation: 0 }}>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Multi accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography variant="body1" sx={{ color: (theme) => theme.palette.nym.text.muted }}>
How to set up multiple accounts
</Typography>
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Alert
severity="warning"
icon={false}
sx={(t) => (t.palette.mode === 'dark' ? { bgcolor: (theme) => theme.palette.background.paper } : {})}
>
<Typography>In order to create multiple accounts your wallet needs a password.</Typography>
<Typography>Follow steps below to create password.</Typography>
</Alert>
<Typography>How to create a password for your account</Typography>
{passwordCreationSteps.map((step, index) => (
<Typography key={step}>{`${index + 1}. ${step}`}</Typography>
))}
</Stack>
</DialogContent>
</Dialog>
);
export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => {
const theme = useTheme();
return (
<Dialog
open={show}
onClose={handleClose}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
>
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Multi accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography variant="body1" sx={{ color: (theme) => theme.palette.nym.text.muted }}>
How to set up multiple accounts
</Typography>
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Alert
severity="warning"
icon={false}
sx={(t) => (t.palette.mode === 'dark' ? { bgcolor: (theme) => theme.palette.background.paper } : {})}
>
<Typography>In order to create multiple accounts your wallet needs a password.</Typography>
<Typography>Follow steps below to create password.</Typography>
</Alert>
<Typography>How to create a password for your account</Typography>
{passwordCreationSteps.map((step, index) => (
<Typography key={step}>{`${index + 1}. ${step}`}</Typography>
))}
</Stack>
</DialogContent>
</Paper>
</Dialog>
);
};
@@ -12,6 +12,7 @@ import {
Divider,
} from '@mui/material';
import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { AccountsContext } from 'src/context';
import { AccountItem } from '../AccountItem';
import { ConfirmPasswordModal } from './ConfirmPasswordModal';
@@ -21,6 +22,8 @@ export const AccountsModal = () => {
useContext(AccountsContext);
const [accountToSwitchTo, setAccountToSwitchTo] = useState<string>();
const theme = useTheme();
const handleClose = () => {
setDialogToDisplay(undefined);
setError(undefined);
@@ -47,48 +50,51 @@ export const AccountsModal = () => {
open={dialogToDisplay === 'Accounts'}
onClose={handleClose}
fullWidth
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Switch between accounts
</Typography>
</DialogTitle>
<DialogContent sx={{ padding: 0 }}>
{accounts?.map(({ id, address }) => (
<AccountItem
name={id}
address={address}
key={address}
onSelectAccount={() => {
if (selectedAccount?.id !== id) {
setAccountToSwitchTo(id);
}
}}
/>
))}
</DialogContent>
<Divider variant="middle" sx={{ mt: 3 }} />
<DialogActions sx={{ p: 3 }}>
<Button startIcon={<ArrowDownwardSharp />} onClick={() => setDialogToDisplay('Import')}>
Import account
</Button>
<Button
disableElevation
variant="contained"
startIcon={<Add fontSize="small" />}
onClick={() => setDialogToDisplay('Add')}
>
Add new account
</Button>
</DialogActions>
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Switch between accounts
</Typography>
</DialogTitle>
<DialogContent sx={{ padding: 0 }}>
{accounts?.map(({ id, address }) => (
<AccountItem
name={id}
address={address}
key={address}
onSelectAccount={() => {
if (selectedAccount?.id !== id) {
setAccountToSwitchTo(id);
}
}}
/>
))}
</DialogContent>
<Divider variant="middle" sx={{ mt: 3 }} />
<DialogActions sx={{ p: 3 }}>
<Button startIcon={<ArrowDownwardSharp />} onClick={() => setDialogToDisplay('Import')}>
Import account
</Button>
<Button
disableElevation
variant="contained"
startIcon={<Add fontSize="small" />}
onClick={() => setDialogToDisplay('Add')}
>
Add new account
</Button>
</DialogActions>
</Paper>
</Dialog>
);
};
@@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import { Box, Paper, Dialog, DialogTitle, IconButton, Typography } from '@mui/material';
import { ArrowBack } from '@mui/icons-material';
import { Paper, Dialog, DialogTitle, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { ConfirmPassword } from 'src/components/ConfirmPassword';
import { AccountsContext } from 'src/context';
@@ -14,28 +14,32 @@ export const ConfirmPasswordModal = ({
onConfirm: (password: string) => Promise<void>;
}) => {
const { isLoading, error } = useContext(AccountsContext);
const theme = useTheme();
return (
<Dialog
open={Boolean(accountName)}
onClose={onClose}
fullWidth
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
>
<DialogTitle>
<Typography variant="h6">Switch account</Typography>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
</DialogTitle>
<ConfirmPassword
onConfirm={onConfirm}
error={error}
isLoading={isLoading}
buttonTitle="Switch account"
onCancel={onClose}
/>
<Paper>
<DialogTitle>
<Typography variant="h6">Switch account</Typography>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
</DialogTitle>
<ConfirmPassword
onConfirm={onConfirm}
error={error}
isLoading={isLoading}
buttonTitle="Switch account"
onCancel={onClose}
/>
</Paper>
</Dialog>
);
};
@@ -12,6 +12,7 @@ import {
Typography,
} from '@mui/material';
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { AccountsContext } from 'src/context';
export const EditAccountModal = () => {
@@ -19,6 +20,8 @@ export const EditAccountModal = () => {
const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext);
const theme = useTheme();
useEffect(() => {
setAccountName(accountToEdit ? accountToEdit?.id : '');
}, [accountToEdit]);
@@ -28,48 +31,51 @@ export const EditAccountModal = () => {
open={dialogToDisplay === 'Edit'}
onClose={() => setDialogToDisplay('Accounts')}
fullWidth
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Edit account name</Typography>
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
New wallet address
</Typography>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ px: 3, mt: 1 }}>
<TextField
label="Account name"
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Edit account name</Typography>
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
New wallet address
</Typography>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ px: 3, mt: 1 }}>
<TextField
label="Account name"
fullWidth
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
autoFocus
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3 }}>
<Button
fullWidth
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
autoFocus
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3 }}>
<Button
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => {
if (accountToEdit) {
handleEditAccount({ ...accountToEdit, id: accountName });
setDialogToDisplay('Accounts');
}
}}
disabled={!accountName?.length}
>
Edit
</Button>
</DialogActions>
disableElevation
variant="contained"
size="large"
onClick={() => {
if (accountToEdit) {
handleEditAccount({ ...accountToEdit, id: accountName });
setDialogToDisplay('Accounts');
}
}}
disabled={!accountName?.length}
>
Edit
</Button>
</DialogActions>
</Paper>
</Dialog>
);
};
+1 -1
View File
@@ -15,7 +15,7 @@ export const AppBar = () => {
const navigate = useNavigate();
return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', mt: 3 }}>
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none' }}>
<Toolbar disableGutters>
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
<Grid item container alignItems="center" spacing={1}>
+2 -2
View File
@@ -14,11 +14,11 @@ export const Bond = ({
<Box
sx={{
display: 'flex',
alignItems: 'center',
alignItems: 'flex-end',
justifyContent: 'space-between',
}}
>
<Typography>Bond a mixnode or a gateway</Typography>
<Typography variant="body2">Bond a mixnode or a gateway</Typography>
<Box
sx={{
display: 'flex',
@@ -1,5 +1,4 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { TBondedMixnode, urls } from 'src/context';
@@ -56,11 +55,8 @@ export const BondedMixnode = ({
network?: Network;
onActionSelect: (action: TBondedMixnodeActions) => void;
}) => {
const navigate = useNavigate();
const { name, stake, bond, stakeSaturation, profitMargin, operatorRewards, delegators, status, identityKey } =
mixnode;
const cells: Cell[] = [
{
cell: `${stake.amount} ${stake.denom}`,
@@ -121,7 +117,7 @@ export const BondedMixnode = ({
<Button
variant="text"
color="secondary"
onClick={() => navigate('/bonding/node-settings')}
onClick={() => onActionSelect('nodeSettings')}
startIcon={<NodeIcon />}
>
Settings
@@ -157,7 +157,7 @@ const AmountFormData = ({
return (
<Stack gap={3}>
<Box display="flex" gap={3} justifyContent="center" sx={{ mt: 2 }}>
<Box display="flex" gap={3} justifyContent="center">
{hasVestingTokens && <TokenPoolSelector disabled={false} onSelect={(pool) => setValue('tokenPool', pool)} />}
<CurrencyFormField
required
@@ -205,7 +205,7 @@ export const BondMixnodeForm = ({
<>
{step === 1 && (
<>
<Box sx={{ mb: 2 }}>
<Box sx={{ mb: 3 }}>
<NodeTypeSelector disabled={false} setNodeType={onSelectNodeType} nodeType="mixnode" />
</Box>
<NodeFormData onNext={onValidateMixnodeData} mixnodeData={mixnodeData} />
@@ -152,18 +152,16 @@ export const BondMixnodeModal = ({
subHeader={`Step ${step}/2`}
okLabel="Next"
>
<Box sx={{ mb: 2 }}>
<BondMixnodeForm
step={step}
denom={denom}
mixnodeData={mixnodeData}
amountData={amountData}
hasVestingTokens={hasVestingTokens}
onValidateMixnodeData={handleUpdateMixnodeData}
onValidateAmountData={handleUpdateAmountData}
onSelectNodeType={onSelectNodeType}
/>
</Box>
<BondMixnodeForm
step={step}
denom={denom}
mixnodeData={mixnodeData}
amountData={amountData}
hasVestingTokens={hasVestingTokens}
onValidateMixnodeData={handleUpdateMixnodeData}
onValidateAmountData={handleUpdateAmountData}
onSelectNodeType={onSelectNodeType}
/>
</SimpleModal>
);
};
@@ -12,7 +12,6 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { FeeDetails } from '@nymproject/types';
//Now we are using the node setting page instead of this modal
export const NodeSettings = ({
currentPm,
isVesting,
@@ -26,7 +25,7 @@ export const NodeSettings = ({
onClose: () => void;
onError: (err: string) => void;
}) => {
const [pm, setPm] = useState(currentPm?.toString());
const [pm, setPm] = useState(currentPm.toString());
const [error, setError] = useState(false);
const { fee, getFee, resetFeeState, isFeeLoading, feeError } = useGetFee();
@@ -112,7 +111,7 @@ export const NodeSettings = ({
Set profit margin
</Typography>
<Box sx={{ mb: 3 }}>
<TextField placeholder="Profit margin" value={pm} onChange={(e) => setPm(e.target.value)} fullWidth />
<TextField label="Profit margin" value={pm} onChange={(e) => setPm(e.target.value)} fullWidth />
{error && (
<FormHelperText sx={{ color: 'error.main' }}>
Profit margin should be a number between 0 and 100
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { Typography, Alert, TextField } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import * as React from 'react';
import { Box, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import { TBondedGateway, TBondedMixnode } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { isGateway, isMixnode } from 'src/types';
@@ -13,6 +13,8 @@ import {
simulateVestingUnbondGateway,
simulateVestingUnbondMixnode,
} from '../../../requests';
import { ConfirmationModal } from '../../Modals/ConfirmationModal';
import { Error } from '../../Error';
interface Props {
node: TBondedMixnode | TBondedGateway;
@@ -21,54 +23,12 @@ interface Props {
onError: (e: string) => void;
}
type TUnbondModalStep = 1 | 2 | 3;
const UnbondFirstStep = ({ handleVerification, onClose }: { handleVerification: any; onClose: () => void }) => {
const [verificationText, setVerificationText] = useState<string>('');
const theme = useTheme();
return (
<SimpleModal
open
header="Unbond"
okLabel="Unbond"
okDisabled={!verificationText.toUpperCase().match(/^UNBOND$/)}
onOk={handleVerification}
onClose={onClose}
>
<Typography sx={{ fontSize: 14, fontWeight: 600, mb: 3 }}>
If you unbond your node you will loose all your delegators!
</Typography>
<Alert
severity="error"
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
This action is irreversible and it will not be possible to restore the current state again
</Alert>
<Typography sx={{ my: 3, fontSize: 14 }}>
To unbond, type <span style={{ color: theme.palette.nym.highlight }}>UNBOND</span> in the field below and click
UNBOND button
</Typography>
<TextField
type="input"
value={verificationText}
onChange={(e) => {
setVerificationText(e.target.value);
}}
fullWidth
/>
</SimpleModal>
);
};
const UnbondSecondStep = ({ node, onConfirm, onClose, onError }: Props) => {
export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
const { fee, isFeeLoading, getFee, feeError } = useGetFee();
const [isConfirmed, setIsConfirmed] = useState(false);
const [showConfirmModal, setShowConfirmModal] = useState(true);
const [confirmField, setConfirmField] = useState('');
useEffect(() => {
if (feeError) {
onError(feeError);
@@ -93,42 +53,63 @@ const UnbondSecondStep = ({ node, onConfirm, onClose, onError }: Props) => {
}
}, [node]);
return (
<SimpleModal
open
header="Unbond"
subHeader="Unbond and remove your node from the mixnet"
okLabel="Unbond"
onOk={onConfirm}
onClose={onClose}
>
<ModalListItem label="Amount to unbond" value={`${node.bond.amount} ${node.bond.denom.toUpperCase()}`} divider />
{isMixnode(node) && (
if (showConfirmModal) {
return (
<ConfirmationModal
title="Unbond"
confirmButton="UNBOND"
open={showConfirmModal}
onConfirm={() => {
setIsConfirmed(true);
setShowConfirmModal(false);
}}
onClose={onClose}
disabled={confirmField !== 'UNBOND'}
>
<Typography fontWeight={600} mb={2}>
If you unbond your node you will loose all your delegators!
</Typography>
<Error message="This action is irreversible and it will not be possible to restore the current state again" />
<Typography mt={2} mb={2}>
To unbond, type{' '}
<Typography display="inline" component="span" sx={{ color: (t) => t.palette.nym.highlight }}>
UNBOND
</Typography>{' '}
in the field below and click UNBOND button
</Typography>
<TextField fullWidth value={confirmField} onChange={(e) => setConfirmField(e.target.value)} />
</ConfirmationModal>
);
}
if (isConfirmed) {
return (
<SimpleModal
open
header="Unbond"
subHeader="Unbond and remove your node from the mixnet"
okLabel="Unbond"
onOk={onConfirm}
onClose={onClose}
>
<ModalListItem
label="Operator rewards"
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
label="Amount to unbond"
value={`${node.bond.amount} ${node.bond.denom.toUpperCase()}`}
divider
/>
)}
<ModalFee isLoading={isFeeLoading} fee={fee} divider />
<Typography fontSize="small">Tokens will be transferred to the account you are logged in with now</Typography>
</SimpleModal>
);
};
export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
const [step, setStep] = useState<TUnbondModalStep>(1);
const handleVerification = () => {
setStep(2);
};
return (
<div>
{step === 1 && <UnbondFirstStep handleVerification={handleVerification} onClose={onClose} />}
{step === 2 && <UnbondSecondStep onClose={onClose} onConfirm={onConfirm} onError={onError} node={node} />}
</div>
);
{isMixnode(node) && (
<ModalListItem
label="Operator rewards"
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
)}
<ModalFee isLoading={isFeeLoading} fee={fee} divider />
<Typography fontSize="small">Tokens will be transferred to the account you are logged in with now</Typography>
</SimpleModal>
);
}
return <Box />;
};
@@ -10,9 +10,9 @@ export default {
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
<ConfirmTx {...args}>
<ModalListItem label="Transaction type:" value="Bond" divider />
<ModalListItem label="Current bond:" value="100 NYM" divider />
<ModalListItem label="Additional bond:" value="50 NYM" divider />
<ModalListItem label="Transaction type" value="Bond" divider />
<ModalListItem label="Current bond" value="100 NYM" divider />
<ModalListItem label="Additional bond" value="50 NYM" divider />
</ConfirmTx>
);
@@ -213,8 +213,8 @@ export const DelegateModal: React.FC<{
onPrev={resetFeeState}
onConfirm={handleOk}
>
<ModalListItem label="Node identity key:" value={identityKey} divider />
<ModalListItem label="Amount:" value={`${amount} ${denom.toUpperCase()}`} divider />
<ModalListItem label="Node identity key" value={identityKey} divider />
<ModalListItem label="Amount" value={`${amount} ${denom.toUpperCase()}`} divider />
</ConfirmTx>
);
}
@@ -267,7 +267,7 @@ export const DelegateModal: React.FC<{
>
{errorIdentityKey}
</Typography>
<Box display="flex" gap={3} alignItems="center" sx={{ mt: 3 }}>
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 3 }}>
{hasVestingContract && <TokenPoolSelector disabled={false} onSelect={(pool) => setTokenPool(pool)} />}
<CurrencyFormField
required
+18 -1
View File
@@ -18,7 +18,24 @@ export const Mnemonic = ({
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
</Typography>
</Warning>
<TextField multiline rows={3} value={mnemonic} fullWidth />
<TextField
label="Mnemonic"
type="input"
value={mnemonic}
multiline
autoFocus={false}
fullWidth
inputProps={{
style: {
height: '160px',
},
}}
sx={{
'input::-webkit-textfield-decoration-container': {
alignItems: 'start',
},
}}
/>
<Button
color="inherit"

Some files were not shown because too many files have changed in this diff Show More