Compare commits

..

202 Commits

Author SHA1 Message Date
Simon Wicky 00aa88b578 add replay detection to packet processing 2024-02-14 14:32:52 +01:00
Simon Wicky 6dddeaff1d add bloom filter based replay detection 2024-02-14 14:32:34 +01:00
Simon Wicky b2c545770b add replay tag for sphinx 2024-02-14 14:32:13 +01:00
Jędrzej Stuczyński a40cd73dec Merge pull request #4402 from nymtech/bugfix/post-ephemera-nym-api
fixed nym-api config template
2024-02-13 17:41:38 +00:00
Jędrzej Stuczyński d7255374de fixed nym-api config template 2024-02-13 17:41:07 +00:00
Jon Häggblad 3088b69711 Merge pull request #3503 from nymtech/jon/feat/test-rustls
Replace openssl with rustls
2024-02-13 08:55:47 +01:00
Jon Häggblad 412b7b9898 Remove sdk-version-bump from main workspace temporarily
In the upcoming cargo-edit version then the dependency on ureq is
dropped and also the implicit dependency on openssl
2024-02-13 08:29:39 +01:00
Jon Häggblad 30754a7a4a Switch tungstenite to rustls 2024-02-13 08:28:09 +01:00
Jon Häggblad e99b04f1c6 Remove explicit openssl dependency 2024-02-13 08:28:09 +01:00
Jon Häggblad 279fea9a0b Switch reqwest to rustls 2024-02-13 08:28:09 +01:00
Jon Häggblad c2aba223b8 Add openssl to cargo deny ban 2024-02-13 08:28:09 +01:00
Jędrzej Stuczyński 501f314266 Merge pull request #4356 from nymtech/chore/remove-ephemera
Chore/remove ephemera
2024-02-12 18:31:58 +00:00
Jędrzej Stuczyński 3ecd2af216 fixed test imports 2024-02-12 17:14:25 +00:00
Jon Häggblad cec05a99f4 Tweak packet rate log string 2024-02-12 13:05:30 +01:00
Jon Häggblad d487f4d98c Merge pull request #4389 from nymtech/jon/handle-multiple-ip-packet-in-ipr
Handle multiple IP packets in ip-packet-router
2024-02-12 12:39:40 +01:00
Jon Häggblad b9e9809938 Extract out handle_responses 2024-02-12 12:14:51 +01:00
Jędrzej Stuczyński 9b50188d7d Merge pull request #4391 from nymtech/chore/reexport-types
re-export cosmrs' cosmwasm types
2024-02-12 09:14:33 +00:00
Jon Häggblad 0e3dbece8b Fix unit test 2024-02-12 08:21:32 +01:00
Jędrzej Stuczyński 052f7649a8 re-export cosmrs' cosmwasm types 2024-02-11 18:57:32 +00:00
Jon Häggblad 3fde9e648f Add health request response 2024-02-10 23:53:03 +01:00
Jon Häggblad 0b37b9fb1c Add ping pong request response 2024-02-10 23:35:36 +01:00
Jon Häggblad e273bfc25e Add message for unrequested disconnect on the IPR 2024-02-10 23:27:07 +01:00
Jon Häggblad d2ef94f1bd Add buffer timeout to connect request 2024-02-10 23:13:50 +01:00
Jon Häggblad 92ab794294 Encode packets in connection handler 2024-02-10 23:07:45 +01:00
Jon Häggblad 3f0210d56a Handle incoming multi-ip packets in IPR 2024-02-10 22:40:21 +01:00
Jon Häggblad 9b53473bee Tweak retransmission log info (#4387) 2024-02-09 18:22:25 +01:00
Tommy Verrall 5fdae14cb9 Merge pull request #4385 from nymtech/bugfix/gateway-vk-caching-without-coconut
[bugfix] remove hard failure on dkg contract queries in case it doesn't exist
2024-02-09 18:11:05 +01:00
Jędrzej Stuczyński ccb4d7fd5e comment regarding removal of ephemera 2024-02-09 14:40:24 +00:00
Jędrzej Stuczyński a8e520d13b removed unused import 2024-02-09 14:38:43 +00:00
Jędrzej Stuczyński 148db2f350 replaced uses of 'serde_derive' with 'serde' 2024-02-09 14:38:02 +00:00
Jędrzej Stuczyński 2f4fad3ce3 [bugfix] remove hard failure on dkg contract queries in case it doesn't exist 2024-02-09 11:39:27 +00:00
Jon Häggblad cc604c5f18 Merge pull request #4380 from nymtech/jon/ipr-connected-client-handler
Connected client handler in the IPR
2024-02-09 11:37:13 +01:00
Jon Häggblad d0aece501f Add missing deploy step to ci-build-upload-binaries 2024-02-09 11:28:32 +01:00
Jon Häggblad 22b5670396 Update release/publish workflow names to match filenames (#4383) 2024-02-09 11:26:39 +01:00
benedetta davico 4ebbf175fc Merge branch 'develop' into chore/remove-ephemera 2024-02-09 11:24:52 +01:00
Jon Häggblad 79e9399dfe Add nightly schedule trigger for ci-build-upload-binaries 2024-02-09 11:17:34 +01:00
Jon Häggblad 8450df28df Tweak logging 2024-02-09 10:58:49 +01:00
Jon Häggblad 0b23d1624f Switch to JoinHandle 2024-02-09 09:49:18 +01:00
Jon Häggblad 2026ffd61f Error logging 2024-02-09 09:49:18 +01:00
Jon Häggblad 48e5aecda1 Don't unwrap on failed to send close signal 2024-02-09 09:49:18 +01:00
Jon Häggblad d8e484b77e Disconnect stopped client handlers 2024-02-09 09:49:18 +01:00
Jon Häggblad d4ca2a7220 Implement drop for client handlers too 2024-02-09 09:49:18 +01:00
Jon Häggblad 2f0074821c Downgrade some logging after checking it works 2024-02-09 09:49:18 +01:00
Jon Häggblad d5e332ad39 Deduplicate and clean up 2024-02-09 09:49:18 +01:00
Jon Häggblad 14bf5645b1 Add missing module 2024-02-09 09:49:18 +01:00
Jon Häggblad a11582749c Add connected_client_handler 2024-02-09 09:49:18 +01:00
Jon Häggblad aedff7fe30 Fix clippy::useless_conversion (#4384) 2024-02-09 09:37:32 +01:00
Jon Häggblad 36e4c181fc Add enable_wireguard toggle to build-upload-binaries workflow (#4382)
* Add enable_wireguard toggle to ci-build-upload-binaries workflow

* Remove old deprecated build-upload-binaries

* fixup! Add enable_wireguard toggle to ci-build-upload-binaries workflow
2024-02-09 09:15:30 +01:00
Tommy Verrall 68cfe2e755 fix unit test 2024-02-08 17:43:51 +00:00
Tommy Verrall 2baac3de1b Merge pull request #4338 from nymtech/feature/nym-cli-multisend
nym-cli: add command to broadcast a transaction with multiple send token messages
2024-02-08 17:08:41 +01:00
Tommy Verrall edc9b78b6c last clippy warning 2024-02-08 16:07:13 +00:00
Tommy Verrall 9f07f3aff3 fmt 2024-02-08 15:55:57 +00:00
Tommy Verrall 23ba8298be amend code, as described on PR, trialled and testing on QA 2024-02-08 15:46:21 +00:00
Tommy Verrall 629d124838 Merge pull request #4371 from nymtech/feature/DKG-revamp1
Feature/dkg revamp1
2024-02-08 14:35:33 +01:00
mx fe2d602cd8 Max/new mdbook theme (#4377)
* stripped out theme plugin + edited coal default

* cleanedup gitignore

* stripped down light theme

* new theme dir structure

* removed themes aside from dark and light custom

* moved search to right hand side

* added toc

* changed up header bar

* hard centred title

* themed dropdown menus

* copied all vars between book tomls for the moment

* moved new theming to operators and devportal

* changed comment on future language support

---------

Co-authored-by: mfahampshire <mfahampshire@pm.me>
2024-02-08 10:02:57 +00:00
Jon Häggblad 8b2f80b03c Multi IP packet codec (#4379)
* Codec implementation

* rustfmt

* Extract out magic numbers
2024-02-08 11:01:01 +01:00
Jędrzej Stuczyński 336cd30dd8 updated dkg contract schema 2024-02-07 12:28:51 +00:00
Jon Häggblad a8dc703399 Merge pull request #4370 from nymtech/jon/reply-on-ipr-version-incompat
Extend IPR request / responses to handle more reply types
2024-02-07 13:24:14 +01:00
Jędrzej Stuczyński c8562ecac1 fixed dkg contract test build 2024-02-07 11:56:44 +00:00
Jędrzej Stuczyński dd0067f542 fixed arguments passed for dealer registration 2024-02-07 11:55:49 +00:00
Jędrzej Stuczyński a18dab55a6 fixed coconut key existence check in key derivation 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński 4c8ae077a2 fixed nym-api config template 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński 79a7860185 changed 'DealingChunkInfo' 'size' field from usize to u64 to remove floating point operations during deserialization 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński bb71da55e8 regenerated DKG contract schema 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński ca18fb9f33 fixed clippy warnings on existing code 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński ceec8217e0 nym-cli build fix 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński a52e81b66e temporarily commented out broken test 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński a44339433e fixed nym-api tests 2024-02-07 11:55:48 +00:00
Jędrzej Stuczyński c9290cbcc0 handle chunking on nym-api side 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński ce3e674528 updated dkg client traits 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński c9f5594ca5 contract changes 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński a7feeaa660 dealing metadata storage logic 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński e7bc50fc4a resharing test + bugfixes 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński e926a1e2c0 fixed bug in DKG to allow for different sets of dealers and receivers 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński bd9a628a98 restored key derivation tests 2024-02-07 11:55:47 +00:00
Jędrzej Stuczyński 19a9d5413d restored dealings tests 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński 016ab58648 updated contract schema 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński 8ec7534b57 clippy fixes 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński 3c66ab9adc test code compiles
but doesnt fully work yet
2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński a66f63e34d cleanup 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński c9814a1c6e more completed key derivation 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński 59d31cfa2b more completed key validation 2024-02-07 11:55:46 +00:00
Jędrzej Stuczyński 6d9bc302ff more completed key finalization 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński 75cc310fc8 happy path for key finalization 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński bd7eebf463 happy path for key validation 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński c31561d46d actually working happy path with a unit test 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński faffdf9b2f happy path for key derivation 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński 7081076842 improved test fixture + dealing exchange test 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński b0174dcd0b [wip] dealing exchange 2024-02-07 11:55:45 +00:00
Jędrzej Stuczyński 90de0a30a8 storing epoch id alongside coconut key 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński 546e7c794f [wip]: improving error recovery during key submission phase 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński a1f68170c9 more explicit errors in the controller outer loop 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński ae29e86db0 cleaned up key loading 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński 359f038dff removed the dkg client retries 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński 0aa8084625 cargo fmt 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński 9b7815d45b making nym-api aware of the changes 2024-02-07 11:55:44 +00:00
Jędrzej Stuczyński ad5a167fe5 client support 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 16f7ac9998 schema 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 0235932dda making dkg kick off when a start message is sent 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 96fd084582 fixed the return type of the query 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 7344248f3b added a query msg for the data 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 824dfa3d6d added cw2 interface to dkg contract 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński 2548c8d42d missing test fix 2024-02-07 11:55:43 +00:00
Jędrzej Stuczyński f4facc08ea fixed tests 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński f20f96831a api support: submit ed25519 public key alongside the bte public key 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński a94196eb82 submit ed25519 public key alongside the bte public key 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 02884d183d reusing already generated dealings 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 75b02c739d client support 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 3b39ec4b28 schema 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 8a6b6ead95 contract query for dealing status 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 6b6bbe535f fixed dealings query arguments 2024-02-07 11:55:42 +00:00
Jędrzej Stuczyński 85d9d65da3 more clippy 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 9f580d7bc2 updated dkg schema 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 4dee8858da clippy 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 49797d46bb removed old debug code 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 4060489bd1 ephemera contract fix 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 205e44a857 fixes 2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 6bf9dca722 reintroducing bug in deterministic_filter_dealers to make tests pass
yes, it's as bad as it sounds
2024-02-07 11:55:41 +00:00
Jędrzej Stuczyński 48be25f9c7 ability to query for dkg contract state 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński 58080ec681 client support 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński 45e8d3d78e renaming 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński c7b8622cf4 removed todos from commented tests 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński fbd58122f4 storage and query tests 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński 13f8449dc8 updated dealings queries 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński db36f72200 storing dealings in new map 2024-02-07 11:55:40 +00:00
Jędrzej Stuczyński e09986e505 fixed arguments for installing yarn (#4376) 2024-02-07 12:52:59 +01:00
Jędrzej Stuczyński bb3c015633 Merge pull request #4353 from nymtech/feature/gateway-cached-vk
make gateway cache master verification keys between requests
2024-02-07 11:31:12 +00:00
Jon Häggblad b21346064e Add disconnect request/response for the future 2024-02-07 10:18:23 +01:00
import this fa81b96951 [DOC]: hotfix - typo correction (#4374) 2024-02-06 16:03:17 +01:00
Jon Häggblad 8cccc9ab24 Bump IPR request version 2024-02-06 11:01:54 +01:00
Drazen Urch b567ac22d3 Fix builds action (#4372)
* Fix builds action

* Filter deb files
2024-02-05 22:05:21 +01:00
Jon Häggblad b43a1b8c94 Include destination in error reply 2024-02-05 11:27:28 +01:00
Jon Häggblad d7da6ed1ab Remove unused error case 2024-02-05 09:01:11 +01:00
Jon Häggblad 4d62dc9c74 Respond to sender on version mismatch 2024-02-04 18:34:29 +01:00
import this 2d39f3c722 [DOC]: NymVPN GUI new auto-script implementation (#4369)
Binaries have new name convention and the script was failing. This PR solves the issue for both MacOS and Linux users.
2024-02-02 19:11:21 +00:00
Jon Häggblad 3d122f45b4 Add two more error responses likely to be used in the future 2024-02-02 17:02:24 +01:00
Jon Häggblad cb375f15c2 Add ipr response for version mismatch 2024-02-02 16:43:52 +01:00
Tommy Verrall 7406fdd677 Merge pull request #4363 from nymtech/jon/ci-cargo-deny-pull-request-trigger
Enable pull_request trigger on ci-cargo-deny
2024-02-02 16:16:52 +01:00
Jon Häggblad d7d4c9f09a Split ip-packet-requests types into modules 2024-02-02 14:49:51 +01:00
Tommy Verrall 94d83648c2 Merge pull request #4357 from nymtech/chore/update-rocket
Chore/update rocket
2024-02-01 19:50:57 +01:00
Tommy Verrall aa51af7023 Merge pull request #4366 from nymtech/feature/validator-rewarder-monitor-only
Add config to run the block signing in monitoring mode only
2024-02-01 19:29:20 +01:00
import this f45ed78806 [DOC] : add NymVPN videos (#4367)
* create community councel and landing pages stub

* address review comments -> finished

* remove redundant

* reorganize menu and fix link

* branch url fix

* update testing video
2024-02-01 13:50:14 +00:00
import this c0337ec1d4 [DOC/operators]: Legal Forum - Community landing pages stub (#4365)
* create community councel and landing pages stub

* address review comments -> finished

* remove redundant
2024-02-01 07:41:24 +00:00
Tommy Verrall 6fe049d1a2 Merge pull request #4351 from nymtech/move-ppa-s3
Clean up git, update docs,
2024-02-01 06:11:22 +01:00
Mark Sinclair 63ed99d4d6 Add config to run the block signing in monitoring mode only. Will write 0's as the hash for rewarding. 2024-01-31 11:46:57 +00:00
Jędrzej Stuczyński 2061629d1d Merge pull request #4364 from nymtech/bugfix/rewarding-epoch-start
[bugfix] make sure first rewarding epoch starts at :00
2024-01-31 09:30:50 +00:00
Jędrzej Stuczyński 9b99a19ba0 [bugfix] make sure first rewarding epoch starts at :00 2024-01-31 09:08:21 +00:00
Jon Häggblad 6a3afb50b8 Remove continue-on-error for cargo deny check licenses 2024-01-31 09:40:51 +01:00
Jon Häggblad bea64b926f Add license to cpu-cycles matching libcpucycles 2024-01-30 21:56:34 +01:00
Jon Häggblad 3b83c30558 Add zlib to the list of allowed licenses 2024-01-30 21:37:19 +01:00
Jon Häggblad ceeccbba07 License typo 2024-01-30 21:37:19 +01:00
Jon Häggblad be55bb61cb Add missing license annotations to ephemera and bity integration 2024-01-30 21:37:08 +01:00
Jon Häggblad f2af35fc2e Enable pull_request trigger on ci-cargo-deny 2024-01-30 13:56:12 +01:00
Jon Häggblad b874fc9314 Standalone ip-packet-router (#4342)
* Initial copy of code from network-requester

* Fix unused

* Fix reading nym-api

* Log env setup steps

* rustfmt

* fix

* Fix unused

* Log number of retransmissions instead of rate
2024-01-30 13:15:34 +01:00
Jędrzej Stuczyński 914c586e68 Merge pull request #4346 from nymtech/feature/rewarder-whitelist
Feature/rewarder whitelist
2024-01-30 10:08:21 +00:00
Jon Häggblad 10ba3c2ab9 Fix printing percentage instead of fraction (#4359) 2024-01-29 22:53:35 +01:00
Jędrzej Stuczyński 7e32787ab2 updated lock file 2024-01-26 17:28:01 +00:00
Jędrzej Stuczyński 7062f69e45 updated rocket in explorer-api 2024-01-26 17:27:51 +00:00
Jędrzej Stuczyński 5e98c14a98 updated rocket in network statistics 2024-01-26 17:27:42 +00:00
Jędrzej Stuczyński f04d1fea56 updated rocket in nym-api 2024-01-26 17:27:33 +00:00
Jędrzej Stuczyński 339c6c6d24 finally using the correct feature in nym-node for utoipa 2024-01-26 17:21:24 +00:00
Jędrzej Stuczyński bd6ba89e96 removed ephemera usage from nym-api 2024-01-26 17:21:01 +00:00
import this 836e237116 [DOC] Hot-Fix (#4355)
* update gui ato-script

* update scripts and simplify steps

* finish NymVPN demo guide update
2024-01-26 16:38:41 +00:00
import this 0f9bd648a1 [DOC]: NymVPN auto-scripts update (#4350)
* update gui ato-script

* update scripts and simplify steps
2024-01-26 14:29:25 +00:00
Jędrzej Stuczyński 0c2c0bdc54 delay acquiring vk lock 2024-01-26 12:11:43 +00:00
Jędrzej Stuczyński 991cc3fa01 make gateway cache master verification keys between requests 2024-01-26 12:07:51 +00:00
Jon Häggblad 3510ee8df6 Report packet traffic rates in nym client (#4345)
* Report packet traffic rates

* Tweak log

* Check for unusual events

* Log tweaks
2024-01-26 12:13:46 +01:00
Jon Häggblad 6774158e7a Upgrade publicsuffix crate to latest (#4341)
* Upgrade publicsuffix crate to latest

This is a step in removing dependencies on OpenSSL

* fix clippy
2024-01-26 10:35:12 +01:00
durch f98698a121 Clean up git 2024-01-26 09:50:18 +01:00
import this 8e99c17f49 [DOC]: Update NymVPN Guides to v0.0.3 (#4347)
* correct GW API endpoint url

* update shasum verification

* update nym-vpn versions

* syntax edit

* change vars in book.toml

* fix name convention

* address feedback changes

* udpdate script name
2024-01-24 17:19:39 +00:00
mx ab4cc9b282 C++ FFI (#4348)
* first commit in monorepo

* *formatting
*added license

* Fix up license headers

---------

Co-authored-by: mfahampshire <mfahampshire@pm.me>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2024-01-24 15:05:59 +00:00
Jon Häggblad dbe6a5de7d Fix reported sizes of the received packets (#4343)
The received packet sizes as reported in the stats should include the
encryption that is decrypted by the client. Note that this does not
include the sphinx encryption, which is already removed by the exit
gateway. This is also the reason for the relatively large discrepancy of
the reported sent and received packets.
2024-01-24 14:54:40 +01:00
Drazen Urch 1948fd8e67 Publish deb packages to builds.ci (#4344)
* Publish deb

* Pass secret as argument

* Install cargo-deb
2024-01-24 13:18:52 +01:00
Jędrzej Stuczyński c8f38ae785 added whitelisting information in logs 2024-01-23 15:10:53 +00:00
Jędrzej Stuczyński f32ea17de5 disabled credential issuance by default 2024-01-23 15:05:53 +00:00
Jędrzej Stuczyński 4ac25aef4d setting 0 ratios for not whitelisted runners 2024-01-23 14:54:41 +00:00
Jędrzej Stuczyński 3ad6a31e1f don't increment total issued credentials from not whitelisted nodes 2024-01-23 14:52:38 +00:00
Jędrzej Stuczyński 6cacc53e5a resolved old todo 2024-01-23 14:43:46 +00:00
Jędrzej Stuczyński 387933a975 using whitelisting information for rewarding 2024-01-23 14:41:15 +00:00
Jędrzej Stuczyński f6c24412c0 checking for empty whitelists 2024-01-23 13:54:19 +00:00
Jędrzej Stuczyński 5c753c0794 added new whitelist entries to the config 2024-01-23 13:53:31 +00:00
Tommy Verrall 67132161f4 Merge pull request #4332 from nymtech/update/explorer-rounded-values
Mixnode table - Round value to 2dp
2024-01-22 10:42:58 +01:00
Jon Häggblad 643f54024b Client packet counters (#4325)
* WIP: put in some packet counters

* ws packet counters

* wip

* Add static counters to client traffic stream

* Tweak status log message

* Add packet statistics control

* fixup! Add static counters to client traffic stream

* tweak log

* Move the packet statistics control one level up

* Redo packet stats control to collect locally

* Switch loop cover traffic report over to new channel mechanism

* Switch packet stats in real message stream to channel report

* Finished switching over to channel reporting

* Fix handle stats event

* Log packets received

* Tidy up

* rustfmt

* Add strongly typed stats reporter

* Count cover packets as well

* Log packet sizes sent

* Also log recieved sizes
2024-01-22 09:00:11 +01:00
serinko 16aaf7b5df [DOC]: FAQ Updates (#4339)
* add mixnet live stats to faq

* add point to mixnet faq

* syntax edit

* addressed comments
2024-01-19 14:09:31 +00:00
serinko 17c6b79735 delete redundant leftovers (#4340) 2024-01-19 11:38:03 +00:00
Tommy Verrall 8bd758ad0e Update sandbox.env (#4331) 2024-01-19 11:18:14 +01:00
Jon Häggblad a51fc0cb9e Restore BinaryBuildInformation schema (#4333)
* wip

* Restore BinaryBuildInformation schema
2024-01-19 11:16:25 +01:00
Mark Sinclair fd68debf9d nym-cli: add command to broadcast a transaction with multiple send tokens in it 2024-01-18 15:41:02 +00:00
serinko ae602ae771 [DOC]: Edit commands and text flow (#4337)
* syntax hotfix

erase a white space

* edit numbering

* edit numbering

* edit numbering

* syntax edit

* syntax edit
2024-01-18 11:30:10 +00:00
Sachin Kamath d6d36364b0 Add full node configuration and requirements (#4335)
* docs: add CORS line to maintenance page

* docs: add full node configuration and size to nym-api page

* docs: fix review comments
2024-01-18 09:40:10 +00:00
serinko accb42cad9 [DOCs]: serinko/syntax-hotfix (#4334)
* [DOCs]: nymvpn syntax hotfix

* cli naming hotfix
2024-01-17 14:35:24 +00:00
serinko dd43c5d2d2 [DOCs]: Create NymVPN user manual (#4323)
* initialise new nymvpn guide pages

* docs: nymvpn guide, testing, troubleshooting and faq

* add faq

* remove todo points

* resolve review comments

* change landing page order

* incorporate huxis user feedback

* add binaries link

* change menu naming -> upper case

* final version for cryptotalk demo

* change naming convention client -> cli

* initialise clean and organized  nymvpn guides

* remove redundant

* add faq page

* add cli.md content

* add gui.md content

* almost final version - ready for review

* simplify menu titles

* finished version for review and production

* last tweak

* addressed requests

* syntax fix

* add extra intro warning

* yank directly ./nym-cli --help output text

* change landing page and warning -> info

* add variables and finish the guides

* edit point formatting
2024-01-17 14:09:05 +00:00
fmtabbara e42d46100a allow currencyToString function to accept an object with amount, dp, and denom properties 2024-01-17 13:36:34 +00:00
Drazen Urch ed8b1841dc nym-cli deb + ppa (#4330) 2024-01-17 11:12:46 +01:00
Drazen Urch dd15a9454a Add nym-gateway to ppa repo (#4321)
* Add gateway ppa scaffolding

* Resolve host ip address, add curl dep

* make deb -> make ppa

* Add build targets for deb packages

* Add gateway public-ips

* Update PPA repo

* Typo
2024-01-16 18:31:15 +01:00
Jędrzej Stuczyński f4e42d74c4 Merge pull request #4326 from nymtech/bugfix/nymvisor-windows-ci
Bugfix/nymvisor windows ci
2024-01-16 11:36:47 +01:00
Pierre Dommerc 9de1e6e844 chore: remove nym-vpn projects (#4327) 2024-01-16 11:10:26 +01:00
Tommy Verrall 713df39106 Merge pull request #4324 from nymtech/feature/bity-sell
Update text on buy page to Buy/Sell
2024-01-15 16:55:18 +01:00
Jędrzej Stuczyński 5ec4674f9b even more target locking 2024-01-15 15:45:19 +00:00
Jędrzej Stuczyński 58c5092e80 added a dummy main for non unix targets 2024-01-15 15:18:52 +00:00
Jędrzej Stuczyński 677ad54a7f used global unix cfg 2024-01-15 14:54:49 +00:00
fmtabbara 5e17c3199f update text on buy page to buy/sell 2024-01-15 14:32:39 +00:00
751 changed files with 12401 additions and 31266 deletions
@@ -1,61 +0,0 @@
name: build-upload-binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
env:
NETWORK: mainnet
jobs:
publish-nym:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-20.04]
runs-on: ${{ matrix.platform }}
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v3
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
continue-on-error: true
- 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
with:
toolchain: stable
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-binaries-artifacts
path: |
target/release/nym-client
target/release/nym-gateway
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
retention-days: 30
+72 -13
View File
@@ -2,20 +2,34 @@ name: ci-build-upload-binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
enable_wireguard:
description: 'Add --features wireguard'
required: true
default: false
type: boolean
schedule:
- cron: '14 0 * * *'
pull_request:
paths:
- 'clients/**'
- 'common/**'
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'sdk/rust/nym-sdk/**'
- 'service-providers/**'
- 'nym-api/**'
- 'nym-outfox/**'
- 'tools/nym-cli/**'
- 'tools/ts-rs-cli/**'
- "clients/**"
- "common/**"
- "explorer-api/**"
- "gateway/**"
- "integrations/**"
- "mixnode/**"
- "nym-api/**"
- "nym-node/**"
- "nym-outfox/**"
- "nym-validator-rewarder/**"
- "sdk/rust/nym-sdk/**"
- "service-providers/**"
- "tools/**"
jobs:
publish-nym:
@@ -42,6 +56,18 @@ jobs:
- name: Install Dependencies (Linux)
run: sudo apt update && sudo apt install libudev-dev
- 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: Set CARGO_FEATURES
run: |
echo 'CARGO_FEATURES=--features wireguard' >> $GITHUB_ENV
if: >
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && inputs.enable_wireguard == true)
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -51,9 +77,40 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --release
args: --workspace --release ${{ env.CARGO_FEATURES }}
- name: Install cargo-deb
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-deb
- name: Build deb packages
shell: bash
run: make deb
# If this was a manual workflow_dispatch, publish binaries.
- name: Upload Artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v3
with:
name: nym-binaries-artifacts
path: |
target/release/nym-client
target/release/nym-gateway
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
retention-days: 30
# If this was a pull_request or nightly, upload to build server
- name: Prepare build output
# if: github.event_name == 'schedule' || github.event_name == 'pull_request'
shell: bash
env:
OUTPUT_DIR: ci-builds/${{ github.ref_name }}
@@ -67,8 +124,10 @@ jobs:
cp target/release/nym-network-statistics $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
cp target/debian/*.deb $OUTPUT_DIR
- name: Deploy branch to CI www
# if: github.event_name == 'schedule' || github.event_name == 'pull_request'
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
+5 -5
View File
@@ -1,5 +1,8 @@
name: ci-cargo-deny
on: [workflow_dispatch]
on:
workflow_dispatch:
pull_request:
jobs:
cargo-deny:
runs-on: ubuntu-22.04
@@ -7,10 +10,7 @@ jobs:
matrix:
checks:
# - advisories
- licenses
- bans sources
continue-on-error: ${{ matrix.checks == 'licenses' }}
- licenses bans sources
steps:
- uses: actions/checkout@v3
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v2
- name: install yarn in root
run: cd ../.. yarn install
run: cd ../.. && yarn install
- name: Install npm
run: npm install
-40
View File
@@ -1,40 +0,0 @@
name: ci-nym-vpn-ui-js
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src/**'
- 'nym-vpn/ui/package.json'
- 'nym-vpn/ui/index.html'
jobs:
check:
runs-on: custom-linux
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
working-directory: nym-vpn/ui
run: yarn
- name: Type-check
working-directory: nym-vpn/ui
run: yarn typecheck
- name: Check lint
working-directory: nym-vpn/ui
run: yarn lint
- name: Check formatting
working-directory: nym-vpn/ui
run: yarn fmt:check
# - name: Run tests
# working-directory: nym-vpn/ui
# run: yarn test
- name: Check build
working-directory: nym-vpn/ui
run: yarn build
-63
View File
@@ -1,63 +0,0 @@
name: ci-nym-vpn-ui-rust
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src-tauri/**'
jobs:
build:
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
CARGOTOML_PATH: ./nym-vpn/ui/src-tauri/Cargo.toml
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
continue-on-error: true
- name: Checkout
uses: actions/checkout@v4
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Prepare build
run: mkdir nym-vpn/ui/dist
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path ${{ env.CARGOTOML_PATH }} --features custom-protocol
# - name: Run all tests
# uses: actions-rs/cargo@v1
# with:
# command: test
# args: --manifest-path ${{ env.CARGOTOML_PATH }}
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all -- --check
- name: Annotate with clippy checks
uses: actions-rs/clippy-check@v1
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features --all-targets -- -D warnings
+1 -1
View File
@@ -1,4 +1,4 @@
name: Publish Nym binaries
name: publish-nym-binaries
on:
workflow_dispatch:
@@ -1,4 +1,4 @@
name: Publish Nym Connect - desktop (MacOS)
name: publish-nym-connect-macos
on:
workflow_dispatch:
release:
@@ -1,4 +1,4 @@
name: Publish Nym Connect - desktop (Ubuntu)
name: publish-nym-connect-ubuntu
on:
workflow_dispatch:
release:
@@ -1,4 +1,4 @@
name: Publish Nym Connect - desktop (Windows 10)
name: publish-nym-connect-win10
on:
workflow_dispatch:
release:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Build release of Nym smart contracts
name: publish-nym-contracts
on:
workflow_dispatch:
release:
@@ -1,4 +1,4 @@
name: Publish Nym Wallet (MacOS)
name: publish-nym-wallet-macos
on:
workflow_dispatch:
release:
@@ -1,4 +1,4 @@
name: Publish Nym Wallet (Ubuntu)
name: publish-nym-wallet-ubuntu
on:
workflow_dispatch:
release:
@@ -1,4 +1,4 @@
name: Publish Nym Wallet (Windows 10)
name: publish-nym-wallet-win10
on:
workflow_dispatch:
release:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Publish Typescript SDK
name: publish-sdk-npm
on:
workflow_dispatch:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Releases - calculate file hashes
name: release-calculate-hash
on:
workflow_call:
Generated
+257 -1505
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -33,7 +33,7 @@ members = [
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/coconut-dkg",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/ephemera",
# "common/cosmwasm-smart-contracts/ephemera",
"common/cosmwasm-smart-contracts/group-contract",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
@@ -104,7 +104,7 @@ members = [
"nym-outfox",
"nym-validator-rewarder",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
# "tools/internal/sdk-version-bump",
"tools/nym-cli",
"tools/nym-nr-query",
"tools/nymvisor",
@@ -128,7 +128,7 @@ default-members = [
"nym-validator-rewarder",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles", "sdk/ffi/cpp"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -158,7 +158,7 @@ log = "0.4"
once_cell = "1.7.2"
parking_lot = "0.12.1"
rand = "0.8.5"
reqwest = "0.11.22"
reqwest = { version = "0.11.22", default_features = false, features = ["rustls-tls"] }
schemars = "0.8.1"
serde = "1.0.152"
serde_json = "1.0.91"
@@ -168,9 +168,9 @@ time = "0.3.30"
thiserror = "1.0.48"
tokio = "1.33.0"
tokio-util = "0.7.10"
tokio-tungstenite = "0.20.1"
tokio-tungstenite = { version = "0.20.1", features = ["rustls"] }
tracing = "0.1.37"
tungstenite = { version = "0.20.1", default-features = false }
tungstenite = { version = "0.20.1", default-features = false, features = ["rustls"] }
ts-rs = "7.0.0"
utoipa = "3.5.0"
utoipa-swagger-ui = "3.1.5"
+17 -3
View File
@@ -12,6 +12,7 @@ help:
@echo " clippy: run clippy for all workspaces"
@echo " test: run clippy, unit tests, and formatting."
@echo " test-all: like test, but also includes the expensive tests"
@echo " deb: build debian packages
# -----------------------------------------------------------------------------
# Meta targets
@@ -157,6 +158,12 @@ build-explorer-api:
build-nym-cli:
cargo build -p nym-cli --release
build-nym-gateway:
cargo build -p nym-gateway --release
build-nym-mixnode:
cargo build -p nym-mixnode --release
# -----------------------------------------------------------------------------
# Misc
# -----------------------------------------------------------------------------
@@ -169,6 +176,13 @@ run-api-tests:
cd nym-api/tests/functional_test && yarn test:qa
# Build debian package, and update PPA
# Requires base64 encode GPG key to be set up in environment PPA_SIGNING_KEY
deb:
scripts/ppa.sh
deb-mixnode: build-nym-mixnode
cargo deb -p nym-mixnode
deb-gateway: build-nym-gateway
cargo deb -p nym-gateway
deb-cli: build-nym-cli
cargo deb -p nym-cli
deb: deb-mixnode deb-gateway deb-cli
+1 -1
View File
@@ -51,7 +51,7 @@ impl InitialisableClient for NativeClientInit {
}
}
#[derive(Args, Clone)]
#[derive(Args, Clone, Debug)]
pub(crate) struct Init {
#[command(flatten)]
common_args: CommonClientInitArgs,
+1 -1
View File
@@ -51,7 +51,7 @@ impl InitialisableClient for Socks5ClientInit {
}
}
#[derive(Args, Clone)]
#[derive(Args, Clone, Debug)]
pub(crate) struct Init {
#[command(flatten)]
common_args: CommonClientInitArgs,
+1
View File
@@ -9,6 +9,7 @@ repository = { workspace = true }
[dependencies]
atty = "0.2"
const-str = "0.5.6"
clap = { workspace = true, features = ["derive"] }
clap_complete = "4.0"
clap_complete_fig = "4.0"
+15 -6
View File
@@ -42,12 +42,20 @@ pub struct BinaryBuildInformation {
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: &'static str,
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
pub cargo_profile: &'static str,
}
impl BinaryBuildInformation {
// explicitly require the build_version to be passed as it's binary specific
pub const fn new(binary_name: &'static str, build_version: &'static str) -> Self {
let cargo_debug = env!("VERGEN_CARGO_DEBUG");
let cargo_profile = if const_str::equal!(cargo_debug, "true") {
"debug"
} else {
"release"
};
BinaryBuildInformation {
binary_name,
build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"),
@@ -57,7 +65,7 @@ impl BinaryBuildInformation {
commit_branch: env!("VERGEN_GIT_BRANCH"),
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
cargo_debug: env!("VERGEN_CARGO_DEBUG"),
cargo_profile,
}
}
@@ -71,7 +79,7 @@ impl BinaryBuildInformation {
commit_branch: self.commit_branch.to_owned(),
rustc_version: self.rustc_version.to_owned(),
rustc_channel: self.rustc_channel.to_owned(),
cargo_debug: self.cargo_debug.to_owned(),
cargo_profile: self.cargo_profile.to_owned(),
}
}
@@ -117,7 +125,8 @@ pub struct BinaryBuildInformationOwned {
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: String,
// NOTE: keep the old name cargo_profile instead of cargo_debug for backwards compatibility
pub cargo_profile: String,
}
impl Display for BinaryBuildInformationOwned {
@@ -151,8 +160,8 @@ impl Display for BinaryBuildInformationOwned {
self.rustc_version,
"rustc Channel:",
self.rustc_channel,
"cargo Debug:",
self.cargo_debug,
"cargo Profile:",
self.cargo_profile,
)
}
}
+1
View File
@@ -46,6 +46,7 @@ nym-validator-client = { path = "../client-libs/validator-client", default-featu
nym-task = { path = "../task" }
nym-credential-storage = { path = "../credential-storage" }
nym-network-defaults = { path = "../network-defaults" }
si-scale = "0.2.2"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
version = "0.1.11"
@@ -109,6 +109,8 @@ pub async fn initialise_client<C>(
) -> Result<InitResultsWithConfig<C::Config>, C::Error>
where
C: InitialisableClient,
<C as InitialisableClient>::Config: std::fmt::Debug,
<C as InitialisableClient>::InitArgs: std::fmt::Debug,
{
info!("initialising {} client", C::NAME);
@@ -140,17 +142,32 @@ where
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = common_args.gateway;
log::debug!("User chosen gateway id: {user_chosen_gateway_id:?}");
let selection_spec = GatewaySelectionSpecification::new(
user_chosen_gateway_id.map(|id| id.to_base58_string()),
Some(common_args.latency_based_selection),
false,
);
log::debug!("Gateway selection specification: {selection_spec:?}");
// Load and potentially override config
log::debug!("Init arguments: {init_args:#?}");
let config = C::construct_config(&init_args);
log::debug!("Constructed config: {config:#?}");
let paths = config.common_paths();
let core = config.core_config();
log::info!(
"Using nym-api: {}",
core.client
.nym_api_urls
.iter()
.map(|url| url.as_str())
.collect::<Vec<&str>>()
.join(",")
);
// Setup gateway by either registering a new one, or creating a new config from the selected
// one but with keys kept, or reusing the gateway configuration.
let key_store = OnDiskKeys::new(paths.keys.clone());
@@ -1,6 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::packet_statistics_control::PacketStatisticsReporter;
use super::received_buffer::ReceivedBufferMessage;
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
use crate::client::base_client::storage::gateway_details::GatewayDetailsStore;
@@ -10,6 +11,7 @@ use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputM
use crate::client::key_manager::persistence::KeyStore;
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::packet_statistics_control::PacketStatisticsControl;
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
@@ -254,6 +256,7 @@ where
self_address: Recipient,
topology_accessor: TopologyAccessor,
mix_tx: BatchMixMessageSender,
stats_tx: PacketStatisticsReporter,
shutdown: TaskClient,
) {
info!("Starting loop cover traffic stream...");
@@ -266,6 +269,7 @@ where
topology_accessor,
debug_config.traffic,
debug_config.cover_traffic,
stats_tx,
);
stream.start_with_shutdown(shutdown);
@@ -285,6 +289,7 @@ where
client_connection_rx: ConnectionCommandReceiver,
shutdown: TaskClient,
packet_type: PacketType,
stats_tx: PacketStatisticsReporter,
) {
info!("Starting real traffic stream...");
@@ -299,6 +304,7 @@ where
reply_controller_receiver,
lane_queue_lengths,
client_connection_rx,
stats_tx,
)
.start_with_shutdown(shutdown, packet_type);
}
@@ -312,6 +318,7 @@ where
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
shutdown: TaskClient,
packet_statistics_control: PacketStatisticsReporter,
) {
info!("Starting received messages buffer controller...");
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
@@ -321,6 +328,7 @@ where
mixnet_receiver,
reply_key_storage,
reply_controller_sender,
packet_statistics_control,
);
controller.start_with_shutdown(shutdown)
}
@@ -506,6 +514,13 @@ where
Ok(())
}
fn start_packet_statistics_control(shutdown: TaskClient) -> PacketStatisticsReporter {
info!("Starting packet statistics control...");
let (packet_statistics_control, packet_stats_reporter) = PacketStatisticsControl::new();
packet_statistics_control.start_with_shutdown(shutdown);
packet_stats_reporter
}
fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown: TaskClient,
@@ -633,6 +648,9 @@ where
)
.await?;
let packet_stats_reporter =
Self::start_packet_statistics_control(shutdown.fork("packet_statistics_control"));
let gateway_packet_router = PacketRouter::new(
ack_sender,
mixnet_messages_sender,
@@ -662,6 +680,7 @@ where
reply_storage.key_storage(),
reply_controller_sender.clone(),
shutdown.fork("received_messages_buffer"),
packet_stats_reporter.clone(),
);
// The message_sender is the transmitter for any component generating sphinx packets
@@ -700,6 +719,7 @@ where
client_connection_rx,
shutdown.fork("real_traffic_controller"),
self.config.debug.traffic.packet_type,
packet_stats_reporter.clone(),
);
if !self
@@ -714,6 +734,7 @@ where
self_address,
shared_topology_accessor.clone(),
message_sender,
packet_stats_reporter,
shutdown.fork("cover_traffic_stream"),
);
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
use crate::client::topology_control::TopologyAccessor;
use crate::{config, spawn_future};
use futures::task::{Context, Poll};
@@ -61,6 +62,8 @@ where
secondary_packet_size: Option<PacketSize>,
packet_type: PacketType,
stats_tx: PacketStatisticsReporter,
}
impl<R> Stream for LoopCoverTrafficStream<R>
@@ -97,7 +100,8 @@ where
// obviously when we finally make shared rng that is on 'higher' level, this should become
// generic `R`
impl LoopCoverTrafficStream<OsRng> {
pub fn new(
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
ack_key: Arc<AckKey>,
average_ack_delay: Duration,
mix_tx: BatchMixMessageSender,
@@ -105,6 +109,7 @@ impl LoopCoverTrafficStream<OsRng> {
topology_access: TopologyAccessor,
traffic_config: config::Traffic,
cover_config: config::CoverTraffic,
stats_tx: PacketStatisticsReporter,
) -> Self {
let rng = OsRng;
@@ -122,6 +127,7 @@ impl LoopCoverTrafficStream<OsRng> {
primary_packet_size: traffic_config.primary_packet_size,
secondary_packet_size: traffic_config.secondary_packet_size,
packet_type: traffic_config.packet_type,
stats_tx,
}
}
@@ -191,6 +197,10 @@ impl LoopCoverTrafficStream<OsRng> {
log::warn!("Failed to send cover message - channel closed");
}
}
} else {
self.stats_tx.report(PacketStatisticsEvent::CoverPacketSent(
cover_traffic_packet_size.size(),
));
}
// TODO: I'm not entirely sure whether this is really required, because I'm not 100%
+1
View File
@@ -7,6 +7,7 @@ pub(crate) mod helpers;
pub mod inbound_messages;
pub mod key_manager;
pub mod mix_traffic;
pub(crate) mod packet_statistics_control;
pub mod real_messages_control;
pub mod received_buffer;
pub mod replies;
@@ -0,0 +1,503 @@
use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use si_scale::helpers::bibytes2;
use crate::spawn_future;
// Time interval between reporting packet statistics
const PACKET_REPORT_INTERVAL_SECS: u64 = 2;
// Interval for taking snapshots of the packet statistics
const SNAPSHOT_INTERVAL_MS: u64 = 500;
// When computing rates, we include snapshots that are up to this old. We set it to some odd number
// a tad larger than an integer number of snapshot intervals, so that we don't have to worry about
// threshold effects.
// Also, set it larger than the packet report interval so that we don't miss notable singular events
const RECORDING_WINDOW_MS: u64 = 2300;
#[derive(Default, Debug, Clone)]
struct PacketStatistics {
// Sent
real_packets_sent: u64,
real_packets_sent_size: usize,
cover_packets_sent: u64,
cover_packets_sent_size: usize,
// Received
real_packets_received: u64,
real_packets_received_size: usize,
cover_packets_received: u64,
cover_packets_received_size: usize,
// Acks
total_acks_received: u64,
total_acks_received_size: usize,
real_acks_received: u64,
real_acks_received_size: usize,
cover_acks_received: u64,
cover_acks_received_size: usize,
// Types of packets queued
// TODO: track the type sent instead
real_packets_queued: u64,
retransmissions_queued: u64,
reply_surbs_queued: u64,
additional_reply_surbs_queued: u64,
}
impl PacketStatistics {
fn handle_event(&mut self, event: PacketStatisticsEvent) {
match event {
PacketStatisticsEvent::RealPacketSent(packet_size) => {
self.real_packets_sent += 1;
self.real_packets_sent_size += packet_size;
}
PacketStatisticsEvent::CoverPacketSent(packet_size) => {
self.cover_packets_sent += 1;
self.cover_packets_sent_size += packet_size;
}
PacketStatisticsEvent::RealPacketReceived(packet_size) => {
self.real_packets_received += 1;
self.real_packets_received_size += packet_size;
}
PacketStatisticsEvent::CoverPacketReceived(packet_size) => {
self.cover_packets_received += 1;
self.cover_packets_received_size += packet_size;
}
PacketStatisticsEvent::AckReceived(packet_size) => {
self.total_acks_received += 1;
self.total_acks_received_size += packet_size;
}
PacketStatisticsEvent::RealAckReceived(packet_size) => {
self.real_acks_received += 1;
self.real_acks_received_size += packet_size;
}
PacketStatisticsEvent::CoverAckReceived(packet_size) => {
self.cover_acks_received += 1;
self.cover_acks_received_size += packet_size;
}
PacketStatisticsEvent::RealPacketQueued => {
self.real_packets_queued += 1;
}
PacketStatisticsEvent::RetransmissionQueued => {
self.retransmissions_queued += 1;
}
PacketStatisticsEvent::ReplySurbRequestQueued => {
self.reply_surbs_queued += 1;
}
PacketStatisticsEvent::AdditionalReplySurbRequestQueued => {
self.additional_reply_surbs_queued += 1;
}
}
}
fn summary(&self) -> (String, String) {
(
format!(
"packets sent: {} (real: {}, cover: {}, retransmissions: {})",
self.real_packets_sent + self.cover_packets_sent,
self.real_packets_sent,
self.cover_packets_sent,
self.retransmissions_queued,
),
format!(
"packets received: {}, (real: {}, cover: {}, acks: {}, acks for cover: {})",
self.real_packets_received + self.cover_packets_received,
self.real_packets_received,
self.cover_packets_received,
self.real_acks_received,
self.cover_acks_received,
),
)
}
}
impl std::ops::Sub for PacketStatistics {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
real_packets_sent: self.real_packets_sent - rhs.real_packets_sent,
real_packets_sent_size: self.real_packets_sent_size - rhs.real_packets_sent_size,
cover_packets_sent: self.cover_packets_sent - rhs.cover_packets_sent,
cover_packets_sent_size: self.cover_packets_sent_size - rhs.cover_packets_sent_size,
real_packets_received: self.real_packets_received - rhs.real_packets_received,
real_packets_received_size: self.real_packets_received_size
- rhs.real_packets_received_size,
cover_packets_received: self.cover_packets_received - rhs.cover_packets_received,
cover_packets_received_size: self.cover_packets_received_size
- rhs.cover_packets_received_size,
total_acks_received: self.total_acks_received - rhs.total_acks_received,
total_acks_received_size: self.total_acks_received_size - rhs.total_acks_received_size,
real_acks_received: self.real_acks_received - rhs.real_acks_received,
real_acks_received_size: self.real_acks_received_size - rhs.real_acks_received_size,
cover_acks_received: self.cover_acks_received - rhs.cover_acks_received,
cover_acks_received_size: self.cover_acks_received_size - rhs.cover_acks_received_size,
real_packets_queued: self.real_packets_queued - rhs.real_packets_queued,
retransmissions_queued: self.retransmissions_queued - rhs.retransmissions_queued,
reply_surbs_queued: self.reply_surbs_queued - rhs.reply_surbs_queued,
additional_reply_surbs_queued: self.additional_reply_surbs_queued
- rhs.additional_reply_surbs_queued,
}
}
}
#[derive(Debug, Clone)]
struct PacketRates {
real_packets_sent: f64,
real_packets_sent_size: f64,
cover_packets_sent: f64,
cover_packets_sent_size: f64,
real_packets_received: f64,
real_packets_received_size: f64,
cover_packets_received: f64,
cover_packets_received_size: f64,
total_acks_received: f64,
total_acks_received_size: f64,
real_acks_received: f64,
real_acks_received_size: f64,
cover_acks_received: f64,
cover_acks_received_size: f64,
real_packets_queued: f64,
retransmissions_queued: f64,
reply_surbs_queued: f64,
additional_reply_surbs_queued: f64,
}
impl From<PacketStatistics> for PacketRates {
fn from(stats: PacketStatistics) -> Self {
Self {
real_packets_sent: stats.real_packets_sent as f64,
real_packets_sent_size: stats.real_packets_sent_size as f64,
cover_packets_sent: stats.cover_packets_sent as f64,
cover_packets_sent_size: stats.cover_packets_sent_size as f64,
real_packets_received: stats.real_packets_received as f64,
real_packets_received_size: stats.real_packets_received_size as f64,
cover_packets_received: stats.cover_packets_received as f64,
cover_packets_received_size: stats.cover_packets_received_size as f64,
total_acks_received: stats.total_acks_received as f64,
total_acks_received_size: stats.total_acks_received_size as f64,
real_acks_received: stats.real_acks_received as f64,
real_acks_received_size: stats.real_acks_received_size as f64,
cover_acks_received: stats.cover_acks_received as f64,
cover_acks_received_size: stats.cover_acks_received_size as f64,
real_packets_queued: stats.real_packets_queued as f64,
retransmissions_queued: stats.retransmissions_queued as f64,
reply_surbs_queued: stats.reply_surbs_queued as f64,
additional_reply_surbs_queued: stats.additional_reply_surbs_queued as f64,
}
}
}
impl std::ops::Sub for PacketRates {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
real_packets_sent: self.real_packets_sent - rhs.real_packets_sent,
real_packets_sent_size: self.real_packets_sent_size - rhs.real_packets_sent_size,
cover_packets_sent: self.cover_packets_sent - rhs.cover_packets_sent,
cover_packets_sent_size: self.cover_packets_sent_size - rhs.cover_packets_sent_size,
real_packets_received: self.real_packets_received - rhs.real_packets_received,
real_packets_received_size: self.real_packets_received_size
- rhs.real_packets_received_size,
cover_packets_received: self.cover_packets_received - rhs.cover_packets_received,
cover_packets_received_size: self.cover_packets_received_size
- rhs.cover_packets_received_size,
total_acks_received: self.total_acks_received - rhs.total_acks_received,
total_acks_received_size: self.total_acks_received_size - rhs.total_acks_received_size,
real_acks_received: self.real_acks_received - rhs.real_acks_received,
real_acks_received_size: self.real_acks_received_size - rhs.real_acks_received_size,
cover_acks_received: self.cover_acks_received - rhs.cover_acks_received,
cover_acks_received_size: self.cover_acks_received_size - rhs.cover_acks_received_size,
real_packets_queued: self.real_packets_queued - rhs.real_packets_queued,
retransmissions_queued: self.retransmissions_queued - rhs.retransmissions_queued,
reply_surbs_queued: self.reply_surbs_queued - rhs.reply_surbs_queued,
additional_reply_surbs_queued: self.additional_reply_surbs_queued
- rhs.additional_reply_surbs_queued,
}
}
}
impl std::ops::Div<f64> for PacketRates {
type Output = Self;
fn div(self, rhs: f64) -> Self::Output {
Self {
real_packets_sent: self.real_packets_sent / rhs,
real_packets_sent_size: self.real_packets_sent_size / rhs,
cover_packets_sent: self.cover_packets_sent / rhs,
cover_packets_sent_size: self.cover_packets_sent_size / rhs,
real_packets_received: self.real_packets_received / rhs,
real_packets_received_size: self.real_packets_received_size / rhs,
cover_packets_received: self.cover_packets_received / rhs,
cover_packets_received_size: self.cover_packets_received_size / rhs,
total_acks_received: self.total_acks_received / rhs,
total_acks_received_size: self.total_acks_received_size / rhs,
real_acks_received: self.real_acks_received / rhs,
real_acks_received_size: self.real_acks_received_size / rhs,
cover_acks_received: self.cover_acks_received / rhs,
cover_acks_received_size: self.cover_acks_received_size / rhs,
real_packets_queued: self.real_packets_queued / rhs,
retransmissions_queued: self.retransmissions_queued / rhs,
reply_surbs_queued: self.reply_surbs_queued / rhs,
additional_reply_surbs_queued: self.additional_reply_surbs_queued / rhs,
}
}
}
impl PacketRates {
fn summary(&self) -> String {
format!(
"down: {}/s, up: {}/s (cover down: {}/s, cover up: {}/s)",
bibytes2(self.real_packets_received_size),
bibytes2(self.real_packets_sent_size),
bibytes2(self.cover_packets_received_size),
bibytes2(self.cover_packets_sent_size),
)
}
fn detailed_summary(&self) -> String {
format!(
"RX: {:.1} mixpkt/s, {}/s (real: {}/s, acks: {}/s), TX: {:.1} mixpkt/s, {}/s (real: {}/s)",
self.real_packets_received + self.cover_packets_received,
bibytes2(self.real_packets_received_size + self.cover_packets_received_size),
bibytes2(self.real_packets_received_size),
bibytes2(self.total_acks_received_size),
self.real_packets_sent + self.cover_packets_sent,
bibytes2(self.real_packets_sent_size + self.cover_packets_sent_size),
bibytes2(self.real_packets_sent_size),
)
}
}
#[derive(Debug)]
pub(crate) enum PacketStatisticsEvent {
// The real packets sent. Recall that acks are sent by the gateway, so it's not included here.
RealPacketSent(usize),
// The cover packets sent
CoverPacketSent(usize),
// Real packets received
RealPacketReceived(usize),
// Cover packets received
CoverPacketReceived(usize),
// Ack of any type received. This is mostly used as a consistency check, and should be the sum
// of real and cover acks received.
AckReceived(usize),
// Out of the total acks received, this is the subset of those that were real
RealAckReceived(usize),
// Out of the total acks received, this is the subset of those that were for cover traffic
CoverAckReceived(usize),
// Types of packets queued
RealPacketQueued,
RetransmissionQueued,
ReplySurbRequestQueued,
AdditionalReplySurbRequestQueued,
}
type PacketStatisticsReceiver = tokio::sync::mpsc::UnboundedReceiver<PacketStatisticsEvent>;
#[derive(Clone)]
pub(crate) struct PacketStatisticsReporter {
stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>,
}
impl PacketStatisticsReporter {
pub(crate) fn new(stats_tx: tokio::sync::mpsc::UnboundedSender<PacketStatisticsEvent>) -> Self {
Self { stats_tx }
}
pub(crate) fn report(&self, event: PacketStatisticsEvent) {
self.stats_tx.send(event).unwrap_or_else(|err| {
log::error!("Failed to report packet stat: {:?}", err);
});
}
}
pub(crate) struct PacketStatisticsControl {
// Incoming packet stats events from other tasks
stats_rx: PacketStatisticsReceiver,
// Keep track of packet statistics over time
stats: PacketStatistics,
// We keep snapshots of the statistics over time so we can compute rates, and also keeping the
// full history allows for some more fancy averaging if we want to do that.
history: VecDeque<(Instant, PacketStatistics)>,
// Keep previous rates so that we can detect notable events
rates: VecDeque<(Instant, PacketRates)>,
}
impl PacketStatisticsControl {
pub(crate) fn new() -> (Self, PacketStatisticsReporter) {
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
(
Self {
stats_rx,
stats: PacketStatistics::default(),
history: VecDeque::new(),
rates: VecDeque::new(),
},
PacketStatisticsReporter::new(stats_tx),
)
}
// Add the current stats to the history, and remove old ones.
fn update_history(&mut self) {
// Update latest
self.history.push_back((Instant::now(), self.stats.clone()));
// Filter out old ones
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
while self
.history
.front()
.map_or(false, |&(t, _)| t < recording_window)
{
self.history.pop_front();
}
}
fn compute_rates(&self) -> Option<PacketRates> {
// NOTE: consider changing this to compute rates over the history instead of using current
// stats. Currently it should not make much of a difference since we call this just after
// updating the history, but it seems like it could be more internally consistent to do it
// that way.
// Do basic averaging over the entire history, which just uses the first and last
if let Some((start, start_stats)) = self.history.front() {
let duration_secs = Instant::now().duration_since(*start).as_secs_f64();
let delta = self.stats.clone() - start_stats.clone();
let rates = PacketRates::from(delta) / duration_secs;
Some(rates)
} else {
None
}
}
fn update_rates(&mut self) {
// Update latest
if let Some(rates) = self.compute_rates() {
self.rates.push_back((Instant::now(), rates));
}
// Filter out old ones
let recording_window = Instant::now() - Duration::from_millis(RECORDING_WINDOW_MS);
while self
.rates
.front()
.map_or(false, |&(t, _)| t < recording_window)
{
self.rates.pop_front();
}
}
fn report_rates(&self) {
if let Some((_, rates)) = self.rates.back() {
log::info!("{}", rates.summary());
log::debug!("{}", rates.detailed_summary());
}
}
fn report_counters(&self) {
log::trace!("packet statistics: {:?}", &self.stats);
let (summary_sent, summary_recv) = self.stats.summary();
log::debug!("{}", summary_sent);
log::debug!("{}", summary_recv);
}
fn check_for_notable_events(&self) {
let Some((_, latest_rates)) = self.rates.back() else {
return;
};
// If we get a burst of retransmissions
// TODO: consider making this the number of retransmissions since last report instead.
if latest_rates.retransmissions_queued > 0.0 {
log::debug!(
"retransmissions: {:.2} pkt/s",
latest_rates.retransmissions_queued
);
// Check what the number of retransmissions was during the recording window
if let Some((_, start_stats)) = self.history.front() {
let delta = self.stats.clone() - start_stats.clone();
log::info!(
"mix packet retransmissions/real mix packets: {}/{}",
delta.retransmissions_queued,
delta.real_packets_queued,
);
} else {
log::warn!("Unable to check retransmissions during recording window");
}
}
// IDEA: if there is a burst of acks, that could indicate tokio task starvation.
}
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
log::debug!("Started PacketStatisticsControl with graceful shutdown support");
let report_interval = Duration::from_secs(PACKET_REPORT_INTERVAL_SECS);
let mut report_interval = tokio::time::interval(report_interval);
let snapshot_interval = Duration::from_millis(SNAPSHOT_INTERVAL_MS);
let mut snapshot_interval = tokio::time::interval(snapshot_interval);
loop {
tokio::select! {
stats_event = self.stats_rx.recv() => match stats_event {
Some(stats_event) => {
log::trace!("PacketStatisticsControl: Received stats event");
self.stats.handle_event(stats_event);
},
None => {
log::trace!("PacketStatisticsControl: stopping since stats channel was closed");
break;
}
},
_ = snapshot_interval.tick() => {
self.update_history();
self.update_rates();
}
_ = report_interval.tick() => {
self.report_rates();
self.check_for_notable_events();
self.report_counters();
}
_ = shutdown.recv_with_delay() => {
log::trace!("PacketStatisticsControl: Received shutdown");
break;
},
}
}
log::debug!("PacketStatisticsControl: Exiting");
}
pub(crate) fn start_with_shutdown(mut self, task_client: nym_task::TaskClient) {
spawn_future(async move {
self.run_with_shutdown(task_client).await;
})
}
}
@@ -1,6 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
use super::action_controller::{AckActionSender, Action};
use futures::StreamExt;
use log::*;
@@ -17,6 +19,7 @@ pub(super) struct AcknowledgementListener {
ack_key: Arc<AckKey>,
ack_receiver: AcknowledgementReceiver,
action_sender: AckActionSender,
stats_tx: PacketStatisticsReporter,
}
impl AcknowledgementListener {
@@ -24,16 +27,21 @@ impl AcknowledgementListener {
ack_key: Arc<AckKey>,
ack_receiver: AcknowledgementReceiver,
action_sender: AckActionSender,
stats_tx: PacketStatisticsReporter,
) -> Self {
AcknowledgementListener {
ack_key,
ack_receiver,
action_sender,
stats_tx,
}
}
async fn on_ack(&mut self, ack_content: Vec<u8>) {
trace!("Received an ack");
self.stats_tx
.report(PacketStatisticsEvent::AckReceived(ack_content.len()));
let frag_id = match recover_identifier(&self.ack_key, &ack_content)
.map(FragmentIdentifier::try_from_bytes)
{
@@ -48,11 +56,14 @@ impl AcknowledgementListener {
// because nothing was inserted in the first place
if frag_id == COVER_FRAG_ID {
trace!("Received an ack for a cover message - no need to do anything");
self.stats_tx
.report(PacketStatisticsEvent::CoverAckReceived(ack_content.len()));
return;
}
trace!("Received {} from the mix network", frag_id);
self.stats_tx
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()));
self.action_sender
.unbounded_send(Action::new_remove(frag_id))
.unwrap();
@@ -8,6 +8,7 @@ use self::{
sent_notification_listener::SentNotificationListener,
};
use crate::client::inbound_messages::InputMessageReceiver;
use crate::client::packet_statistics_control::PacketStatisticsReporter;
use crate::client::real_messages_control::message_handler::MessageHandler;
use crate::client::replies::reply_controller::ReplyControllerSender;
use crate::spawn_future;
@@ -208,6 +209,7 @@ where
connectors: AcknowledgementControllerConnectors,
message_handler: MessageHandler<R>,
reply_controller_sender: ReplyControllerSender,
stats_tx: PacketStatisticsReporter,
) -> Self {
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
@@ -224,6 +226,7 @@ where
Arc::clone(&ack_key),
connectors.ack_receiver,
connectors.ack_action_sender.clone(),
stats_tx,
);
// will listen for any new messages from the client
@@ -35,6 +35,8 @@ use crate::client::replies::reply_controller;
use crate::config;
pub(crate) use acknowledgement_control::{AckActionSender, Action};
use super::packet_statistics_control::PacketStatisticsReporter;
pub(crate) mod acknowledgement_control;
pub(crate) mod message_handler;
pub(crate) mod real_traffic_stream;
@@ -143,6 +145,7 @@ impl RealMessagesController<OsRng> {
reply_controller_receiver: ReplyControllerReceiver,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
stats_tx: PacketStatisticsReporter,
) -> Self {
let rng = OsRng;
@@ -181,6 +184,7 @@ impl RealMessagesController<OsRng> {
ack_controller_connectors,
message_handler.clone(),
reply_controller_sender,
stats_tx.clone(),
);
let reply_control = ReplyController::new(
@@ -199,6 +203,7 @@ impl RealMessagesController<OsRng> {
topology_access,
lane_queue_lengths,
client_connection_rx,
stats_tx,
);
RealMessagesController {
@@ -3,6 +3,7 @@
use self::sending_delay_controller::SendingDelayController;
use crate::client::mix_traffic::BatchMixMessageSender;
use crate::client::packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter};
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
use crate::client::topology_control::TopologyAccessor;
use crate::client::transmission_buffer::TransmissionBuffer;
@@ -113,6 +114,9 @@ where
/// Report queue lengths so that upstream can backoff sending data, and keep connections open.
lane_queue_lengths: LaneQueueLengths,
/// Channel used for sending statistics events to `PacketStatisticsControl`.
stats_tx: PacketStatisticsReporter,
}
#[derive(Debug)]
@@ -171,6 +175,7 @@ where
topology_access: TopologyAccessor,
lane_queue_lengths: LaneQueueLengths,
client_connection_rx: ConnectionCommandReceiver,
stats_tx: PacketStatisticsReporter,
) -> Self {
OutQueueControl {
config,
@@ -184,6 +189,7 @@ where
transmission_buffer: TransmissionBuffer::new(),
client_connection_rx,
lane_queue_lengths,
stats_tx,
}
}
@@ -214,7 +220,7 @@ where
async fn on_message(&mut self, next_message: StreamMessage) {
trace!("created new message");
let (next_message, fragment_id) = match next_message {
let (next_message, fragment_id, packet_size) = match next_message {
StreamMessage::Cover => {
let cover_traffic_packet_size = self.loop_cover_message_size();
trace!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
@@ -250,15 +256,28 @@ where
"Somehow failed to generate a loop cover message with a valid topology",
),
None,
cover_traffic_packet_size.size(),
)
}
StreamMessage::Real(real_message) => {
(real_message.mix_packet, real_message.fragment_id)
let packet_size = real_message.packet_size();
(
real_message.mix_packet,
real_message.fragment_id,
packet_size,
)
}
};
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
log::error!("Failed to send: {err}");
} else {
let event = if fragment_id.is_some() {
PacketStatisticsEvent::RealPacketSent(packet_size)
} else {
PacketStatisticsEvent::CoverPacketSent(packet_size)
};
self.stats_tx.report(event);
}
// notify ack controller about sending our message only after we actually managed to push it
@@ -340,6 +359,28 @@ where
let lane_length = self.transmission_buffer.lane_length(&lane);
self.lane_queue_lengths.set(&lane, lane_length);
// This is the last step in the pipeline where we know the type of the message, so
// lets count the number of retransmissions and reply surb messages sent here.
let stat_event = match lane {
TransmissionLane::General => None,
TransmissionLane::ConnectionId(_) => None,
TransmissionLane::ReplySurbRequest => {
Some(PacketStatisticsEvent::ReplySurbRequestQueued)
}
TransmissionLane::AdditionalReplySurbs => {
Some(PacketStatisticsEvent::AdditionalReplySurbRequestQueued)
}
TransmissionLane::Retransmission => Some(PacketStatisticsEvent::RetransmissionQueued),
};
if let Some(stat_event) = stat_event {
self.stats_tx.report(stat_event);
}
// To avoid comparing apples to oranges when presenting the fraction of packets that are
// retransmissions, we also need to keep track to the total number of real messages queued,
// even though we also track the actual number of messages sent later in the pipeline.
self.stats_tx
.report(PacketStatisticsEvent::RealPacketQueued);
Some(real_next)
}
@@ -433,6 +474,13 @@ where
Poll::Ready(Some((real_messages, conn_id))) => {
log::trace!("handling real_messages: size: {}", real_messages.len());
// This is the last step in the pipeline where we know the type of the message, so
// lets count the number of retransmissions here.
if conn_id == TransmissionLane::Retransmission {
self.stats_tx
.report(PacketStatisticsEvent::RetransmissionQueued);
}
// First store what we got for the given connection id
self.transmission_buffer.store(&conn_id, real_messages);
let real_next = self.pop_next_message().expect("we just added one");
@@ -471,10 +519,10 @@ where
let mult = self.sending_delay_controller.current_multiplier();
let delay = self.current_average_message_sending_delay().as_millis();
let status_str = if self.config.traffic.disable_main_poisson_packet_distribution {
format!("Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), no delay")
format!("Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, no delay")
} else {
format!(
"Status: {lanes} lanes, backlog: {backlog:.2} kiB ({packets}), avg delay: {delay}ms ({mult})"
"Packet backlog: {backlog:.2} kiB ({packets}), {lanes} lanes, avg delay: {delay}ms ({mult})"
)
};
if packets > 1000 {
@@ -1,8 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_controller::ReplyControllerSender;
use crate::client::replies::reply_storage::SentReplyKeys;
use crate::client::{
packet_statistics_control::{PacketStatisticsEvent, PacketStatisticsReporter},
replies::{reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys},
};
use crate::spawn_future;
use futures::channel::mpsc;
use futures::lock::Mutex;
@@ -43,15 +45,33 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
// but perhaps it should be changed to include timestamps of when the message was reconstructed
// and every now and then remove ids older than X
recently_reconstructed: HashSet<i32>,
stats_tx: PacketStatisticsReporter,
}
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option<NymMessage> {
fn recover_from_fragment(
&mut self,
fragment_data: &[u8],
fragment_data_size: usize,
) -> Option<NymMessage> {
if nym_sphinx::cover::is_cover(fragment_data) {
trace!("The message was a loop cover message! Skipping it");
// NOTE: it's important to note that there is quite a bit of difference in size of
// received and sent packets due to the sphinx layers being removed by the exit gateway
// before it reaches the mixnet client.
self.stats_tx
.report(PacketStatisticsEvent::CoverPacketReceived(
fragment_data_size,
));
return None;
}
self.stats_tx
.report(PacketStatisticsEvent::RealPacketReceived(
fragment_data_size,
));
let fragment = match self.message_receiver.recover_fragment(fragment_data) {
Err(err) => {
warn!("failed to recover fragment from raw data: {err}. The whole underlying message might be corrupted and unrecoverable!");
@@ -103,15 +123,17 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
reply_ciphertext: &mut [u8],
reply_key: SurbEncryptionKey,
) -> Result<Option<NymMessage>, MessageRecoveryError> {
let reply_ciphertext_size = reply_ciphertext.len();
// note: this performs decryption IN PLACE without extra allocation
self.message_receiver
.recover_plaintext_from_reply(reply_ciphertext, reply_key)?;
let fragment_data = reply_ciphertext;
Ok(self.recover_from_fragment(fragment_data))
Ok(self.recover_from_fragment(fragment_data, reply_ciphertext_size))
}
fn process_received_regular_packet(&mut self, mut raw_fragment: Vec<u8>) -> Option<NymMessage> {
let raw_fragment_size = raw_fragment.len();
let fragment_data = match self.message_receiver.recover_plaintext_from_regular_packet(
self.local_encryption_keypair.private_key(),
&mut raw_fragment,
@@ -123,7 +145,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
Ok(frag_data) => frag_data,
};
self.recover_from_fragment(fragment_data)
self.recover_from_fragment(fragment_data, raw_fragment_size)
}
}
@@ -141,6 +163,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
local_encryption_keypair: Arc<encryption::KeyPair>,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
stats_tx: PacketStatisticsReporter,
) -> Self {
ReceivedMessagesBuffer {
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
@@ -149,6 +172,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
message_receiver: R::new(),
message_sender: None,
recently_reconstructed: HashSet::new(),
stats_tx,
})),
reply_key_storage,
reply_controller_sender,
@@ -353,7 +377,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
};
if let Some(completed) = completed_message {
info!("received {completed}");
debug!("received {completed}");
completed_messages.push(completed)
}
}
@@ -480,11 +504,13 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
mixnet_packet_receiver: MixnetMessageReceiver,
reply_key_storage: SentReplyKeys,
reply_controller_sender: ReplyControllerSender,
packet_statistics_reporter: PacketStatisticsReporter,
) -> Self {
let received_buffer = ReceivedMessagesBuffer::new(
local_encryption_keypair,
reply_key_storage,
reply_controller_sender,
packet_statistics_reporter,
);
ReceivedMessagesBufferController {
+1 -1
View File
@@ -65,7 +65,7 @@ pub async fn current_gateways<R: Rng>(
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = nym_validator_client::client::NymApiClient::new(nym_api.clone());
log::trace!("Fetching list of gateways from: {nym_api}");
log::debug!("Fetching list of gateways from: {nym_api}");
let gateways = client.get_cached_described_gateways().await?;
log::debug!("Found {} gateways", gateways.len());
+1 -1
View File
@@ -178,7 +178,7 @@ impl<T> From<PersistedGatewayDetails<T>> for GatewayDetails<T> {
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum GatewaySelectionSpecification<T = EmptyCustomDetails> {
/// Uniformly choose a random remote gateway.
UniformRemote { must_use_tls: bool },
+1 -1
View File
@@ -48,7 +48,7 @@ features = ["net", "sync", "time"]
workspace = true
# the choice of this particular tls feature was arbitrary;
# if you reckon a different one would be more appropriate, feel free to change it
features = ["native-tls"]
# features = ["native-tls"]
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
@@ -31,7 +31,6 @@ log = { workspace = true }
url = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["sync", "time"] }
futures = { workspace = true }
openssl = { version = "^0.10.55", features = ["vendored"], optional = true }
nym-coconut-interface = { path = "../../coconut-interface" }
nym-network-defaults = { path = "../../network-defaults" }
@@ -90,7 +89,7 @@ required-features = ["http-client"]
[features]
default = ["http-client"]
http-client = ["cosmrs/rpc", "openssl"]
http-client = ["cosmrs/rpc"]
generate-ts = []
contract-testing = ["nym-mixnet-contract-common/contract-testing"]
@@ -7,17 +7,18 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::types::ChunkIndex;
use serde::Deserialize;
pub use nym_coconut_dkg_common::{
dealer::{
DealerDetailsResponse, DealingResponse, DealingStatusResponse, PagedDealerResponse,
PagedDealingsResponse,
dealer::{DealerDetailsResponse, PagedDealerResponse},
dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatusResponse,
},
msg::QueryMsg as DkgQueryMsg,
types::{
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData,
PartialContractDealing, State,
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
},
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
};
@@ -77,6 +78,31 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_dealings_metadata(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingMetadataResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealer_dealings_status(
&self,
epoch_id: EpochId,
dealer: String,
) -> Result<DealerDealingsStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealerDealingsStatus { epoch_id, dealer };
self.query_dkg_contract(request).await
}
async fn get_dealing_status(
&self,
epoch_id: EpochId,
@@ -92,34 +118,37 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_dealing(
async fn get_dealing_chunk_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingResponse, NyxdError> {
let request = DkgQueryMsg::GetDealing {
chunk_index: ChunkIndex,
) -> Result<DealingChunkStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
dealing_index,
chunk_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealer_dealings_paged(
async fn get_dealing_chunk(
&self,
epoch_id: EpochId,
dealer: &str,
start_after: Option<DealingIndex>,
limit: Option<u32>,
) -> Result<PagedDealingsResponse, NyxdError> {
let request = DkgQueryMsg::GetDealings {
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Result<DealingChunkResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingChunk {
epoch_id,
dealer: dealer.to_string(),
limit,
start_after,
dealer,
dealing_index,
chunk_index,
};
self.query_dkg_contract(request).await
}
@@ -165,14 +194,6 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
collect_paged!(self, get_past_dealers_paged, dealers)
}
async fn get_all_dealer_dealings(
&self,
epoch_id: EpochId,
dealer: &str,
) -> Result<Vec<PartialContractDealing>, NyxdError> {
collect_paged!(self, get_dealer_dealings_paged, dealings, epoch_id, dealer)
}
async fn get_all_verification_key_shares(
&self,
epoch_id: EpochId,
@@ -206,6 +227,7 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::IgnoreValue;
use nym_coconut_dkg_common::msg::QueryMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -236,18 +258,31 @@ mod tests {
} => client
.get_dealing_status(epoch_id, dealer, dealing_index)
.ignore(),
DkgQueryMsg::GetDealing {
DkgQueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
} => client.get_dealing(epoch_id, dealer, dealing_index).ignore(),
DkgQueryMsg::GetDealings {
} => client
.get_dealings_metadata(epoch_id, dealer, dealing_index)
.ignore(),
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
client.get_dealer_dealings_status(epoch_id, dealer).ignore()
}
DkgQueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
limit,
start_after,
dealing_index,
chunk_index,
} => client
.get_dealer_dealings_paged(epoch_id, &dealer, limit, start_after)
.get_dealing_chunk_status(epoch_id, dealer, dealing_index, chunk_index)
.ignore(),
DkgQueryMsg::GetDealingChunk {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => client
.get_dealing_chunk(epoch_id, dealer, dealing_index, chunk_index)
.ignore(),
DkgQueryMsg::GetVerificationKey { epoch_id, owner } => {
client.get_vk_share(epoch_id, owner).ignore()
@@ -8,9 +8,9 @@ use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
use crate::signing::signer::OfflineSigner;
use async_trait::async_trait;
use cosmrs::AccountId;
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, PartialContractDealing};
use nym_coconut_dkg_common::types::{DealingIndex, EncodedBTEPublicKeyWithProof};
use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
use nym_contracts_common::IdentityKey;
@@ -65,15 +65,32 @@ pub trait DkgSigningClient {
.await
}
async fn submit_dealing_bytes(
async fn submit_dealing_metadata(
&self,
dealing: PartialContractDealing,
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealing { dealing, resharing };
let req = DkgExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
};
self.execute_dkg_contract(fee, req, "dealing commitment".to_string(), vec![])
self.execute_dkg_contract(fee, req, "dealing metadata commitment".to_string(), vec![])
.await
}
async fn submit_dealing_chunk(
&self,
chunk: PartialContractDealing,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
.await
}
@@ -100,9 +117,10 @@ pub trait DkgSigningClient {
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
// the call to unchecked is fine as we're converting from pre-validated `AccountId`
let owner = Addr::unchecked(owner.to_string());
let req = DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let req = DkgExecuteMsg::VerifyVerificationKeyShare {
owner: owner.to_string(),
resharing,
};
self.execute_dkg_contract(
fee,
@@ -167,18 +185,21 @@ mod tests {
None,
)
.ignore(),
DkgExecuteMsg::CommitDealing { dealing, resharing } => client
.submit_dealing_bytes(dealing, resharing, None)
DkgExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
} => client
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
.ignore(),
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
client.submit_dealing_chunk(chunk, resharing, None).ignore()
}
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
.submit_verification_key_share(share, resharing, None)
.ignore(),
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
.verify_verification_key_share(
&owner.into_string().parse().unwrap(),
resharing,
None,
)
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
.ignore(),
DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(),
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
@@ -16,7 +16,6 @@ use crate::signing::tx_signer::TxSigner;
use crate::signing::AccountData;
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
use async_trait::async_trait;
use cosmrs::cosmwasm;
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
use cosmrs::tx::{Raw, SignDoc};
use cosmwasm_std::Addr;
@@ -40,7 +39,7 @@ pub use crate::rpc::TendermintRpcClient;
pub use coin::Coin;
pub use cosmrs::{
bank::MsgSend,
bip32,
bip32, cosmwasm,
crypto::PublicKey,
query::{PageRequest, PageResponse},
tendermint::{
+2
View File
@@ -13,9 +13,11 @@ bs58 = "0.4"
comfy-table = "6.0.0"
cfg-if = "1.0.0"
clap = { workspace = true, features = ["derive"] }
csv = "1.3.0"
cw-utils = { workspace = true }
handlebars = "3.0.1"
humantime-serde = "1.0"
inquire = "0.6.2"
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
log = { workspace = true }
rand = {version = "0.6", features = ["std"] }
@@ -0,0 +1,4 @@
n1q85lscptz860j3dx92f8phaeaw08j2l5dt7adq,50,nym
n136ckky0n39eskewg04xhvahq9yp9f7sgtnxytt,50,unym
n1xh7ru0zp67czxhvx0r5e8ur7rvg6n3ymmje0ju,50,nyx
n13mpm4aj03alffnvz2x9ynjy4u3cxemxn2xw34w,50,unyx
1 n1q85lscptz860j3dx92f8phaeaw08j2l5dt7adq 50 nym
2 n136ckky0n39eskewg04xhvahq9yp9f7sgtnxytt 50 unym
3 n1xh7ru0zp67czxhvx0r5e8ur7rvg6n3ymmje0ju 50 nyx
4 n13mpm4aj03alffnvz2x9ynjy4u3cxemxn2xw34w 50 unyx
+4 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
@@ -7,6 +7,7 @@ pub mod balance;
pub mod create;
pub mod pubkey;
pub mod send;
pub mod send_multiple;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -25,4 +26,6 @@ pub enum AccountCommands {
PubKey(crate::validator::account::pubkey::Args),
/// Sends tokens to another account
Send(crate::validator::account::send::Args),
/// Batch multiple token sends
SendMultiple(crate::validator::account::send_multiple::Args),
}
@@ -0,0 +1,216 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use crate::utils::pretty_coin;
use clap::Parser;
use comfy_table::Table;
use cosmrs::rpc::endpoint::tx::Response;
use log::{error, info};
use nym_validator_client::nyxd::{AccountId, Coin};
use serde_json::json;
use std::str::FromStr;
use std::{fs, io::Write};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub memo: Option<String>,
#[clap(
long,
help = "Input file path (CSV format) with account/amount pairs to send"
)]
pub input: String,
#[clap(
long,
help = "An output file path (CSV format) to create or append a log of results to"
)]
pub output: Option<String>,
}
pub async fn send_multiple(args: Args, client: &SigningClient) {
let memo = args
.memo
.unwrap_or_else(|| "Sending tokens with nym-cli".to_owned());
let rows = InputFileReader::new(&args.input);
if let Err(e) = rows {
error!("Failed to read input file: {}", e);
return;
}
let rows = rows.unwrap();
let mut table = Table::new();
if rows.rows.is_empty() {
error!("No transactions to send");
return;
}
println!(
"The following transfer will be made from account {} to:",
client.address()
);
table.set_header(vec!["Address", "Amount"]);
for row in rows.rows.iter() {
table.add_row(vec![row.address.to_string(), pretty_coin(&row.amount)]);
}
println!("{table}");
let ans = inquire::Confirm::new("Do you want to continue with the transfers?")
.with_default(false)
.with_help_message("You must confirm before the transaction will be sent")
.prompt();
if let Err(e) = ans {
info!("Aborting, {}...", e);
return;
}
if let Ok(false) = ans {
info!("Aborting!");
return;
}
info!("Transferring from {}...", client.address());
let multiple_sends: Vec<(AccountId, Vec<Coin>)> = rows
.rows
.iter()
.map(|row| (row.address.clone(), vec![row.amount.clone()]))
.collect();
let res = client
.send_multiple(multiple_sends, memo, None)
.await
.expect("failed to send tokens!");
info!("Sending result: {}", json!(res));
println!();
println!(
"Nodesguru: https://nym.explorers.guru/transaction/{}",
&res.hash
);
println!("Mintscan: https://www.mintscan.io/nyx/txs/{}", &res.hash);
println!("Transaction result code: {}", &res.tx_result.code.value());
println!("Transaction hash: {}", &res.hash);
if let Some(output_filename) = args.output {
println!("\nWriting output log to {}", output_filename);
if let Err(e) = write_output_file(rows, res, &output_filename) {
error!(
"Failed to write output file {} with error {}",
output_filename, e
);
}
}
}
fn write_output_file(
rows: InputFileReader,
res: Response,
output_filename: &String,
) -> Result<(), anyhow::Error> {
let mut file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(output_filename)?;
let now = time::OffsetDateTime::now_utc();
let now = now.format(&time::format_description::well_known::Rfc3339)?;
let data = rows
.rows
.iter()
.map(|row| {
format!(
"{},{},{},{},{}",
row.address, row.amount.amount, row.amount.denom, now, res.hash
)
})
.collect::<Vec<String>>()
.join("\n");
Ok(file.write_all(format!("{}\n", data).as_bytes())?)
}
#[derive(Debug)]
pub struct InputFileRow {
pub address: AccountId,
pub amount: Coin,
}
pub struct InputFileReader {
pub rows: Vec<InputFileRow>,
}
impl InputFileReader {
pub fn new(path: &str) -> Result<InputFileReader, anyhow::Error> {
let mut rows: Vec<InputFileRow> = vec![];
let file_contents = fs::read_to_string(path)?;
let lines: Vec<String> = file_contents.lines().map(String::from).collect();
for line in lines {
let tokens: Vec<_> = line.split(',').collect();
if tokens.len() < 3 {
return Err(anyhow::anyhow!(
"'{}' does not have enough columns, expecting <address>,<amount>,<denom>",
line
));
}
// try parse amount to u128
let amount = u128::from_str(tokens[1])
.map_err(|_| anyhow::anyhow!("'{}' has an invalid amount", line))?;
let denom: String = tokens[2].into();
// multiply when a whole token amount, e.g. 50nym (50.123456nym is not allowed, that must be input as 50123456unym)
let (amount, denom) = if !denom.starts_with('u') {
(amount * 1_000_000u128, format!("u{}", denom))
} else {
(amount, denom)
};
let address = AccountId::from_str(tokens[0])
.map_err(|e| anyhow::anyhow!("'{}' has an invalid address: {}", line, e))?;
let amount = Coin { amount, denom };
rows.push(InputFileRow { address, amount })
}
Ok(InputFileReader { rows })
}
}
#[cfg(test)]
mod test_multiple_send_input_csv {
use super::*;
use nym_validator_client::nyxd::AccountId;
use std::str::FromStr;
#[test]
fn works_on_happy_path() {
let input_csv = InputFileReader::new("fixtures/test_send_multiple.csv").unwrap();
assert_eq!(
AccountId::from_str("n1q85lscptz860j3dx92f8phaeaw08j2l5dt7adq").unwrap(),
input_csv.rows[0].address
);
println!("{:?}", input_csv.rows);
assert_eq!(50_000_000u128, input_csv.rows[0].amount.amount);
assert_eq!(50u128, input_csv.rows[1].amount.amount);
assert_eq!(50_000_000u128, input_csv.rows[2].amount.amount);
assert_eq!(50u128, input_csv.rows[3].amount.amount);
assert_eq!("unym", input_csv.rows[0].amount.denom);
assert_eq!("unym", input_csv.rows[1].amount.denom);
assert_eq!("unyx", input_csv.rows[2].amount.denom);
assert_eq!("unyx", input_csv.rows[3].amount.denom);
}
}
@@ -3,10 +3,11 @@
use clap::Parser;
use log::{debug, info};
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use std::str::FromStr;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::{TimeConfiguration, DEFAULT_DEALINGS};
use nym_coconut_dkg_common::types::TimeConfiguration;
use nym_validator_client::nyxd::AccountId;
#[derive(Debug, Parser)]
@@ -1,10 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{
ContractDealing, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, NodeIndex,
PartialContractDealing,
};
use crate::types::{EncodedBTEPublicKeyWithProof, NodeIndex};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
@@ -68,53 +65,3 @@ impl PagedDealerResponse {
}
}
}
#[cw_serde]
pub struct DealingResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub dealing: Option<ContractDealing>,
}
#[cw_serde]
pub struct DealingStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub dealing_submitted: bool,
}
#[cw_serde]
pub struct PagedDealingsResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealings: Vec<PartialContractDealing>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<DealingIndex>,
}
impl PagedDealingsResponse {
pub fn new(
epoch_id: EpochId,
dealer: Addr,
dealings: Vec<PartialContractDealing>,
start_next_after: Option<DealingIndex>,
) -> Self {
PagedDealingsResponse {
epoch_id,
dealer,
dealings,
start_next_after,
}
}
}
@@ -0,0 +1,290 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData};
use contracts_common::dealings::ContractSafeBytes;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use std::collections::{BTreeMap, HashMap};
/// Defines the maximum size of a dealing chunk. Currently set to 2kB
pub const MAX_DEALING_CHUNK_SIZE: usize = 2048;
/// Defines the maximum size of a full dealing.
/// Currently set to 100kB (which is enough for a dealing created for 100 parties)
pub const MAX_DEALING_SIZE: usize = 102400;
pub const MAX_DEALING_CHUNKS: usize = MAX_DEALING_SIZE / MAX_DEALING_CHUNK_SIZE;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
pub fn chunk_dealing(
dealing_index: DealingIndex,
dealing_bytes: Vec<u8>,
chunk_size: usize,
) -> HashMap<ChunkIndex, PartialContractDealing> {
let mut chunks = HashMap::new();
for (chunk_index, chunk) in dealing_bytes.chunks(chunk_size).enumerate() {
let chunk = PartialContractDealing {
dealing_index,
chunk_index: chunk_index as ChunkIndex,
data: ContractSafeBytes(chunk.to_vec()),
};
chunks.insert(chunk_index as ChunkIndex, chunk);
}
chunks
}
#[cw_serde]
#[derive(Copy)]
pub struct DealingChunkInfo {
pub size: u64,
}
impl DealingChunkInfo {
pub fn new(size: usize) -> Self {
DealingChunkInfo { size: size as u64 }
}
pub fn construct(dealing_len: usize, chunk_size: usize) -> Vec<Self> {
let (full_chunks, overflow) = (dealing_len / chunk_size, dealing_len % chunk_size);
let mut chunks = Vec::new();
for _ in 0..full_chunks {
chunks.push(DealingChunkInfo::new(chunk_size));
}
if overflow != 0 {
chunks.push(DealingChunkInfo::new(overflow));
}
chunks
}
}
#[cw_serde]
#[derive(Copy)]
pub struct SubmittedChunk {
pub info: DealingChunkInfo,
pub status: ChunkSubmissionStatus,
}
#[cw_serde]
#[derive(Default, Copy)]
pub struct ChunkSubmissionStatus {
// this field is updated by the contract itself to indicate when this particular chunk has been received
pub submission_height: Option<u64>,
}
impl ChunkSubmissionStatus {
pub fn submitted(&self) -> bool {
self.submission_height.is_some()
}
}
impl From<DealingChunkInfo> for SubmittedChunk {
fn from(value: DealingChunkInfo) -> Self {
SubmittedChunk::new(value)
}
}
impl SubmittedChunk {
pub fn new(info: DealingChunkInfo) -> Self {
SubmittedChunk {
info,
status: Default::default(),
}
}
pub fn submitted(&self) -> bool {
self.status.submitted()
}
}
#[cw_serde]
pub struct DealingMetadata {
pub dealing_index: DealingIndex,
pub submitted_chunks: BTreeMap<ChunkIndex, SubmittedChunk>,
}
impl DealingMetadata {
pub fn new(dealing_index: DealingIndex, chunks: Vec<DealingChunkInfo>) -> Self {
DealingMetadata {
dealing_index,
submitted_chunks: chunks
.into_iter()
.enumerate()
.map(|(id, chunk)| (id as ChunkIndex, chunk.into()))
.collect(),
}
}
pub fn is_complete(&self) -> bool {
self.submitted_chunks.values().all(|c| c.submitted())
}
pub fn total_size(&self) -> usize {
self.submitted_chunks
.values()
.map(|c| c.info.size as usize)
.sum()
}
pub fn submission_statuses(&self) -> BTreeMap<ChunkIndex, ChunkSubmissionStatus> {
self.submitted_chunks
.iter()
.map(|(id, c)| (*id, c.status))
.collect()
}
}
#[cw_serde]
pub struct PartialContractDealing {
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub data: PartialContractDealingData,
}
impl PartialContractDealing {
pub fn new(
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
data: PartialContractDealingData,
) -> Self {
PartialContractDealing {
dealing_index,
chunk_index,
data,
}
}
}
#[cw_serde]
pub struct DealingMetadataResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub metadata: Option<DealingMetadata>,
}
#[cw_serde]
pub struct DealingChunkResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub chunk: Option<PartialContractDealingData>,
}
#[cw_serde]
pub struct DealingChunkStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub status: ChunkSubmissionStatus,
}
#[cw_serde]
pub struct DealingStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub status: DealingStatus,
}
#[cw_serde]
pub struct DealingStatus {
pub has_metadata: bool,
pub fully_submitted: bool,
pub chunk_submission_status: BTreeMap<ChunkIndex, ChunkSubmissionStatus>,
}
impl From<Option<DealingMetadata>> for DealingStatus {
fn from(metadata: Option<DealingMetadata>) -> Self {
DealingStatus {
has_metadata: metadata.is_some(),
fully_submitted: metadata
.as_ref()
.map(|m| m.is_complete())
.unwrap_or_default(),
chunk_submission_status: metadata
.map(|m| m.submission_statuses())
.unwrap_or_default(),
}
}
}
#[cw_serde]
pub struct DealerDealingsStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub all_dealings_fully_submitted: bool,
pub dealing_submission_status: BTreeMap<DealingIndex, DealingStatus>,
}
impl DealerDealingsStatusResponse {
pub fn full_dealings(&self) -> usize {
self.dealing_submission_status
.values()
.filter(|s| s.fully_submitted)
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunking_dealings() {
const CHUNK_SIZE: usize = 512;
let test_cases = [
(CHUNK_SIZE - 10, CHUNK_SIZE, 1),
(CHUNK_SIZE, CHUNK_SIZE, 1),
(CHUNK_SIZE + 10, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2 + 1, CHUNK_SIZE, 3),
(CHUNK_SIZE * 10 + 42, CHUNK_SIZE, 11),
];
for (dealing_len, chunk_size, expected_chunks) in test_cases {
let chunks = DealingChunkInfo::construct(dealing_len, chunk_size);
assert_eq!(expected_chunks, chunks.len());
assert_eq!(
dealing_len as u64,
chunks.iter().map(|c| c.size).sum::<u64>()
);
let mut expected_last = dealing_len % chunk_size;
if expected_last == 0 {
expected_last = chunk_size;
}
assert_eq!(chunks.last().unwrap().size, expected_last as u64);
}
}
}
@@ -1,4 +1,8 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod dealer;
pub mod dealing;
pub mod event_attributes;
pub mod msg;
pub mod types;
@@ -1,23 +1,24 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealing::{DealingChunkInfo, PartialContractDealing};
use crate::types::{
DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, PartialContractDealing, TimeConfiguration,
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration,
};
use crate::verification_key::VerificationKeyShare;
use contracts_common::IdentityKey;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
#[cfg(feature = "schema")]
use crate::{
dealer::{
DealerDetailsResponse, DealingResponse, DealingStatusResponse, PagedDealerResponse,
PagedDealingsResponse,
dealer::{DealerDetailsResponse, PagedDealerResponse},
dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatusResponse,
},
types::{Epoch, InitialReplacementData, State},
verification_key::{PagedVKSharesResponse, VkShareResponse},
};
use contracts_common::IdentityKey;
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
@@ -44,8 +45,14 @@ pub enum ExecuteMsg {
resharing: bool,
},
CommitDealing {
dealing: PartialContractDealing,
CommitDealingsMetadata {
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
},
CommitDealingsChunk {
chunk: PartialContractDealing,
resharing: bool,
},
@@ -55,8 +62,7 @@ pub enum ExecuteMsg {
},
VerifyVerificationKeyShare {
// TODO: this should be using a String...
owner: Addr,
owner: String,
resharing: bool,
},
@@ -95,6 +101,16 @@ pub enum QueryMsg {
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(DealingMetadataResponse))]
GetDealingsMetadata {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
},
#[cfg_attr(feature = "schema", returns(DealerDealingsStatusResponse))]
GetDealerDealingsStatus { epoch_id: EpochId, dealer: String },
#[cfg_attr(feature = "schema", returns(DealingStatusResponse))]
GetDealingStatus {
epoch_id: EpochId,
@@ -102,19 +118,20 @@ pub enum QueryMsg {
dealing_index: DealingIndex,
},
#[cfg_attr(feature = "schema", returns(DealingResponse))]
GetDealing {
#[cfg_attr(feature = "schema", returns(DealingChunkStatusResponse))]
GetDealingChunkStatus {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
GetDealings {
#[cfg_attr(feature = "schema", returns(DealingChunkResponse))]
GetDealingChunk {
epoch_id: EpochId,
dealer: String,
limit: Option<u32>,
start_after: Option<DealingIndex>,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[cfg_attr(feature = "schema", returns(VkShareResponse))]
@@ -15,31 +15,11 @@ pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
pub type EpochId = u64;
pub type DealingIndex = u32;
pub type ContractDealing = ContractSafeBytes;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
#[cw_serde]
pub struct PartialContractDealing {
pub index: DealingIndex,
pub data: ContractDealing,
}
impl PartialContractDealing {
pub fn new(index: DealingIndex, data: ContractDealing) -> Self {
PartialContractDealing { index, data }
}
}
impl From<(DealingIndex, ContractDealing)> for PartialContractDealing {
fn from(value: (DealingIndex, ContractDealing)) -> Self {
PartialContractDealing {
index: value.0,
data: value.1,
}
}
}
// we really don't need to hold more data than that (even u8 would have been enough),
// but explicitly make it different type than `DealingIndex` so type system would detect any
// accidental misuses
pub type ChunkIndex = u16;
pub type PartialContractDealingData = ContractSafeBytes;
#[cw_serde]
pub struct InitialReplacementData {
@@ -272,4 +252,8 @@ impl EpochState {
pub fn is_final(&self) -> bool {
*self == EpochState::InProgress
}
pub fn is_in_progress(&self) -> bool {
matches!(self, EpochState::InProgress)
}
}
@@ -43,7 +43,10 @@ pub fn to_cosmos_msg(
multisig_addr: String,
expiration_time: Timestamp,
) -> StdResult<CosmosMsg> {
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare {
owner: owner.to_string(),
resharing,
};
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_dkg_addr,
msg: to_binary(&verify_vk_share_req)?,
@@ -67,7 +70,7 @@ pub fn to_cosmos_msg(
// DKG SAFETY:
// each legit verification proposal will only contain a single execute msg,
// if they have more than one, we can safely ignore it
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<String> {
if msgs.len() != 1 {
return None;
}
@@ -1,17 +1,17 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
// some sane upper-bound size on byte sizes
// currently set to 128 bytes
pub const MAX_DISPLAY_SIZE: usize = 128;
// TODO: if we are to use this for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
// helps to transfer bytes between contract boundary to decrease amount of data sent accross
// after it's put to `Binary`
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeBytes(pub Vec<u8>);
@@ -23,12 +23,24 @@ impl Deref for ContractSafeBytes {
}
}
impl DerefMut for ContractSafeBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Vec<u8>> for ContractSafeBytes {
fn from(value: Vec<u8>) -> Self {
ContractSafeBytes(value)
}
}
impl<'a> From<&'a [u8]> for ContractSafeBytes {
fn from(value: &'a [u8]) -> Self {
value.to_vec().into()
}
}
impl Display for ContractSafeBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if !self.0.is_empty() {
+3 -2
View File
@@ -8,12 +8,13 @@ documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bincode = "1.3.3"
bytes = "1.5.0"
nym-bin-common = { path = "../bin-common" }
nym-sphinx = { path = "../nymsphinx" }
rand = "0.8.5"
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["time"] }
tokio-util = { workspace = true, features = ["codec"] }
+123
View File
@@ -0,0 +1,123 @@
use std::time::Duration;
use bytes::{Buf, Bytes, BytesMut};
use tokio_util::codec::{Decoder, Encoder};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{0}")]
IO(#[from] std::io::Error),
}
pub const BUFFER_TIMEOUT: Duration = Duration::from_millis(20);
// TODO: increase this to make max out effective sphinx payload size. Sphinx packets also carry the
// MixAck so that's why we can't just use 2kb.
pub const MAX_PACKET_SIZE: usize = 1500;
// Each IP packet is prefixed by a 2 byte length prefix
const LENGTH_PREFIX_SIZE: usize = 2;
// Tokio codec for bundling multiple IP packets into one buffer that is at most 1500 bytes long.
// These packets are separated by a 2 byte length prefix. We need a timer so that we don't wait too
// long for the buffer to fill up, since this kills latency.
pub struct MultiIpPacketCodec {
buffer: BytesMut,
buffer_timeout: tokio::time::Interval,
}
impl MultiIpPacketCodec {
pub fn new(buffer_timeout: Duration) -> Self {
MultiIpPacketCodec {
buffer: BytesMut::new(),
buffer_timeout: tokio::time::interval(buffer_timeout),
}
}
// Append a packet to the buffer and return the buffer if it's full
pub fn append_packet(&mut self, packet: Bytes) -> Option<Bytes> {
let mut bundled_packets = BytesMut::new();
self.encode(packet, &mut bundled_packets).unwrap();
if bundled_packets.is_empty() {
None
} else {
// log::info!("Sphinx packet utilization: {:.2}", self.buffer.len() as f64 / MAX_PACKET_SIZE as f64);
Some(bundled_packets.freeze())
}
}
// Flush the current buffer and return it.
fn flush_current_buffer(&mut self) -> Bytes {
let mut output_buffer = BytesMut::new();
std::mem::swap(&mut output_buffer, &mut self.buffer);
output_buffer.freeze()
}
// Wait for the buffer_timeout to tick and then flush the buffer.
// This is useful when we want to send the buffer even if it's not full.
pub async fn buffer_timeout(&mut self) -> Option<Bytes> {
// Wait for buffer_timeout to tick
let _ = self.buffer_timeout.tick().await;
// Flush the buffer and return it
let packets = self.flush_current_buffer();
if packets.is_empty() {
None
} else {
Some(packets)
}
}
}
impl Encoder<Bytes> for MultiIpPacketCodec {
type Error = Error;
fn encode(&mut self, packet: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
if self.buffer.is_empty() {
self.buffer_timeout.reset();
}
let packet_size = packet.len();
if self.buffer.len() + packet_size + LENGTH_PREFIX_SIZE > MAX_PACKET_SIZE {
// If the packet doesn't fit in the buffer, send the buffer and then add it to the buffer
dst.extend_from_slice(&self.buffer);
self.buffer = BytesMut::new();
self.buffer_timeout.reset();
}
// Add the packet size
self.buffer
.extend_from_slice(&(packet_size as u16).to_be_bytes());
// Add the packet to the buffer
self.buffer.extend_from_slice(&packet);
Ok(())
}
}
impl Decoder for MultiIpPacketCodec {
type Item = Bytes;
type Error = Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if src.len() < LENGTH_PREFIX_SIZE {
// Not enough bytes to read the length prefix
return Ok(None);
}
let packet_size = u16::from_be_bytes([src[0], src[1]]) as usize;
if src.len() < packet_size + LENGTH_PREFIX_SIZE {
// Not enough bytes to read the packet
return Ok(None);
}
// Remove the length prefix
src.advance(LENGTH_PREFIX_SIZE);
// Read the packet
let packet = src.split_to(packet_size);
Ok(Some(packet.freeze()))
}
}
+4 -375
View File
@@ -1,319 +1,8 @@
use std::net::IpAddr;
pub mod codec;
pub mod request;
pub mod response;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
pub const CURRENT_VERSION: u8 = 1;
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketRequest {
pub version: u8,
pub data: IpPacketRequestData,
}
impl IpPacketRequest {
pub fn new_static_connect_request(
ip: IpAddr,
reply_to: Recipient,
reply_to_hops: Option<u8>,
reply_to_avg_mix_delays: Option<f64>,
) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
request_id,
ip,
reply_to,
reply_to_hops,
reply_to_avg_mix_delays,
}),
},
request_id,
)
}
pub fn new_dynamic_connect_request(
reply_to: Recipient,
reply_to_hops: Option<u8>,
reply_to_avg_mix_delays: Option<f64>,
) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest {
request_id,
reply_to,
reply_to_hops,
reply_to_avg_mix_delays,
}),
},
request_id,
)
}
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::Data(DataRequest { ip_packet }),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => Some(request.request_id),
IpPacketRequestData::DynamicConnect(request) => Some(request.request_id),
IpPacketRequestData::Data(_) => None,
}
}
pub fn recipient(&self) -> Option<&Recipient> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to),
IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to),
IpPacketRequestData::Data(_) => None,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum IpPacketRequestData {
StaticConnect(StaticConnectRequest),
DynamicConnect(DynamicConnectRequest),
Data(DataRequest),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct StaticConnectRequest {
pub request_id: u64,
pub ip: IpAddr,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DynamicConnectRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DataRequest {
pub ip_packet: bytes::Bytes,
}
// ---
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketResponse {
pub version: u8,
pub data: IpPacketResponseData,
}
impl IpPacketResponse {
pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
request_id,
reply_to,
reply: StaticConnectResponseReply::Success,
}),
}
}
pub fn new_static_connect_failure(
request_id: u64,
reply_to: Recipient,
reason: StaticConnectFailureReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
request_id,
reply_to,
reply: StaticConnectResponseReply::Failure(reason),
}),
}
}
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
request_id,
reply_to,
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
}),
}
}
pub fn new_dynamic_connect_failure(
request_id: u64,
reply_to: Recipient,
reason: DynamicConnectFailureReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
request_id,
reply_to,
reply: DynamicConnectResponseReply::Failure(reason),
}),
}
}
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
IpPacketResponseData::Data(_) => None,
}
}
pub fn recipient(&self) -> Option<&Recipient> {
match &self.data {
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
IpPacketResponseData::Data(_) => None,
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum IpPacketResponseData {
StaticConnect(StaticConnectResponse),
DynamicConnect(DynamicConnectResponse),
Data(DataResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StaticConnectResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: StaticConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StaticConnectResponseReply {
Success,
Failure(StaticConnectFailureReason),
}
impl StaticConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
StaticConnectResponseReply::Success => true,
StaticConnectResponseReply::Failure(_) => false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum StaticConnectFailureReason {
#[error("requested ip address is already in use")]
RequestedIpAlreadyInUse,
#[error("requested nym-address is already in use")]
RequestedNymAddressAlreadyInUse,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DynamicConnectResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: DynamicConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DynamicConnectResponseReply {
Success(DynamicConnectSuccess),
Failure(DynamicConnectFailureReason),
}
impl DynamicConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
DynamicConnectResponseReply::Success(_) => true,
DynamicConnectResponseReply::Failure(_) => false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DynamicConnectSuccess {
pub ip: IpAddr,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum DynamicConnectFailureReason {
#[error("requested nym-address is already in use")]
RequestedNymAddressAlreadyInUse,
#[error("no available ip address")]
NoAvailableIp,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataResponse {
pub ip_packet: bytes::Bytes,
}
pub const CURRENT_VERSION: u8 = 3;
fn make_bincode_serializer() -> impl bincode::Options {
use bincode::Options;
@@ -321,63 +10,3 @@ fn make_bincode_serializer() -> impl bincode::Options {
.with_big_endian()
.with_varint_encoding()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_size_of_request() {
let connect = IpPacketRequest {
version: 4,
data: IpPacketRequestData::StaticConnect(
StaticConnectRequest {
request_id: 123,
ip: IpAddr::from([10, 0, 0, 1]),
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
reply_to_hops: None,
reply_to_avg_mix_delays: None,
},
)
};
assert_eq!(connect.to_bytes().unwrap().len(), 107);
}
#[test]
fn check_size_of_data() {
let data = IpPacketRequest {
version: 4,
data: IpPacketRequestData::Data(DataRequest {
ip_packet: bytes::Bytes::from(vec![1u8; 32]),
}),
};
assert_eq!(data.to_bytes().unwrap().len(), 35);
}
#[test]
fn serialize_and_deserialize_data_request() {
let data = IpPacketRequest {
version: 4,
data: IpPacketRequestData::Data(DataRequest {
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
}),
};
let serialized = data.to_bytes().unwrap();
let deserialized = IpPacketRequest::from_reconstructed_message(
&nym_sphinx::receiver::ReconstructedMessage {
message: serialized,
sender_tag: None,
},
)
.unwrap();
assert_eq!(deserialized.version, 4);
assert_eq!(
deserialized.data,
IpPacketRequestData::Data(DataRequest {
ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
})
);
}
}
+269
View File
@@ -0,0 +1,269 @@
use std::net::IpAddr;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, CURRENT_VERSION};
fn generate_random() -> u64 {
use rand::RngCore;
let mut rng = rand::rngs::OsRng;
rng.next_u64()
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketRequest {
pub version: u8,
pub data: IpPacketRequestData,
}
impl IpPacketRequest {
pub fn new_static_connect_request(
ip: IpAddr,
reply_to: Recipient,
reply_to_hops: Option<u8>,
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::StaticConnect(StaticConnectRequest {
request_id,
ip,
reply_to,
reply_to_hops,
reply_to_avg_mix_delays,
buffer_timeout,
}),
},
request_id,
)
}
pub fn new_dynamic_connect_request(
reply_to: Recipient,
reply_to_hops: Option<u8>,
reply_to_avg_mix_delays: Option<f64>,
buffer_timeout: Option<u64>,
) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest {
request_id,
reply_to,
reply_to_hops,
reply_to_avg_mix_delays,
buffer_timeout,
}),
},
request_id,
)
}
pub fn new_disconnect_request(reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::Disconnect(DisconnectRequest {
request_id,
reply_to,
}),
},
request_id,
)
}
pub fn new_data_request(ip_packets: bytes::Bytes) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketRequestData::Data(DataRequest { ip_packets }),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => Some(request.request_id),
IpPacketRequestData::DynamicConnect(request) => Some(request.request_id),
IpPacketRequestData::Disconnect(request) => Some(request.request_id),
IpPacketRequestData::Data(_) => None,
IpPacketRequestData::Ping(request) => Some(request.request_id),
IpPacketRequestData::Health(request) => Some(request.request_id),
}
}
pub fn recipient(&self) -> Option<&Recipient> {
match &self.data {
IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to),
IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to),
IpPacketRequestData::Disconnect(request) => Some(&request.reply_to),
IpPacketRequestData::Data(_) => None,
IpPacketRequestData::Ping(request) => Some(&request.reply_to),
IpPacketRequestData::Health(request) => Some(&request.reply_to),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum IpPacketRequestData {
StaticConnect(StaticConnectRequest),
DynamicConnect(DynamicConnectRequest),
Disconnect(DisconnectRequest),
Data(DataRequest),
Ping(PingRequest),
Health(HealthRequest),
}
// A static connect request is when the client provides the internal IP address it will use on the
// ip packet router.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct StaticConnectRequest {
pub request_id: u64,
pub ip: IpAddr,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
// with ip packets.
pub buffer_timeout: Option<u64>,
}
// A dynamic connect request is when the client does not provide the internal IP address it will use
// on the ip packet router, and instead requests one to be assigned to it.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DynamicConnectRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
// ip packet router.
pub reply_to_avg_mix_delays: Option<f64>,
// The maximum time in milliseconds the IPR should wait when filling up a mix packet
// with ip packets.
pub buffer_timeout: Option<u64>,
}
// A disconnect request is when the client wants to disconnect from the ip packet router and free
// up the allocated IP address.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DisconnectRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
}
// A data request is when the client wants to send an IP packet to a destination.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct DataRequest {
pub ip_packets: bytes::Bytes,
}
// A ping request is when the client wants to check if the ip packet router is still alive.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PingRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct HealthRequest {
pub request_id: u64,
// The nym-address the response should be sent back to
pub reply_to: Recipient,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn check_size_of_request() {
let connect = IpPacketRequest {
version: 4,
data: IpPacketRequestData::StaticConnect(
StaticConnectRequest {
request_id: 123,
ip: IpAddr::from([10, 0, 0, 1]),
reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(),
reply_to_hops: None,
reply_to_avg_mix_delays: None,
buffer_timeout: None,
},
)
};
assert_eq!(connect.to_bytes().unwrap().len(), 108);
}
#[test]
fn check_size_of_data() {
let data = IpPacketRequest {
version: 4,
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1u8; 32]),
}),
};
assert_eq!(data.to_bytes().unwrap().len(), 35);
}
#[test]
fn serialize_and_deserialize_data_request() {
let data = IpPacketRequest {
version: 4,
data: IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
}),
};
let serialized = data.to_bytes().unwrap();
let deserialized = IpPacketRequest::from_reconstructed_message(
&nym_sphinx::receiver::ReconstructedMessage {
message: serialized,
sender_tag: None,
},
)
.unwrap();
assert_eq!(deserialized.version, 4);
assert_eq!(
deserialized.data,
IpPacketRequestData::Data(DataRequest {
ip_packets: bytes::Bytes::from(vec![1, 2, 4, 2, 5]),
})
);
}
}
+362
View File
@@ -0,0 +1,362 @@
use std::net::IpAddr;
use nym_sphinx::addressing::clients::Recipient;
use serde::{Deserialize, Serialize};
use crate::{make_bincode_serializer, CURRENT_VERSION};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct IpPacketResponse {
pub version: u8,
pub data: IpPacketResponseData,
}
impl IpPacketResponse {
pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
request_id,
reply_to,
reply: StaticConnectResponseReply::Success,
}),
}
}
pub fn new_static_connect_failure(
request_id: u64,
reply_to: Recipient,
reason: StaticConnectFailureReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::StaticConnect(StaticConnectResponse {
request_id,
reply_to,
reply: StaticConnectResponseReply::Failure(reason),
}),
}
}
pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
request_id,
reply_to,
reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }),
}),
}
}
pub fn new_dynamic_connect_failure(
request_id: u64,
reply_to: Recipient,
reason: DynamicConnectFailureReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse {
request_id,
reply_to,
reply: DynamicConnectResponseReply::Failure(reason),
}),
}
}
pub fn new_disconnect_success(request_id: u64, reply_to: Recipient) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Disconnect(DisconnectResponse {
request_id,
reply_to,
reply: DisconnectResponseReply::Success,
}),
}
}
pub fn new_disconnect_failure(
request_id: u64,
reply_to: Recipient,
reason: DisconnectFailureReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Disconnect(DisconnectResponse {
request_id,
reply_to,
reply: DisconnectResponseReply::Failure(reason),
}),
}
}
pub fn new_unrequested_disconnect(
reply_to: Recipient,
reason: UnrequestedDisconnectReason,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::UnrequestedDisconnect(UnrequestedDisconnect {
reply_to,
reason,
}),
}
}
pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Data(DataResponse { ip_packet }),
}
}
pub fn new_version_mismatch(
request_id: u64,
reply_to: Recipient,
request_version: u8,
our_version: u8,
) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Error(ErrorResponse {
request_id,
reply_to,
reply: ErrorResponseReply::VersionMismatch {
request_version,
response_version: our_version,
},
}),
}
}
pub fn new_data_error_response(reply_to: Recipient, reply: ErrorResponseReply) -> Self {
Self {
version: CURRENT_VERSION,
data: IpPacketResponseData::Error(ErrorResponse {
request_id: 0,
reply_to,
reply,
}),
}
}
pub fn id(&self) -> Option<u64> {
match &self.data {
IpPacketResponseData::StaticConnect(response) => Some(response.request_id),
IpPacketResponseData::DynamicConnect(response) => Some(response.request_id),
IpPacketResponseData::Disconnect(response) => Some(response.request_id),
IpPacketResponseData::UnrequestedDisconnect(_) => None,
IpPacketResponseData::Data(_) => None,
IpPacketResponseData::Pong(response) => Some(response.request_id),
IpPacketResponseData::Health(response) => Some(response.request_id),
IpPacketResponseData::Error(response) => Some(response.request_id),
}
}
pub fn recipient(&self) -> Option<&Recipient> {
match &self.data {
IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to),
IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to),
IpPacketResponseData::Disconnect(response) => Some(&response.reply_to),
IpPacketResponseData::UnrequestedDisconnect(response) => Some(&response.reply_to),
IpPacketResponseData::Data(_) => None,
IpPacketResponseData::Pong(response) => Some(&response.reply_to),
IpPacketResponseData::Health(response) => Some(&response.reply_to),
IpPacketResponseData::Error(response) => Some(&response.reply_to),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum IpPacketResponseData {
// Response for a static connect request
StaticConnect(StaticConnectResponse),
// Response for a dynamic connect request
DynamicConnect(DynamicConnectResponse),
// Response for a disconnect initiqated by the client
Disconnect(DisconnectResponse),
// Message from the server that the client got disconnected without the client initiating it
UnrequestedDisconnect(UnrequestedDisconnect),
// Response to a data request
Data(DataResponse),
// Response to ping request
Pong(PongResponse),
// Response for a health request
Health(HealthResponse),
// Error response
Error(ErrorResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StaticConnectResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: StaticConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StaticConnectResponseReply {
Success,
Failure(StaticConnectFailureReason),
}
impl StaticConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
StaticConnectResponseReply::Success => true,
StaticConnectResponseReply::Failure(_) => false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum StaticConnectFailureReason {
#[error("requested ip address is already in use")]
RequestedIpAlreadyInUse,
#[error("requested nym-address is already in use")]
RequestedNymAddressAlreadyInUse,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DynamicConnectResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: DynamicConnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DynamicConnectResponseReply {
Success(DynamicConnectSuccess),
Failure(DynamicConnectFailureReason),
}
impl DynamicConnectResponseReply {
pub fn is_success(&self) -> bool {
match self {
DynamicConnectResponseReply::Success(_) => true,
DynamicConnectResponseReply::Failure(_) => false,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DynamicConnectSuccess {
pub ip: IpAddr,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum DynamicConnectFailureReason {
#[error("requested nym-address is already in use")]
RequestedNymAddressAlreadyInUse,
#[error("no available ip address")]
NoAvailableIp,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DisconnectResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: DisconnectResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DisconnectResponseReply {
Success,
Failure(DisconnectFailureReason),
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum DisconnectFailureReason {
#[error("requested nym-address is not currently connected")]
RequestedNymAddressNotConnected,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UnrequestedDisconnect {
pub reply_to: Recipient,
pub reason: UnrequestedDisconnectReason,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum UnrequestedDisconnectReason {
#[error("client mixnet traffic timeout")]
ClientMixnetTrafficTimeout,
#[error("client tun traffic timeout")]
ClientTunTrafficTimeout,
#[error("{0}")]
Other(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DataResponse {
pub ip_packet: bytes::Bytes,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PongResponse {
pub request_id: u64,
pub reply_to: Recipient,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: HealthResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HealthResponseReply {
// Return the binary build information of the IPR
pub build_info: nym_bin_common::build_information::BinaryBuildInformationOwned,
// Return if the IPR has performed a successful routing test.
pub routable: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ErrorResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: ErrorResponseReply,
}
#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)]
pub enum ErrorResponseReply {
#[error("{msg}")]
Generic { msg: String },
#[error(
"version mismatch: response is v{request_version} and response is v{response_version}"
)]
VersionMismatch {
request_version: u8,
response_version: u8,
},
#[error("destination failed exit policy filter check: {dst}")]
ExitPolicyFilterCheckFailed { dst: String },
}
+1
View File
@@ -24,6 +24,7 @@ tokio = { version = "1.24.1", features = [
tokio-util = { workspace = true, features = ["codec"] }
url = { workspace = true }
thiserror = { workspace = true }
fastbloom-rs = { git = "https://github.com/simonwicky/fastbloom"}
## tracing
tracing = { version = "0.1.37", optional = true }
@@ -28,4 +28,7 @@ pub enum MixProcessingError {
#[error("failed to process received outfox packet: {0}")]
OutfoxProcessingError(#[from] OutfoxError),
#[error("this packet was already processed, it's a replay")]
ReplayedPacketDetected,
}
@@ -3,3 +3,4 @@
pub mod error;
pub mod processor;
pub mod replay_detection;
@@ -3,6 +3,7 @@
use crate::measure;
use crate::packet_processor::error::MixProcessingError;
use crate::packet_processor::replay_detection::ReplayDetector;
use log::*;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
@@ -40,6 +41,9 @@ pub enum MixProcessingResult {
pub struct SphinxPacketProcessor {
/// Private sphinx key of this node required to unwrap received sphinx packet.
sphinx_key: Arc<PrivateKey>,
/// Detector of replay attack
replay_detector: ReplayDetector,
}
impl SphinxPacketProcessor {
@@ -47,6 +51,7 @@ impl SphinxPacketProcessor {
pub fn new(sphinx_key: PrivateKey) -> Self {
SphinxPacketProcessor {
sphinx_key: Arc::new(sphinx_key),
replay_detector: ReplayDetector::new(),
}
}
@@ -184,7 +189,7 @@ impl SphinxPacketProcessor {
match packet {
NymProcessedPacket::Sphinx(packet) => {
match packet {
ProcessedPacket::ForwardHop(packet, address, delay) => self
ProcessedPacket::ForwardHop(packet, address, delay, _) => self
.process_forward_hop(
NymPacket::Sphinx(*packet),
address,
@@ -193,12 +198,13 @@ impl SphinxPacketProcessor {
),
// right now there's no use for the surb_id included in the header - probably it should get removed from the
// sphinx all together?
ProcessedPacket::FinalHop(destination, _, payload) => self.process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
),
ProcessedPacket::FinalHop(destination, _, payload, _) => self
.process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
),
}
}
NymProcessedPacket::Outfox(packet) => {
@@ -239,6 +245,10 @@ impl SphinxPacketProcessor {
// unwrap the sphinx packet and if possible and appropriate, cache keys
let processed_packet = self.perform_initial_unwrapping(received)?;
//check for replay attack
self.replay_detector
.handle_replay_tag(&processed_packet.replay_tag())?;
// for forward packets, extract next hop and set delay (but do NOT delay here)
// for final packets, extract SURBAck
let final_processing_result =
@@ -0,0 +1,76 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::packet_processor::error::MixProcessingError;
use fastbloom_rs::{BloomFilter, FilterBuilder, Membership};
use std::sync::{Arc, Mutex};
const BLOOM_FILTER_SIZE: u64 = 10_000_000;
const FP_RATE: f64 = 1e-4;
//alias for convenience
type ReplayTag = [u8];
#[derive(Clone, Debug)]
pub struct ReplayDetector(Arc<Mutex<ReplayDetectorInner>>);
impl ReplayDetector {
pub fn new() -> Self {
ReplayDetector(Arc::new(Mutex::new(ReplayDetectorInner::new())))
}
//check if secret has been seen already
//if no, return Ok
//if yes, add the secret to the list, then return an error
pub fn handle_replay_tag(&self, replay_tag: &ReplayTag) -> Result<(), MixProcessingError> {
match self.0.lock() {
Ok(mut inner) => {
if !inner.lookup_then_insert(replay_tag) {
Ok(())
} else {
Err(MixProcessingError::ReplayedPacketDetected)
}
}
Err(err) => {
log::warn!("Failed to handle replay_tag : {err}");
Ok(()) //what is the sensible thing to do, if the lock is poisoned? Reset the filter ?
}
}
}
}
impl Default for ReplayDetector {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct ReplayDetectorInner {
filter: BloomFilter,
}
impl ReplayDetectorInner {
pub fn new() -> Self {
ReplayDetectorInner {
filter: FilterBuilder::new(BLOOM_FILTER_SIZE, FP_RATE).build_bloom_filter(),
}
}
pub fn lookup_then_insert(&mut self, replay_tag: &ReplayTag) -> bool {
self.filter.contains_then_add(replay_tag)
}
}
#[cfg(test)]
mod replay_detector_test {
use super::*;
#[test]
fn handle_replay_tag_correctly_detects_replay() {
let replay_detector = ReplayDetector::new();
let replay_tag = b"Hello World!";
assert!(replay_detector.handle_replay_tag(replay_tag).is_ok()); //first insert is fine
assert!(replay_detector.handle_replay_tag(replay_tag).is_err()); //second is not
}
}
+1
View File
@@ -11,6 +11,7 @@ repository.workspace = true
cfg-if = { workspace = true }
dotenvy = { workspace = true }
hex-literal = "0.3.3"
log = { workspace = true }
once_cell = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"]}
+25 -3
View File
@@ -400,11 +400,25 @@ fn fix_deprecated_environmental_variables() {
}
}
// Read the variables from the file and log what the corresponding values in the environment are.
fn print_env_vars_with_keys_in_file<P: AsRef<Path> + Copy>(config_env_file: P) {
let items = dotenvy::from_path_iter(config_env_file)
.expect("Invalid path to environment configuration file");
for item in items {
let (key, val) = item.expect("Invalid item in environment configuration file");
log::debug!("{}: {}", key, val);
}
}
pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
match std::env::var(var_names::CONFIGURED) {
// if the configuration is not already set in the env vars
Err(std::env::VarError::NotPresent) => {
if let Some(config_env_file) = config_env_file {
if let Some(config_env_file) = &config_env_file {
log::debug!(
"Loading environment variables from {:?}",
config_env_file.as_ref()
);
dotenvy::from_path(config_env_file)
.expect("Invalid path to environment configuration file");
fix_deprecated_environmental_variables();
@@ -412,17 +426,25 @@ pub fn setup_env<P: AsRef<Path>>(config_env_file: Option<P>) {
// 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
log::debug!("Loading mainnet defaults");
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
Err(_) => {
log::debug!("Environment variables already set. Using them");
crate::mainnet::export_to_env()
}
_ => {
fix_deprecated_environmental_variables();
}
}
// if we haven't explicitly defined any of the constants, fallback to defaults
crate::mainnet::export_to_env_if_not_set()
crate::mainnet::export_to_env_if_not_set();
if let Some(config_env_file) = &config_env_file {
print_env_vars_with_keys_in_file(config_env_file);
}
}
// Name of the event triggered by the eth contract. If the event name is changed,
+9 -2
View File
@@ -239,8 +239,15 @@ impl NymMessage {
let (packets_used, space_left) =
chunking::number_of_required_fragments(total_required_bytes, plaintext_per_packet);
let wasted_space = space_left as f32 / (bytes.len() + 1 + space_left) as f32;
log::trace!("Padding {self_display}: {} of raw plaintext bytes are required. They're going to be put into {packets_used} sphinx packets with {space_left} bytes of leftover space. {wasted_space}% of packet capacity is going to be wasted.", bytes.len() + 1);
let wasted_space_percentage =
(space_left as f32 / (bytes.len() + 1 + space_left) as f32) * 100.0;
log::trace!(
"Padding {self_display}: {} of raw plaintext bytes are required. \
They're going to be put into {packets_used} sphinx packets with {space_left} bytes \
of leftover space. {wasted_space_percentage:.1}% of packet capacity is going to \
be wasted.",
bytes.len() + 1
);
bytes
.into_iter()
+2 -1
View File
@@ -8,9 +8,10 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
sphinx-packet = { version = "0.1.0", optional = true }
#sphinx-packet = { version = "0.1.0", optional = true }
nym-outfox = { path = "../../../nym-outfox", optional = true }
thiserror = { workspace = true }
sphinx-packet = { git = "https://github.com/nymtech/sphinx.git", branch = "simon/replay_tag", optional = true}
[features]
default = ["sphinx"]
+10
View File
@@ -9,6 +9,7 @@ pub use nym_outfox::{
// re-exporting types and constants available in sphinx
#[cfg(feature = "outfox")]
use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket};
use sphinx_packet::header::keys::ReplayTag;
#[cfg(feature = "sphinx")]
pub use sphinx_packet::{
constants::{
@@ -57,6 +58,15 @@ pub enum NymProcessedPacket {
Outfox(OutfoxProcessedPacket),
}
impl NymProcessedPacket {
pub fn replay_tag(&self) -> ReplayTag {
match self {
NymProcessedPacket::Sphinx(sphinx) => sphinx.replay_tag(),
NymProcessedPacket::Outfox(_) => todo!(), //SW temporary while I add a replay tag to outfox
}
}
}
impl fmt::Debug for NymPacket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[allow(unreachable_patterns)]
+420 -96
View File
@@ -140,17 +140,51 @@
{
"type": "object",
"required": [
"commit_dealing"
"commit_dealings_metadata"
],
"properties": {
"commit_dealing": {
"commit_dealings_metadata": {
"type": "object",
"required": [
"dealing",
"chunks",
"dealing_index",
"resharing"
],
"properties": {
"dealing": {
"chunks": {
"type": "array",
"items": {
"$ref": "#/definitions/DealingChunkInfo"
}
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"resharing": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"commit_dealings_chunk"
],
"properties": {
"commit_dealings_chunk": {
"type": "object",
"required": [
"chunk",
"resharing"
],
"properties": {
"chunk": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
@@ -201,7 +235,7 @@
],
"properties": {
"owner": {
"$ref": "#/definitions/Addr"
"type": "string"
},
"resharing": {
"type": "boolean"
@@ -240,10 +274,6 @@
}
],
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
"items": {
@@ -252,17 +282,37 @@
"minimum": 0.0
}
},
"DealingChunkInfo": {
"type": "object",
"required": [
"size"
],
"properties": {
"size": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"PartialContractDealing": {
"type": "object",
"required": [
"chunk_index",
"data",
"index"
"dealing_index"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
@@ -407,6 +457,66 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings_metadata"
],
"properties": {
"get_dealings_metadata": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealer_dealings_status"
],
"properties": {
"get_dealer_dealings_status": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -443,17 +553,23 @@
{
"type": "object",
"required": [
"get_dealing"
"get_dealing_chunk_status"
],
"properties": {
"get_dealing": {
"get_dealing_chunk_status": {
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"type": "string"
},
@@ -476,39 +592,35 @@
{
"type": "object",
"required": [
"get_dealings"
"get_dealing_chunk"
],
"properties": {
"get_dealings": {
"get_dealing_chunk": {
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -909,6 +1021,80 @@
"format": "uint64",
"minimum": 0.0
},
"get_dealer_dealings_status": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealerDealingsStatusResponse",
"type": "object",
"required": [
"all_dealings_fully_submitted",
"dealer",
"dealing_submission_status",
"epoch_id"
],
"properties": {
"all_dealings_fully_submitted": {
"type": "boolean"
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DealingStatus"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingStatus": {
"type": "object",
"required": [
"chunk_submission_status",
"fully_submitted",
"has_metadata"
],
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
},
"has_metadata": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
},
"get_dealer_details": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealerDetailsResponse",
@@ -977,20 +1163,18 @@
}
}
},
"get_dealing": {
"get_dealing_chunk": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingResponse",
"title": "DealingChunkResponse",
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing": {
"chunk": {
"anyOf": [
{
"$ref": "#/definitions/ContractSafeBytes"
@@ -1000,6 +1184,14 @@
}
]
},
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
@@ -1027,6 +1219,62 @@
}
}
},
"get_dealing_chunk_status": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingChunkStatusResponse",
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id",
"status"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"status": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
"get_dealing_status": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingStatusResponse",
@@ -1034,7 +1282,79 @@
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id",
"status"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"status": {
"$ref": "#/definitions/DealingStatus"
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingStatus": {
"type": "object",
"required": [
"chunk_submission_status",
"fully_submitted",
"has_metadata"
],
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
},
"has_metadata": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
},
"get_dealings_metadata": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingMetadataResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
@@ -1046,55 +1366,20 @@
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
},
"get_dealings": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"type": "object",
"required": [
"dealer",
"dealings",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/PartialContractDealing"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/DealingMetadata"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
@@ -1103,28 +1388,67 @@
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"PartialContractDealing": {
"DealingChunkInfo": {
"type": "object",
"required": [
"data",
"index"
"size"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"size": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingMetadata": {
"type": "object",
"required": [
"dealing_index",
"submitted_chunks"
],
"properties": {
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"submitted_chunks": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/SubmittedChunk"
}
}
},
"additionalProperties": false
},
"SubmittedChunk": {
"type": "object",
"required": [
"info",
"status"
],
"properties": {
"info": {
"$ref": "#/definitions/DealingChunkInfo"
},
"status": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"additionalProperties": false
+61 -11
View File
@@ -51,17 +51,51 @@
{
"type": "object",
"required": [
"commit_dealing"
"commit_dealings_metadata"
],
"properties": {
"commit_dealing": {
"commit_dealings_metadata": {
"type": "object",
"required": [
"dealing",
"chunks",
"dealing_index",
"resharing"
],
"properties": {
"dealing": {
"chunks": {
"type": "array",
"items": {
"$ref": "#/definitions/DealingChunkInfo"
}
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"resharing": {
"type": "boolean"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"commit_dealings_chunk"
],
"properties": {
"commit_dealings_chunk": {
"type": "object",
"required": [
"chunk",
"resharing"
],
"properties": {
"chunk": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
@@ -112,7 +146,7 @@
],
"properties": {
"owner": {
"$ref": "#/definitions/Addr"
"type": "string"
},
"resharing": {
"type": "boolean"
@@ -151,10 +185,6 @@
}
],
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
"items": {
@@ -163,17 +193,37 @@
"minimum": 0.0
}
},
"DealingChunkInfo": {
"type": "object",
"required": [
"size"
],
"properties": {
"size": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"PartialContractDealing": {
"type": "object",
"required": [
"chunk_index",
"data",
"index"
"dealing_index"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
+82 -20
View File
@@ -133,6 +133,66 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings_metadata"
],
"properties": {
"get_dealings_metadata": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealer_dealings_status"
],
"properties": {
"get_dealer_dealings_status": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -169,17 +229,23 @@
{
"type": "object",
"required": [
"get_dealing"
"get_dealing_chunk_status"
],
"properties": {
"get_dealing": {
"get_dealing_chunk_status": {
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"type": "string"
},
@@ -202,39 +268,35 @@
{
"type": "object",
"required": [
"get_dealings"
"get_dealing_chunk"
],
"properties": {
"get_dealings": {
"get_dealing_chunk": {
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
@@ -0,0 +1,74 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealerDealingsStatusResponse",
"type": "object",
"required": [
"all_dealings_fully_submitted",
"dealer",
"dealing_submission_status",
"epoch_id"
],
"properties": {
"all_dealings_fully_submitted": {
"type": "boolean"
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/DealingStatus"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingStatus": {
"type": "object",
"required": [
"chunk_submission_status",
"fully_submitted",
"has_metadata"
],
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
},
"has_metadata": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
}
@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingChunkResponse",
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"chunk": {
"anyOf": [
{
"$ref": "#/definitions/ContractSafeBytes"
},
{
"type": "null"
}
]
},
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
}
@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingChunkStatusResponse",
"type": "object",
"required": [
"chunk_index",
"dealer",
"dealing_index",
"epoch_id",
"status"
],
"properties": {
"chunk_index": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"status": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
@@ -5,8 +5,8 @@
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id"
"epoch_id",
"status"
],
"properties": {
"dealer": {
@@ -17,13 +17,13 @@
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"status": {
"$ref": "#/definitions/DealingStatus"
}
},
"additionalProperties": false,
@@ -31,6 +31,43 @@
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingStatus": {
"type": "object",
"required": [
"chunk_submission_status",
"fully_submitted",
"has_metadata"
],
"properties": {
"chunk_submission_status": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"fully_submitted": {
"type": "boolean"
},
"has_metadata": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
}
@@ -0,0 +1,107 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingMetadataResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"metadata": {
"anyOf": [
{
"$ref": "#/definitions/DealingMetadata"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"ChunkSubmissionStatus": {
"type": "object",
"properties": {
"submission_height": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingChunkInfo": {
"type": "object",
"required": [
"size"
],
"properties": {
"size": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
},
"DealingMetadata": {
"type": "object",
"required": [
"dealing_index",
"submitted_chunks"
],
"properties": {
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"submitted_chunks": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/SubmittedChunk"
}
}
},
"additionalProperties": false
},
"SubmittedChunk": {
"type": "object",
"required": [
"info",
"status"
],
"properties": {
"info": {
"$ref": "#/definitions/DealingChunkInfo"
},
"status": {
"$ref": "#/definitions/ChunkSubmissionStatus"
}
},
"additionalProperties": false
}
}
}
+44 -15
View File
@@ -5,8 +5,11 @@ use crate::dealers::queries::{
query_current_dealers_paged, query_dealer_details, query_past_dealers_paged,
};
use crate::dealers::transactions::try_add_dealer;
use crate::dealings::queries::{query_dealing, query_dealing_status, query_dealings_paged};
use crate::dealings::transactions::try_commit_dealings;
use crate::dealings::queries::{
query_dealer_dealings_status, query_dealing_chunk, query_dealing_chunk_status,
query_dealing_metadata, query_dealing_status,
};
use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata};
use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
@@ -100,8 +103,13 @@ pub fn execute(
announce_address,
resharing,
),
ExecuteMsg::CommitDealing { dealing, resharing } => {
try_commit_dealings(deps, info, dealing, resharing)
ExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
} => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing),
ExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
try_commit_dealings_chunk(deps, env, info, chunk, resharing)
}
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
@@ -132,6 +140,19 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetPastDealers { limit, start_after } => {
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
} => to_binary(&query_dealing_metadata(
deps,
epoch_id,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
to_binary(&query_dealer_dealings_status(deps, epoch_id, dealer)?)?
}
QueryMsg::GetDealingStatus {
epoch_id,
dealer,
@@ -142,22 +163,29 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealing {
QueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
dealing_index,
} => to_binary(&query_dealing(deps, epoch_id, dealer, dealing_index)?)?,
QueryMsg::GetDealings {
epoch_id,
dealer,
limit,
start_after,
} => to_binary(&query_dealings_paged(
chunk_index,
} => to_binary(&query_dealing_chunk_status(
deps,
epoch_id,
dealer,
start_after,
limit,
dealing_index,
chunk_index,
)?)?,
QueryMsg::GetDealingChunk {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => to_binary(&query_dealing_chunk(
deps,
epoch_id,
dealer,
dealing_index,
chunk_index,
)?)?,
QueryMsg::GetVerificationKey { owner, epoch_id } => {
to_binary(&query_vk_share(deps, owner, epoch_id)?)?
@@ -207,8 +235,9 @@ mod tests {
use cosmwasm_std::{coins, Addr};
use cw4::Member;
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use nym_coconut_dkg_common::msg::ExecuteMsg::{InitiateDkg, RegisterDealer};
use nym_coconut_dkg_common::types::{NodeIndex, DEFAULT_DEALINGS};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
fn instantiate_with_group(app: &mut App, members: &[Addr]) -> Addr {
+140 -216
View File
@@ -1,17 +1,63 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealings::storage;
use crate::dealings::storage::StoredDealing;
use crate::dealings::storage::{StoredDealing, DEALINGS_METADATA};
use crate::state::storage::STATE;
use cosmwasm_std::{Deps, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::dealer::{
DealingResponse, DealingStatusResponse, PagedDealingsResponse,
use nym_coconut_dkg_common::dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatus, DealingStatusResponse,
};
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId};
use std::collections::BTreeMap;
// this does almost the same as query_dealing but doesn't return the actual dealing to make it easier on the validator
// so it wouldn't need to deal with the deserialization
/// Get the metadata associated with the particular dealing
pub fn query_dealing_metadata(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingMetadataResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
Ok(DealingMetadataResponse {
epoch_id,
dealer,
dealing_index,
metadata,
})
}
/// Get the status of all dealings of particular dealer for given epoch.
pub fn query_dealer_dealings_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
) -> StdResult<DealerDealingsStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let state = STATE.load(deps.storage)?;
let mut dealing_submission_status: BTreeMap<DealingIndex, DealingStatus> = BTreeMap::new();
// Since our key size is in single digit range, querying all of this at once on chain is fine
for dealing_index in 0..state.key_size {
let metadata =
DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
dealing_submission_status.insert(dealing_index, metadata.into());
}
Ok(DealerDealingsStatusResponse {
epoch_id,
dealer,
all_dealings_fully_submitted: dealing_submission_status
.values()
.all(|d| d.fully_submitted),
dealing_submission_status,
})
}
/// Get the status of particular dealing, i.e. whether it has been fully submitted.
pub fn query_dealing_status(
deps: Deps<'_>,
epoch_id: EpochId,
@@ -19,101 +65,119 @@ pub fn query_dealing_status(
dealing_index: DealingIndex,
) -> StdResult<DealingStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let dealing_submitted = StoredDealing::exists(deps.storage, epoch_id, &dealer, dealing_index);
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
Ok(DealingStatusResponse {
epoch_id,
dealer,
dealing_index,
dealing_submitted,
status: metadata.into(),
})
}
pub fn query_dealing(
/// Get the status of particular chunk, i.e. whether (and when) it has been fully submitted.
pub fn query_dealing_chunk_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingResponse> {
chunk_index: ChunkIndex,
) -> StdResult<DealingChunkStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let dealing = StoredDealing::read(deps.storage, epoch_id, &dealer, dealing_index);
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
Ok(DealingResponse {
let status = metadata
.as_ref()
.and_then(|m| m.submitted_chunks.get(&chunk_index))
.map(|&c| c.status)
.unwrap_or_default();
Ok(DealingChunkStatusResponse {
epoch_id,
dealer,
dealing_index,
dealing,
chunk_index,
status,
})
}
pub fn query_dealings_paged(
/// Get the particular chunk of the dealing.
pub fn query_dealing_chunk(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
start_after: Option<DealingIndex>,
limit: Option<u32>,
) -> StdResult<PagedDealingsResponse> {
let limit = limit
.unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALINGS_PAGE_MAX_LIMIT);
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> StdResult<DealingChunkResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let start = start_after.map(Bound::exclusive);
let chunk = StoredDealing::read(deps.storage, epoch_id, &dealer, dealing_index, chunk_index);
let dealings = StoredDealing::prefix_range(deps.storage, (epoch_id, &dealer), start)
.take(limit as usize)
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = dealings.last().map(|dealing| dealing.index);
Ok(PagedDealingsResponse::new(
Ok(DealingChunkResponse {
epoch_id,
dealer,
dealings,
start_next_after,
))
dealing_index,
chunk_index,
chunk,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT};
use crate::support::tests::fixtures::{dealing_bytes_fixture, partial_dealing_fixture};
use crate::support::tests::helpers::init_contract;
use cosmwasm_std::{Addr, DepsMut};
use nym_coconut_dkg_common::types::PartialContractDealing;
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
fn fill_dealings(deps: DepsMut<'_>, epoch: EpochId, dealers: usize, key_size: u32) {
#[allow(unused)]
fn fill_dealings(
deps: DepsMut<'_>,
epoch: EpochId,
dealers: usize,
key_size: u32,
chunks: u16,
) {
for i in 0..dealers {
let dealer = Addr::unchecked(format!("dealer{i}"));
for dealing_index in 0..key_size {
StoredDealing::save(
deps.storage,
epoch,
&dealer,
PartialContractDealing {
index: dealing_index,
data: dealing_bytes_fixture(),
},
)
let data = dealing_bytes_fixture();
let chunks = data.0.chunks(data.len() / chunks as usize);
let mut chunk_infos = Vec::new();
for (chunk_index, chunk) in chunks.enumerate() {
chunk_infos.push(DealingChunkInfo {
size: chunk.len() as u64,
});
StoredDealing::save(
deps.storage,
epoch,
&dealer,
PartialContractDealing {
dealing_index,
chunk_index: chunk_index as ChunkIndex,
data: chunk.into(),
},
)
}
}
}
}
#[test]
fn test_query_dealing() {
fn test_query_dealing_chunk() {
let mut deps = init_contract();
let bad_address = "FOOMP".to_string();
assert!(query_dealing(deps.as_ref(), 0, bad_address, 0).is_err());
assert!(query_dealing_chunk(deps.as_ref(), 0, bad_address, 0, 0).is_err());
let empty = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
let empty = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap();
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.chunk_index, 0);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(empty.dealing.is_none());
assert!(empty.chunk.is_none());
// insert the dealing
// insert the dealing chunk
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
@@ -122,16 +186,17 @@ pub(crate) mod tests {
dealing.clone(),
);
let retrieved = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
let retrieved = query_dealing_chunk(deps.as_ref(), 0, "foo".to_string(), 0, 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealing_index, dealing.dealing_index);
assert_eq!(retrieved.chunk_index, dealing.chunk_index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert_eq!(retrieved.dealing.unwrap(), dealing.data);
assert_eq!(retrieved.chunk.unwrap(), dealing.data);
}
#[test]
fn test_query_dealing_status() {
let mut deps = init_contract();
let deps = init_contract();
let bad_address = "FOOMP".to_string();
assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
@@ -140,167 +205,26 @@ pub(crate) mod tests {
assert_eq!(empty.epoch_id, 0);
assert_eq!(empty.dealing_index, 0);
assert_eq!(empty.dealer, Addr::unchecked("foo"));
assert!(!empty.dealing_submitted);
assert!(!empty.status.fully_submitted);
assert!(!empty.status.has_metadata);
assert!(empty.status.chunk_submission_status.is_empty());
// insert the dealing
let dealing = partial_dealing_fixture();
StoredDealing::save(
deps.as_mut().storage,
0,
&Addr::unchecked("foo"),
dealing.clone(),
);
// insert the metadata
//
let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
assert_eq!(retrieved.epoch_id, 0);
assert_eq!(retrieved.dealing_index, dealing.index);
assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
assert!(retrieved.dealing_submitted)
}
#[cfg(test)]
mod query_dealings {
use super::*;
use nym_coconut_dkg_common::types::DEFAULT_DEALINGS;
#[test]
fn dealings_empty_on_init() {
let deps = init_contract();
let all_dealings = StoredDealing::unchecked_all_entries(&deps.storage);
assert!(all_dealings.is_empty())
}
#[test]
fn dealings_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let limit = 2;
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(limit))
.unwrap();
assert_eq!(limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// query without explicitly setting a limit
let page1 = query_dealings_paged(deps.as_ref(), 0, dealer, None, None).unwrap();
assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT;
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(crazy_limit))
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DEALINGS_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_pagination_works() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 0, 10, 1);
let per_page = 2;
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 =
query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(per_page))
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.dealings.len());
}
// save another
fill_dealings(deps.as_mut(), 1, 10, 2);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// page1 should have 2 results on it
let page1 =
query_dealings_paged(deps.as_ref(), 1, dealer, None, Option::from(per_page))
.unwrap();
assert_eq!(2, page1.dealings.len());
}
fill_dealings(deps.as_mut(), 3, 10, 3);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
// page1 still has 2 results
let page1 = query_dealings_paged(
deps.as_ref(),
3,
dealer.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.dealings.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
3,
dealer,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.dealings.len());
}
fill_dealings(deps.as_mut(), 4, 10, 4);
for dealer in 0..10 {
let dealer = format!("dealer{dealer}");
let page1 = query_dealings_paged(
deps.as_ref(),
4,
dealer.clone(),
None,
Option::from(per_page),
)
.unwrap();
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
4,
dealer,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.dealings.len());
}
}
// // insert the dealing
// let dealing = partial_dealing_fixture();
// StoredDealing::save(
// deps.as_mut().storage,
// 0,
// &Addr::unchecked("foo"),
// dealing.clone(),
// );
//
// let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(retrieved.epoch_id, 0);
// assert_eq!(retrieved.dealing_index, dealing.dealing_index);
// assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
// assert!(retrieved.dealing_submitted)
}
}
+257 -128
View File
@@ -1,57 +1,87 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Order, Record, StdResult, Storage};
use cw_storage_plus::{Bound, Key, KeyDeserialize, Path, Prefix, Prefixer, PrimaryKey};
use crate::error::ContractError;
use cosmwasm_std::{Addr, Storage};
use cw_storage_plus::{Key, Map, Path, PrimaryKey};
use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing};
use nym_coconut_dkg_common::types::{
ContractDealing, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealing,
ChunkIndex, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealingData,
};
pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2;
pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1;
type Dealer<'a> = &'a Addr;
// dealings are stored in a multilevel map with the following hierarchy:
/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`.
pub(crate) const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> =
Map::new("dealings_metadata");
pub(crate) fn metadata_exists(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> bool {
DEALINGS_METADATA.has(storage, (epoch_id, dealer, dealing_index))
}
pub(crate) fn must_read_metadata(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Result<DealingMetadata, ContractError> {
DEALINGS_METADATA
.may_load(storage, (epoch_id, dealer, dealing_index))?
.ok_or_else(|| ContractError::UnavailableDealingMetadata {
epoch_id,
dealer: dealer.to_owned(),
dealing_index,
})
}
pub(crate) fn store_metadata(
storage: &mut dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
metadata: &DealingMetadata,
) -> Result<(), ContractError> {
Ok(DEALINGS_METADATA.save(storage, (epoch_id, dealer, dealing_index), metadata)?)
}
// dealings data is stored in a multilevel map with the following hierarchy:
// - epoch-id:
// - issuer-address:
// - dealing id:
// - dealing content
// - chunk_id:
// - dealing content
// NOTE: we're storing raw bytes bypassing serialization, so we can't use the `Map` type,
// thus make sure you always use the below methods for using the storage!
pub(crate) struct StoredDealing;
// part of `StoredDealing` to make existence lookup cheaper
// TODO: do it later since we need to chunk the dealings anyway
// pub(crate) struct UNIMPLEMENTED_DealingLookup;
impl StoredDealing {
const NAMESPACE: &'static [u8] = b"dealing";
fn deserialize_dealing_record(kv: Record) -> StdResult<(DealingIndex, ContractDealing)> {
// prefix-range related should we need it
#[cfg(test)]
fn deserialize_dealing_record(
kv: cosmwasm_std::Record,
) -> cosmwasm_std::StdResult<(ChunkIndex, PartialContractDealingData)> {
let (k, v) = kv;
let index = <DealingIndex as KeyDeserialize>::from_vec(k)?;
let index = <ChunkIndex as cw_storage_plus::KeyDeserialize>::from_vec(k)?;
let data = ContractSafeBytes(v);
Ok((index, data))
}
fn storage_key(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Path<Vec<u8>> {
// just replicate the behaviour from `Map::key`
let key = (epoch_id, dealer, dealing_index);
Path::new(
Self::NAMESPACE,
&key.key().iter().map(Key::as_ref).collect::<Vec<_>>(),
)
}
// prefix-range related should we need it
#[cfg(test)]
fn prefix(
prefix: (EpochId, Dealer, DealingIndex),
) -> cw_storage_plus::Prefix<ChunkIndex, PartialContractDealingData, ChunkIndex> {
use cw_storage_plus::Prefixer;
fn prefix(prefix: (EpochId, Dealer)) -> Prefix<DealingIndex, ContractSafeBytes, DealingIndex> {
Prefix::with_deserialization_functions(
cw_storage_plus::Prefix::with_deserialization_functions(
Self::NAMESPACE,
&prefix.prefix(),
&[],
@@ -61,24 +91,76 @@ impl StoredDealing {
)
}
pub(crate) fn exists(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: &Addr,
dealing_index: DealingIndex,
) -> bool {
StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage)
// prefix-range related should we need it
#[cfg(test)]
pub(crate) fn prefix_range<'a>(
storage: &'a dyn Storage,
prefix: (EpochId, Dealer, DealingIndex),
start: Option<cw_storage_plus::Bound<ChunkIndex>>,
) -> impl Iterator<Item = cosmwasm_std::StdResult<PartialContractDealing>> + 'a {
let dealing_index = prefix.2;
Self::prefix(prefix)
.range(storage, start, None, cosmwasm_std::Order::Ascending)
.map(move |maybe_record| {
maybe_record.map(|(chunk_index, data)| PartialContractDealing {
dealing_index,
chunk_index,
data,
})
})
}
fn storage_key(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Path<Vec<u8>> {
// just replicate the behaviour from `Map::key`
// note: `PrimaryKey` trait is not implemented for tuple (T, U, V, W), only for up to (T, U, V)
// that's why we create a (T, U, (V, W)) tuple(s) instead
let key = (epoch_id, dealer, (dealing_index, chunk_index));
Path::new(
Self::NAMESPACE,
&key.key().iter().map(Key::as_ref).collect::<Vec<_>>(),
)
}
// pub(crate) fn exists(
// storage: &dyn Storage,
// epoch_id: EpochId,
// dealer: &Addr,
// dealing_index: DealingIndex,
// chunk_index: ChunkIndex,
// ) -> StdResult<bool> {
// // whenever the dealing is saved, the metadata is appropriately updated
// // reading metadata is way cheaper than the dealing chunk itself
// let Some(metadata) =
// DEALINGS_METADATA.may_load(storage, (epoch_id, dealer, dealing_index))?
// else {
// return Ok(false);
// };
// let Some(chunk_info) = metadata.submitted_chunks.get(&chunk_index) else {
// return Ok(false);
// };
// Ok(chunk_info.status.submitted())
// // StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage)
// }
pub(crate) fn save(
storage: &mut dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing: PartialContractDealing,
dealng_chunk: PartialContractDealing,
) {
// NOTE: we're storing bytes directly here!
let storage_key = StoredDealing::storage_key(epoch_id, dealer, dealing.index);
storage.set(&storage_key, dealing.data.as_slice());
let storage_key = Self::storage_key(
epoch_id,
dealer,
dealng_chunk.dealing_index,
dealng_chunk.chunk_index,
);
storage.set(&storage_key, dealng_chunk.data.as_slice());
}
pub(crate) fn read(
@@ -86,41 +168,40 @@ impl StoredDealing {
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Option<ContractDealing> {
let storage_key = StoredDealing::storage_key(epoch_id, dealer, dealing_index);
let raw_dealing = storage.get(&storage_key);
raw_dealing.map(ContractSafeBytes)
}
pub(crate) fn prefix_range<'a>(
storage: &'a dyn Storage,
prefix: (EpochId, Dealer),
start: Option<Bound<DealingIndex>>,
) -> impl Iterator<Item = StdResult<PartialContractDealing>> + 'a {
Self::prefix(prefix)
.range(storage, start, None, Order::Ascending)
.map(|maybe_record| maybe_record.map(Into::into))
chunk_index: ChunkIndex,
) -> Option<PartialContractDealingData> {
let storage_key = Self::storage_key(epoch_id, dealer, dealing_index, chunk_index);
storage.get(&storage_key).map(ContractSafeBytes)
}
// iterate over all values, only to be used in tests due to the amount of data being returned
#[cfg(test)]
#[allow(clippy::type_complexity)]
pub(crate) fn unchecked_all_entries(
storage: &dyn Storage,
) -> Vec<((EpochId, Addr, DealingIndex), ContractDealing)> {
type StorageKey<'a> = (EpochId, Dealer<'a>, DealingIndex);
) -> Vec<(
(EpochId, Addr, (DealingIndex, ChunkIndex)),
PartialContractDealingData,
)> {
use cw_storage_plus::KeyDeserialize;
let empty_prefix: Prefix<StorageKey, ContractDealing, StorageKey> =
Prefix::with_deserialization_functions(
Self::NAMESPACE,
&[],
&[],
|_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))),
|_, _, _| unimplemented!(),
);
type StorageKey<'a> = (EpochId, Dealer<'a>, (DealingIndex, ChunkIndex));
let empty_prefix: cw_storage_plus::Prefix<
StorageKey,
PartialContractDealingData,
StorageKey,
> = cw_storage_plus::Prefix::with_deserialization_functions(
Self::NAMESPACE,
&[],
&[],
|_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))),
|_, _, _| unimplemented!(),
);
empty_prefix
.range(storage, None, None, Order::Ascending)
.collect::<StdResult<_>>()
.range(storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<cosmwasm_std::StdResult<_>>()
.unwrap()
}
}
@@ -129,24 +210,36 @@ impl StoredDealing {
mod tests {
use super::*;
use crate::support::tests::helpers::init_contract;
use cw_storage_plus::Bound;
use std::collections::HashMap;
fn dealing_data(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> ContractDealing {
chunk_index: ChunkIndex,
) -> PartialContractDealingData {
ContractSafeBytes(
format!("{epoch_id},{dealer},{dealing_index}")
format!("{epoch_id},{dealer},{dealing_index},{chunk_index}")
.as_bytes()
.to_vec(),
)
}
#[test]
fn saving_dealing() {
fn saving_dealing_chunks() {
let mut deps = init_contract();
fn exists_in_storage(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> bool {
StoredDealing::storage_key(epoch_id, dealer, dealing_index, chunk_index).has(storage)
}
// make sure to check all combinations of epoch id, dealer address and dealing index to ensure nothing overlaps
let epochs = [54, 423, 754];
let dealers = [
@@ -157,26 +250,31 @@ mod tests {
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
let chunk_indices = [0, 1, 2, 3, 4];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
assert!(!StoredDealing::exists(
&deps.storage,
*epoch_id,
dealer,
*dealing_index
));
for chunk_index in &chunk_indices {
assert!(!exists_in_storage(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index
));
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
index: *dealing_index,
data: dealing_data(*epoch_id, dealer, *dealing_index),
},
)
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
dealing_index: *dealing_index,
chunk_index: *chunk_index,
data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index),
},
)
}
}
}
}
@@ -186,36 +284,45 @@ mod tests {
.collect();
assert_eq!(
all.len(),
epochs.len() * dealers.len() * dealing_indices.len()
epochs.len() * dealers.len() * dealing_indices.len() * chunk_indices.len()
);
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
assert!(StoredDealing::exists(
&deps.storage,
*epoch_id,
dealer,
*dealing_index
));
for chunk_index in &chunk_indices {
assert!(exists_in_storage(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index
));
let content =
StoredDealing::read(&deps.storage, *epoch_id, dealer, *dealing_index)
.unwrap();
let expected = dealing_data(*epoch_id, dealer, *dealing_index);
assert_eq!(expected, content);
assert_eq!(
&expected,
all.get(&(*epoch_id, dealer.clone(), *dealing_index))
.unwrap()
);
let content = StoredDealing::read(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index,
)
.unwrap();
let expected =
dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index);
assert_eq!(expected, content);
assert_eq!(
&expected,
all.get(&(*epoch_id, dealer.clone(), (*dealing_index, *chunk_index)))
.unwrap()
);
}
}
}
}
}
#[test]
fn iterating_over_dealings() {
fn iterating_over_dealing_chunks() {
let mut deps = init_contract();
let epochs = [54, 423, 754];
@@ -227,19 +334,23 @@ mod tests {
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
let chunk_indices = [0, 1, 2, 3, 4];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
index: *dealing_index,
data: dealing_data(*epoch_id, dealer, *dealing_index),
},
)
for chunk_index in &chunk_indices {
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
dealing_index: *dealing_index,
chunk_index: *chunk_index,
data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index),
},
)
}
}
}
}
@@ -248,48 +359,66 @@ mod tests {
// nothing under epoch 0
let dealings =
StoredDealing::prefix_range(&deps.storage, (0, &dealers[0]), None).collect::<Vec<_>>();
StoredDealing::prefix_range(&deps.storage, (0, &dealers[0], dealing_indices[0]), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
// nothing for dealer "foo"
let foo = Addr::unchecked("foo");
let dealings =
StoredDealing::prefix_range(&deps.storage, (epochs[0], &foo), None).collect::<Vec<_>>();
StoredDealing::prefix_range(&deps.storage, (epochs[0], &foo, dealing_indices[0]), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
let all = StoredDealing::prefix_range(&deps.storage, (epochs[0], &dealers[0]), None)
.collect::<Vec<_>>();
assert_eq!(all.len(), dealing_indices.len());
// nothing for dealing index 99
let dealings =
StoredDealing::prefix_range(&deps.storage, (epochs[0], &dealers[0], 99), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
let all = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0], dealing_indices[0]),
None,
)
.collect::<Vec<_>>();
assert_eq!(all.len(), chunk_indices.len());
for (i, dealing) in all.iter().enumerate() {
let expected = dealing_data(epochs[0], &dealers[0], dealing_indices[i]);
let expected =
dealing_data(epochs[0], &dealers[0], dealing_indices[0], chunk_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(dealing_indices[i], dealing.as_ref().unwrap().index);
assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index);
}
// for sanity sake, check another dealer with different epoch
let all_other = StoredDealing::prefix_range(&deps.storage, (epochs[2], &dealers[3]), None)
.collect::<Vec<_>>();
assert_eq!(all_other.len(), dealing_indices.len());
// for sanity sake, check another dealer with different epoch and different dealing index
let all_other = StoredDealing::prefix_range(
&deps.storage,
(epochs[2], &dealers[3], dealing_indices[4]),
None,
)
.collect::<Vec<_>>();
assert_eq!(all_other.len(), chunk_indices.len());
for (i, dealing) in all_other.iter().enumerate() {
let expected = dealing_data(epochs[2], &dealers[3], dealing_indices[i]);
let expected =
dealing_data(epochs[2], &dealers[3], dealing_indices[4], chunk_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(dealing_indices[i], dealing.as_ref().unwrap().index);
assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index);
}
let without_first = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0]),
Some(Bound::exclusive(dealing_indices[0])),
(epochs[0], &dealers[0], dealing_indices[0]),
Some(Bound::exclusive(chunk_indices[0])),
)
.collect::<Vec<_>>();
assert_eq!(&all[1..], without_first);
let mid = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0]),
Some(Bound::inclusive(dealing_indices[3])),
(epochs[0], &dealers[0], dealing_indices[0]),
Some(Bound::inclusive(chunk_indices[3])),
)
.collect::<Vec<_>>();
assert_eq!(&all[3..], mid);
@@ -1,62 +1,203 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::StoredDealing;
use crate::dealings::storage::{
metadata_exists, must_read_metadata, store_metadata, StoredDealing,
};
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::storage::STATE;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use nym_coconut_dkg_common::types::{EpochState, PartialContractDealing};
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
use nym_coconut_dkg_common::dealing::{
DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS,
};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState};
pub fn try_commit_dealings(
deps: DepsMut<'_>,
info: MessageInfo,
dealing: PartialContractDealing,
// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch
fn ensure_permission(
storage: &dyn Storage,
sender: &Addr,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
) -> Result<(), ContractError> {
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
// ensure the sender is a dealer
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
.may_load(storage, sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
if resharing
&& !INITIAL_REPLACEMENT_DATA
.load(deps.storage)?
.load(storage)?
.initial_dealers
.contains(&info.sender)
.contains(sender)
{
return Err(ContractError::NotAnInitialDealer);
}
Ok(())
}
pub fn try_submit_dealings_metadata(
deps: DepsMut,
info: MessageInfo,
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let state = STATE.load(deps.storage)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// check if the index is in range without doing expensive storage reads
// don't allow overwriting existing metadata
if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) {
return Err(ContractError::MetadataAlreadyExists {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
});
}
// make sure the dealing index is in the allowed range
// note: dealing indexing starts from 0
if dealing.index >= state.key_size {
if dealing_index >= state.key_size {
return Err(ContractError::DealingOutOfRange {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
index: dealing_index,
key_size: state.key_size,
});
}
// check if this dealer has already committed this particular dealing
if StoredDealing::exists(deps.storage, epoch.epoch_id, &info.sender, dealing.index) {
return Err(ContractError::DealingAlreadyCommitted {
// make sure the metadata is not empty
if chunks.is_empty() {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing.index,
dealing_index,
});
}
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, dealing);
// make sure the chunks are non empty
if chunks.iter().any(|c| c.size == 0) {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
});
}
// make sure the number of dealing chunks is in the allowed range
// to prevent somebody splitting their dealings into 10B chunks
if chunks.len() > MAX_DEALING_CHUNKS {
return Err(ContractError::TooFragmentedMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
chunks: chunks.len(),
});
}
// make sure all chunks, but the last one, have the same size
// SAFETY: we checked for whether `chunks` is empty and returned an error in that case
#[allow(clippy::unwrap_used)]
let first_chunk_size = chunks.first().unwrap().size;
for (chunk_index, chunk_info) in chunks.iter().enumerate().take(chunks.len() - 1) {
if chunk_info.size != first_chunk_size {
return Err(ContractError::UnevenChunkSplit {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
chunk_index: chunk_index as ChunkIndex,
first_chunk_size,
size: chunk_info.size,
});
}
}
// finally, construct and store the metadata
let metadata = DealingMetadata::new(dealing_index, chunks);
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
dealing_index,
&metadata,
)?;
Ok(Response::new())
}
pub fn try_commit_dealings_chunk(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
chunk: PartialContractDealing,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// read meta
let mut metadata = must_read_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
chunk.dealing_index,
)?;
// check if the received chunk is within the declared range
let Some(submission_status) = metadata.submitted_chunks.get_mut(&chunk.chunk_index) else {
return Err(ContractError::DealingChunkNotInMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
});
};
// check if this dealer has already committed this particular dealing chunk
if let Some(submission_height) = submission_status.status.submission_height {
return Err(ContractError::DealingChunkAlreadyCommitted {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
block_height: submission_height,
});
}
// check if the received chunk has the specified size
if submission_status.info.size != chunk.data.len() as u64 {
return Err(ContractError::InconsistentChunkLength {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
metadata_length: submission_status.info.size,
received: chunk.data.len() as u64,
});
}
// update the metadata
submission_status.status.submission_height = Some(env.block.height);
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
chunk.dealing_index,
&metadata,
)?;
// store the dealing
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, chunk);
Ok(Response::new())
}
@@ -66,18 +207,20 @@ pub(crate) mod tests {
use super::*;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
use crate::support::tests::fixtures::{dealer_details_fixture, partial_dealing_fixture};
use crate::support::tests::fixtures::{
dealer_details_fixture, dealing_metadata_fixture, partial_dealing_fixture,
};
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{
ContractSafeBytes, InitialReplacementData, TimeConfiguration, DEFAULT_DEALINGS,
ContractSafeBytes, InitialReplacementData, TimeConfiguration,
};
#[test]
fn invalid_commit_dealing() {
fn invalid_commit_dealing_chunk() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
@@ -86,8 +229,14 @@ pub(crate) mod tests {
let info = mock_info(owner.as_str(), &[]);
let dealing = partial_dealing_fixture();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
dealing.clone(),
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
@@ -101,10 +250,16 @@ pub(crate) mod tests {
.time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
add_fixture_dealer(deps.as_mut());
advance_epoch_state(deps.as_mut(), env).unwrap();
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
dealing.clone(),
false,
)
.unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails {
@@ -134,8 +289,14 @@ pub(crate) mod tests {
},
)
.unwrap();
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), true).unwrap_err();
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
dealing.clone(),
true,
)
.unwrap_err();
assert_eq!(ret, ContractError::NotAnInitialDealer);
INITIAL_REPLACEMENT_DATA
@@ -153,12 +314,28 @@ pub(crate) mod tests {
})
.unwrap();
// dealing out of range
let ret = try_commit_dealings(
// TODO: test case: no metadata
//
//
// add dealing metadata
try_submit_dealings_metadata(
deps.as_mut(),
info.clone(),
0,
dealing_metadata_fixture(),
false,
)
.unwrap();
// dealing chunk out of range
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
PartialContractDealing {
index: 42,
dealing_index: 0,
chunk_index: 42,
data: ContractSafeBytes(vec![1, 2, 3]),
},
false,
@@ -166,27 +343,41 @@ pub(crate) mod tests {
.unwrap_err();
assert_eq!(
ret,
ContractError::DealingOutOfRange {
ContractError::DealingChunkNotInMetadata {
epoch_id: 0,
dealer: info.sender.clone(),
index: 42,
key_size: DEFAULT_DEALINGS as u32,
dealing_index: 0,
chunk_index: 42,
}
);
// 'good' dealing
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
dealing.clone(),
false,
);
assert!(ret.is_ok());
// duplicate dealing
let ret =
try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
let ret = try_commit_dealings_chunk(
deps.as_mut(),
env.clone(),
info.clone(),
dealing.clone(),
false,
)
.unwrap_err();
assert_eq!(
ret,
ContractError::DealingAlreadyCommitted {
ContractError::DealingChunkAlreadyCommitted {
epoch_id: 0,
dealer: info.sender.clone(),
index: 0,
dealing_index: 0,
chunk_index: 0,
block_height: env.block.height,
}
);
@@ -198,7 +389,16 @@ pub(crate) mod tests {
})
.unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
try_submit_dealings_metadata(
deps.as_mut(),
info.clone(),
0,
dealing_metadata_fixture(),
false,
)
.unwrap();
let ret = try_commit_dealings_chunk(deps.as_mut(), env, info, dealing.clone(), false);
assert!(ret.is_ok());
}
}
+66 -10
View File
@@ -3,7 +3,8 @@
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_coconut_dkg_common::types::{DealingIndex, EpochId};
use nym_coconut_dkg_common::dealing::MAX_DEALING_CHUNKS;
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId};
use thiserror::Error;
/// Custom errors for contract failure conditions.
@@ -37,7 +38,7 @@ pub enum ContractError {
EpochNotInitialised,
#[error(
"Requested action needs state to be {expected_state}, currently in state {current_state}, "
"Requested action needs state to be {expected_state}, currently in state {current_state}"
)]
IncorrectEpochState {
current_state: String,
@@ -50,18 +51,24 @@ pub enum ContractError {
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
#[error(
"Dealer {dealer} has already committed dealing for epoch {epoch_id} with index {index}"
)]
DealingAlreadyCommitted {
#[error("Dealer {dealer} has already committed dealing chunk for epoch {epoch_id} with dealing index {dealing_index} and chunk index {chunk_index} at height {block_height}")]
DealingChunkAlreadyCommitted {
epoch_id: EpochId,
dealer: Addr,
index: DealingIndex,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
block_height: u64,
},
#[error(
"Dealer {dealer} has attempted to commit dealing for epoch {epoch_id} with index {index} while the key size is set to {key_size}"
)]
#[error("dealer {dealer} tried to commit chunk {chunk_index} of dealing {dealing_index} for epoch {epoch_id}, but it hasn't been declared in the prior metadata")]
DealingChunkNotInMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[error("dealer {dealer} has attempted to commit dealing chunk for epoch {epoch_id} with dealing index {index} while the key size is set to {key_size}")]
DealingOutOfRange {
epoch_id: EpochId,
dealer: Addr,
@@ -69,6 +76,55 @@ pub enum ContractError {
key_size: u32,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} with {chunks} chunks while at most {} chunks are allowed", MAX_DEALING_CHUNKS)]
TooFragmentedMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunks: usize,
},
#[error("the declared chunk split for epoch {epoch_id} from dealer {dealer} for dealing index {dealing_index} is uneven. first chunk has size of {first_chunk_size} while chunk at index {chunk_index} has {size}")]
UnevenChunkSplit {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
first_chunk_size: u64,
size: u64,
},
#[error("the received chunk for epoch {epoch_id} from dealer {dealer} at dealing index {dealing_index} at chunk index {chunk_index} has inconsistent length. the metadata contains length of {metadata_length} while the received data is {received} bytes long")]
InconsistentChunkLength {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
metadata_length: u64,
received: u64,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} zero chunks")]
EmptyMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} does not exist")]
UnavailableDealingMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} already exists")]
MetadataAlreadyExists {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String },
@@ -1,9 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{ContractSafeBytes, PartialContractDealing};
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
use nym_coconut_dkg_common::types::ContractSafeBytes;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
pub const TEST_MIX_DENOM: &str = "unym";
@@ -19,17 +20,26 @@ pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare {
}
}
#[allow(unused)]
pub fn dealing_bytes_fixture() -> ContractSafeBytes {
ContractSafeBytes(vec![1, 2, 3])
ContractSafeBytes(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
}
pub fn partial_dealing_fixture() -> PartialContractDealing {
PartialContractDealing {
index: 0,
data: ContractSafeBytes(vec![1, 2, 3]),
chunk_index: 0,
dealing_index: 0,
data: ContractSafeBytes(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
}
}
pub fn dealing_metadata_fixture() -> Vec<DealingChunkInfo> {
let chunk_fixture = partial_dealing_fixture();
vec![DealingChunkInfo {
size: chunk_fixture.data.len() as u64,
}]
}
pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails {
DealerDetails {
address: Addr::unchecked(format!("owner{}", assigned_index)),
@@ -9,8 +9,9 @@ use cosmwasm_std::{
QuerierResult, SystemResult, WasmQuery,
};
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::{DealerDetails, DEFAULT_DEALINGS};
use nym_coconut_dkg_common::types::DealerDetails;
use std::sync::Mutex;
use super::fixtures::TEST_MIX_DENOM;
@@ -8,7 +8,7 @@ use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::storage::{MULTISIG, STATE};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use nym_coconut_dkg_common::types::EpochState;
use nym_coconut_dkg_common::verification_key::{
to_cosmos_msg, ContractVKShare, VerificationKeyShare,
@@ -66,9 +66,11 @@ pub fn try_commit_verification_key_share(
pub fn try_verify_verification_key_share(
deps: DepsMut<'_>,
info: MessageInfo,
owner: Addr,
owner: String,
resharing: bool,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(&owner)?;
check_epoch_state(
deps.storage,
EpochState::VerificationKeyFinalization { resharing },
@@ -96,6 +98,7 @@ mod tests {
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use cw_controllers::AdminError;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration};
@@ -233,7 +236,7 @@ mod tests {
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = mock_info("requester", &[]);
let owner = Addr::unchecked("owner");
let owner = "owner".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
let ret =
@@ -291,7 +294,7 @@ mod tests {
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = Addr::unchecked("owner");
let owner = "owner".to_string();
let info = mock_info(owner.as_ref(), &[]);
let share = "share".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
@@ -309,14 +312,18 @@ mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let dealer_details = DealerDetails {
address: owner.clone(),
address: Addr::unchecked(&owner),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.save(
deps.as_mut().storage,
&Addr::unchecked(&owner),
&dealer_details,
)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
+2 -1
View File
@@ -4,9 +4,10 @@ version = "0.1.0"
edition = "2021"
build = "build.rs"
links = "cpucycles"
license = "LicenseRef-PD-hp OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT"
[dependencies]
libc = "0.2.140"
[build-dependencies]
cfg-if = "1"
cfg-if = "1"
+2
View File
@@ -114,6 +114,7 @@ allow = [
"CC0-1.0",
"Unicode-DFS-2016",
"OpenSSL",
"Zlib",
]
# List of explicitly disallowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
@@ -216,6 +217,7 @@ deny = [
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
{ name = "openssl"},
]
# List of features to allow/deny
+22 -41
View File
@@ -1,64 +1,48 @@
[book]
title = "Nym Developer Portal"
title = "Nym Docs"
authors = ["Max Hampshire, Serinko, Alexia Lorenza Martinel"]
description = "Nym technical documentation"
language = "en"
multilingual = false
multilingual = false # for the moment - ideally work on chinese, brazillian ,portugese spanish next
src = "src"
[rust]
edition = "2018"
#################
# PREPROCESSORS #
#################
# Note if changing these fields:
# - make sure you aren't running `mdbook serve`
# - change value of `turn-off = true` to `false`. This is set to `true` usually to avoid a known constant-reload bug with `mdbook serve` and this preprocessor
# - delete `./theme/` dir
# - change the preprocessor values that you want to edit
# - run `mdbook build`: this will rebuild the `./theme` directory
# - change value of `turn-off` back to `true`
[preprocessor.theme]
pagetoc = true
# some variables related (defined in theme/css/variables.css)
# `content-max-width` + `pagetoc-width` = 95% seems the best
pagetoc-width = "13%"
content-max-width = "70%" # 82
pagetoc-fontsize = "14.5px"
sidebar-width = "300px"
menu-bar-height = "40px" # memu-bar = the bar on the top
page-padding = "20px"
mobile-content-max-width = "98%"
# layout
content-padding = "0 10px"
content-main-margin-left = "10%"
content-main-margin-right = "10%"
nav-chapters-max-width = "auto"
nav-chapters-min-width = "auto"
chapter-line-height = "2em"
section-line-height = "1.5em"
# if true, never read and touch the files in theme dir
sidebar-width = "280px"
content-max-width = "70%"
content-main-margin-left = "5%"
content-main-margin-right = "5%"
root-font-size = "70%"
# DO NOT CHANGE or you might overwrite the custom hbs file
turn-off = true
[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install`
# variables preprocessor: import variables into files
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
wallet_release_version = "1.2.8"
# nym-vpn related variables
nym_vpn_latest_binary_url = "https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.3"
nym_vpn_form_url = "https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2"
[preprocessor.last-changed]
command = "mdbook-last-changed"
renderer = ["html"]
# used for grabbing output of binary commands for automation
# https://github.com/FauconFan/mdbook-cmdrun
# used for grabbing output of binary commands for automation https://github.com/FauconFan/mdbook-cmdrun
[preprocessor.cmdrun]
# more pre-processor plugins to look into from https://github.com/rust-lang/mdBook/wiki/Third-party-plugins & https://lib.rs/keywords/mdbook-preprocessor
# mdbook-i18n
#########
# BUILD #
@@ -75,19 +59,16 @@ extra-watch-dirs = [] # directories to watch for triggering builds
##########
[output.html]
theme = "nym_themes"
default-theme = "coal"
preferred-dark-theme = "coal"
curly-quotes = true
# mathjax-support = false # useful if we want to pull equations in ?
copy-fonts = true
no-section-label = false
additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css", "custom.css", "./mdbook-admonish.css"]
additional-js = ["theme/pagetoc.js"]
additional-css = ["./nym_themes/custom.css", "./nym_themes/mdbook-admonish.css", "./nym_themes/pagetoc.css"]
additional-js = ["./nym_themes/pagetoc.js"]
git-repository-url = "https://github.com/nymtech/nym"
git-repository-icon = "fa-github"
# edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
site-url = "/developers/"
# cname = "nymtech.net"
input-404 = "not-found.md"
[output.html.fold]
@@ -99,7 +80,7 @@ level = 0 # the depth to start folding
editable = false # allows editing the source code
copyable = true # include the copy button for copying code snippets
copy-js = true # includes the JavaScript for the code editor
line-numbers = false # displays line numbers for editable code
line-numbers = true # displays line numbers for editable code
runnable = true # displays a run button for rust code
# options for the built in text search
-37
View File
@@ -1,37 +0,0 @@
:root {
--mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
}
.coal {
--bg: #121726;
--fg: #f2f2f2;
--sidebar-bg: #121726;
--sidebar-fg: #f2f2f2;
--sidebar-active: #fb6e4e;
--icons: #f2f2f2;
--icons-hover: #fb6e4e;
--links: #fb6e4e;
}
.light {
--bg: #f4f6f8;
--fg: #121726;
--sidebar-bg: #f4f6f8;
--sidebar-fg: #121726;
--sidebar-active: #fb6e4e;
--icons: #121726;
--icons-hover: #fb6e4e;
--links: #fb6e4e;
}
footer {
font-size: 0.8em;
text-align: center;
border-top: 1px solid black;
padding: 5px 0;
}
@@ -2,12 +2,6 @@
@import 'variables.css';
::-webkit-scrollbar {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
html {
scrollbar-color: var(--scrollbar) var(--bg);
}
@@ -18,6 +12,19 @@ a > .hljs {
color: var(--links);
}
/*
body-container is necessary because mobile browsers don't seem to like
overflow-x on the body tag when there is a <meta name="viewport"> tag.
*/
#body-container {
/*
This is used when the sidebar pushes the body content off the side of
the screen on small screens. Without it, dragging on mobile Safari
will want to reposition the viewport in a weird way.
*/
overflow-x: clip;
}
/* Menu Bar */
#menu-bar,
@@ -30,9 +37,9 @@ a > .hljs {
display: flex;
flex-wrap: wrap;
background-color: var(--bg);
border-bottom-color: var(--bg);
border-bottom-width: 1px;
border-bottom-style: solid;
border-block-end-color: var(--bg);
border-block-end-width: 1px;
border-block-end-style: solid;
}
#menu-bar.sticky,
.js #menu-bar-hover-placeholder:hover + #menu-bar,
@@ -49,7 +56,7 @@ a > .hljs {
height: var(--menu-bar-height);
}
#menu-bar.bordered {
border-bottom-color: var(--table-border-color);
border-block-end-color: var(--table-border-color);
}
#menu-bar i, #menu-bar .icon-button {
position: relative;
@@ -86,11 +93,13 @@ a > .hljs {
display: flex;
margin: 0 5px;
}
.no-js .left-buttons {
.no-js .left-buttons button {
display: none;
}
.menu-title {
position: absolute;
left: 50%;
display: inline-block;
font-weight: 200;
font-size: 2.4rem;
@@ -153,7 +162,7 @@ a > .hljs {
}
.nav-wrapper {
margin-top: 50px;
margin-block-start: 50px;
display: none;
}
@@ -166,23 +175,34 @@ a > .hljs {
background-color: var(--sidebar-bg);
}
.previous {
float: left;
}
/* Only Firefox supports flow-relative values */
.previous { float: left; }
[dir=rtl] .previous { float: right; }
/* Only Firefox supports flow-relative values */
.next {
float: right;
right: var(--page-padding);
}
[dir=rtl] .next {
float: left;
right: unset;
left: var(--page-padding);
}
/* Use the correct buttons for RTL layouts*/
[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";}
[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; }
@media only screen and (max-width: 1080px) {
.nav-wide-wrapper { display: none; }
.nav-wrapper { display: block; }
}
/* sidebar-visible */
@media only screen and (max-width: 1380px) {
.sidebar-visible .nav-wide-wrapper { display: none; }
.sidebar-visible .nav-wrapper { display: block; }
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; }
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; }
}
/* Inline code */
@@ -229,7 +249,7 @@ pre > .buttons :hover {
background-color: var(--theme-hover);
}
pre > .buttons i {
margin-left: 8px;
margin-inline-start: 8px;
}
pre > .buttons button {
cursor: inherit;
@@ -251,8 +271,14 @@ pre > .buttons button {
/* On mobile, make it easier to tap buttons. */
padding: 0.3rem 1rem;
}
.sidebar-resize-indicator {
/* Hide resize indicator on devices with limited accuracy */
display: none;
}
}
pre > code {
display: block;
padding: 1rem;
}
@@ -266,7 +292,7 @@ pre > code {
}
pre > .result {
margin-top: 10px;
margin-block-start: 10px;
}
/* Search */
@@ -277,8 +303,14 @@ pre > .result {
mark {
border-radius: 2px;
padding: 0 3px 1px 3px;
margin: 0 -3px -1px -3px;
padding-block-start: 0;
padding-block-end: 1px;
padding-inline-start: 3px;
padding-inline-end: 3px;
margin-block-start: 0;
margin-block-end: -1px;
margin-inline-start: -3px;
margin-inline-end: -3px;
background-color: var(--search-mark-bg);
transition: background-color 300ms linear;
cursor: pointer;
@@ -290,14 +322,17 @@ mark.fade-out {
}
.searchbar-outer {
margin-left: auto;
margin-right: auto;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
}
#searchbar {
width: 100%;
margin: 5px auto 0px auto;
margin-block-start: 5px;
margin-block-end: 0;
margin-inline-start: auto;
margin-inline-end: auto;
padding: 10px 16px;
transition: box-shadow 300ms ease-in-out;
border: 1px solid var(--searchbar-border-color);
@@ -313,20 +348,23 @@ mark.fade-out {
.searchresults-header {
font-weight: bold;
font-size: 1em;
padding: 18px 0 0 5px;
padding-block-start: 18px;
padding-block-end: 0;
padding-inline-start: 5px;
padding-inline-end: 0;
color: var(--searchresults-header-fg);
}
.searchresults-outer {
margin-left: auto;
margin-right: auto;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
border-bottom: 1px dashed var(--searchresults-border-color);
border-block-end: 1px dashed var(--searchresults-border-color);
}
ul#searchresults {
list-style: none;
padding-left: 20px;
padding-inline-start: 20px;
}
ul#searchresults li {
margin: 10px 0px;
@@ -339,7 +377,10 @@ ul#searchresults li.focus {
ul#searchresults span.teaser {
display: block;
clear: both;
margin: 5px 0 0 20px;
margin-block-start: 5px;
margin-block-end: 0;
margin-inline-start: 20px;
margin-inline-end: 0;
font-size: 0.8em;
}
ul#searchresults span.teaser em {
@@ -355,19 +396,21 @@ ul#searchresults span.teaser em {
top: 0;
bottom: 0;
width: var(--sidebar-width);
font-size: 1em;
font-size: 0.85em;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
[dir=rtl] .sidebar { left: unset; right: 0; }
.sidebar-resizing {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.no-js .sidebar,
.js:not(.sidebar-resizing) .sidebar {
transition: transform 0.3s; /* Animation: slide away */
}
@@ -387,16 +430,35 @@ ul#searchresults span.teaser em {
position: absolute;
cursor: col-resize;
width: 0;
right: 0;
right: calc(var(--sidebar-resize-indicator-width) * -1);
top: 0;
bottom: 0;
display: flex;
align-items: center;
}
.sidebar-resize-handle .sidebar-resize-indicator {
width: 100%;
height: 12px;
background-color: var(--icons);
margin-inline-start: var(--sidebar-resize-indicator-space);
}
[dir=rtl] .sidebar .sidebar-resize-handle {
left: calc(var(--sidebar-resize-indicator-width) * -1);
right: unset;
}
.js .sidebar .sidebar-resize-handle {
cursor: col-resize;
width: 5px;
width: calc(var(--sidebar-resize-indicator-width) - var(--sidebar-resize-indicator-space));
}
.sidebar-hidden .sidebar {
transform: translateX(calc(0px - var(--sidebar-width)));
/* sidebar-hidden */
#sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
z-index: -1;
}
[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar {
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
}
.sidebar::-webkit-scrollbar {
background: var(--sidebar-bg);
@@ -405,19 +467,26 @@ ul#searchresults span.teaser em {
background: var(--scrollbar);
}
.sidebar-visible .page-wrapper {
transform: translateX(var(--sidebar-width));
/* sidebar-visible */
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width)));
}
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: translateX(calc(0px - var(--sidebar-width) - var(--sidebar-resize-indicator-width)));
}
@media only screen and (min-width: 620px) {
.sidebar-visible .page-wrapper {
#sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: none;
margin-inline-start: calc(var(--sidebar-width) + var(--sidebar-resize-indicator-width));
}
[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper {
transform: none;
margin-left: var(--sidebar-width);
}
}
.chapter {
list-style: none outside none;
padding-left: 0;
padding-inline-start: 0;
line-height: 2em;
}
@@ -447,7 +516,7 @@ ul#searchresults span.teaser em {
.chapter li > a.toggle {
cursor: pointer;
display: block;
margin-left: auto;
margin-inline-start: auto;
padding: 0 10px;
user-select: none;
opacity: 0.68;
@@ -464,7 +533,7 @@ ul#searchresults span.teaser em {
.chapter li.chapter-item {
line-height: 1.5em;
margin-top: 0.6em;
margin-block-start: 0.6em;
}
.chapter li.expanded > a.toggle div {
@@ -487,7 +556,7 @@ ul#searchresults span.teaser em {
.section {
list-style: none outside none;
padding-left: 20px;
padding-inline-start: 20px;
line-height: 1.5em;
}
@@ -510,6 +579,7 @@ ul#searchresults span.teaser em {
/* Don't let the children's background extend past the rounded corners. */
overflow: hidden;
}
[dir=rtl] .theme-popup { left: unset; right: 10px; }
.theme-popup .default {
color: var(--icons);
}
@@ -520,7 +590,7 @@ ul#searchresults span.teaser em {
padding: 2px 20px;
line-height: 25px;
white-space: nowrap;
text-align: left;
text-align: start;
cursor: pointer;
color: inherit;
background: inherit;
@@ -533,6 +603,6 @@ ul#searchresults span.teaser em {
.theme-selected::before {
display: inline-block;
content: "✓";
margin-left: -14px;
margin-inline-start: -14px;
width: 14px;
}
@@ -5,6 +5,7 @@
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 70%;
color-scheme: var(--color-scheme);
}
html {
@@ -24,6 +25,7 @@ body {
code {
font-family: var(--mono-font) !important;
font-size: 0.9em;
direction: ltr !important;
}
/* make long words/inline code not x overflow */
@@ -47,13 +49,13 @@ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
.hide-boring .boring { display: none; }
.hidden { display: none !important; }
h2, h3 { margin-top: 2.5em; }
h4, h5 { margin-top: 2em; }
h2, h3 { margin-block-start: 2.5em; }
h4, h5 { margin-block-start: 2em; }
.header + .header h3,
.header + .header h4,
.header + .header h5 {
margin-top: 1em;
margin-block-start: 1em;
}
h1:target::before,
@@ -64,7 +66,7 @@ h5:target::before,
h6:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
margin-inline-start: -30px;
width: 30px;
}
@@ -73,28 +75,34 @@ h6:target::before {
https://bugs.webkit.org/show_bug.cgi?id=218076
*/
:target {
/* Safari does not support logical properties */
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
}
.page {
outline: 0;
padding: 0 var(--page-padding);
margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
}
.page-wrapper {
box-sizing: border-box;
background-color: var(--bg);
}
.no-js .page-wrapper,
.js:not(.sidebar-resizing) .page-wrapper {
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
}
[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper {
transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */
}
.content {
overflow-y: auto;
padding: 0 10px;
}
.content main {
margin-left: 10%;
margin-right: 10%;
margin-inline-start: auto;
margin-inline-end: auto;
max-width: var(--content-max-width);
}
.content p { line-height: 1.45em; }
@@ -144,8 +152,31 @@ blockquote {
padding: 0 20px;
color: var(--fg);
background-color: var(--quote-bg);
border-top: .1em solid var(--quote-border);
border-bottom: .1em solid var(--quote-border);
border-block-start: .1em solid var(--quote-border);
border-block-end: .1em solid var(--quote-border);
}
.warning {
margin: 20px;
padding: 0 20px;
border-inline-start: 2px solid var(--warning-border);
}
.warning:before {
position: absolute;
width: 3rem;
height: 3rem;
margin-inline-start: calc(-1.5rem - 21px);
content: "ⓘ";
text-align: center;
background-color: var(--bg);
color: var(--warning-border);
font-weight: bold;
font-size: 2rem;
}
blockquote .warning:before {
background-color: var(--quote-bg);
}
kbd {
@@ -163,7 +194,7 @@ kbd {
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
margin-top: 2em;
margin-block-start: 2em;
}
.footnote-definition {
font-size: 0.9em;
@@ -2,9 +2,11 @@
/* Globals */
:root {
--sidebar-width: 300px;
--page-padding: 20px;
--content-max-width: 70%;
--sidebar-width: 280px;
--sidebar-resize-indicator-width: 8px;
--sidebar-resize-indicator-space: 2px;
--page-padding: 15px;
--content-max-width: 80%;
--menu-bar-height: 40px;
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
@@ -18,36 +20,6 @@
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
@media only screen and (max-width:1439px) {
:root{
--content-max-width: 98%;
}
}
/* Themes */
.ayu {
@@ -76,6 +48,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(210, 25%, 13%);
--table-header-bg: hsl(210, 25%, 28%);
--table-alternate-bg: hsl(210, 25%, 11%);
@@ -88,6 +62,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #252932;
--search-mark-bg: #e3b171;
--color-scheme: dark;
}
.coal {
@@ -116,6 +92,8 @@
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--warning-border: #ff8e00;
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
@@ -128,6 +106,8 @@
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
--color-scheme: dark;
}
.light {
@@ -156,6 +136,8 @@
--quote-bg: hsl(197, 37%, 96%);
--quote-border: hsl(197, 37%, 91%);
--warning-border: #ff8e00;
--table-border-color: hsl(0, 0%, 95%);
--table-header-bg: hsl(0, 0%, 80%);
--table-alternate-bg: hsl(0, 0%, 97%);
@@ -168,6 +150,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe;
--search-mark-bg: #a2cff5;
--color-scheme: light;
}
.navy {
@@ -196,6 +180,8 @@
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--warning-border: #ff8e00;
--table-border-color: hsl(226, 23%, 16%);
--table-header-bg: hsl(226, 23%, 31%);
--table-alternate-bg: hsl(226, 23%, 14%);
@@ -208,6 +194,8 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5;
--color-scheme: dark;
}
.rust {
@@ -236,6 +224,8 @@
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--warning-border: #ff8e00;
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
@@ -248,6 +238,8 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
--color-scheme: light;
}
@media (prefers-color-scheme: dark) {
@@ -277,6 +269,8 @@
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--warning-border: #ff8e00;
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
@@ -0,0 +1,54 @@
:root {
--mono-font: Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace;
}
.coal {
--fg: #f2f2f2;
--sidebar-fg: #f2f2f2;
--sidebar-active: #fb6e4e;
--icons-hover: #fb6e4e;
--links: #fb6e4e;
::selection {
color: #121726;
background-color: #c5573d;
}
select option {
background-color: #121726;
}
}
.light {
--sidebar-active: #fb6e4e;
--icons-hover: #fb6e4e;
--links: #fb6e4e;
::selection {
color:#f2f2f2;
background-color: #c5573d;
}
}
/*properly centering the title given the additional header items*/
.menu-title {
position: absolute;
left: 50%;
}
.menu-bar .a:hover,
/*necessary because of ^*/
.right-buttons {
position: absolute;
right: 0;
}
select {
font-size: 16px;
}
footer {
font-size: 0.8em;
text-align: center;
border-top: 1px solid black;
padding: 5px 0;
}
@@ -0,0 +1,381 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>{{ title }}</title>
{{#if is_print }}
<meta name="robots" content="noindex">
{{/if}}
{{#if base_url}}
<base href="{{ base_url }}">
{{/if}}
<!-- Custom HTML head -->
{{> head}}
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
{{#if favicon_svg}}
<link rel="icon" href="{{ path_to_root }}favicon.svg">
{{/if}}
{{#if favicon_png}}
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
{{/if}}
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
{{#if print_enable}}
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
{{/if}}
<!-- Fonts -->
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
{{#if copy_fonts}}
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
{{/if}}
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
<!-- Custom theme stylesheets -->
{{#each additional_css}}
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
{{/each}}
<!-- {{#if mathjax_support}}-->
<!-- &lt;!&ndash; MathJax &ndash;&gt;-->
<!-- <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>-->
<!-- {{/if}}-->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Dark</button></li>
</ul>
<!-- CUSTOM -->
<select id="dropdown-menu3" class="icon-button">
<option value="">Network Protocol</option>
<option value="https://nymtech.net/docs">Network Docs</option>
<option value="https://nymtech.net/learn/papers">Academic Papers</option>
</select>
<select id="dropdown-menu" class="icon-button">
<option value="">Developer Docs</option>
<option value="https://nymtech.net/developers">Dev Portal</option>
<option value="https://nymtech.net/docs/sdk/rust/rust.html">Rust SDK</option>
<option value="https://sdk.nymtech.net">Typescript SDK</option>
</select>
<select id="dropdown-menu2" class="icon-button">
<option value="">Setup Guides</option>
<option value="https://nymtech.net/operators">Operators</option>
<option value="https://nymtech.net/developers/nymvpn/intro.html">NymVPN Testing</option>
</select>
<script>
document.getElementById('dropdown-menu').addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (selected.value !== '') {
window.location.href = selected.value;
}
});
</script>
<script>
document.getElementById('dropdown-menu2').addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (selected.value !== '') {
window.location.href = selected.value;
}
});
</script>
<script>
document.getElementById('dropdown-menu3').addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (selected.value !== '') {
window.location.href = selected.value;
}
});
</script>
<!-- END CUSTOM -->
</div>
<h1 class="menu-title"> {{ book_title }}</h1>
<div class="right-buttons">
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
{{/if}}
{{#if print_enable}}
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{/if}}
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
{{#if git_repository_edit_url}}
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
{{/if}}
</div>
</div>
{{#if search_enabled}}
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
{{/if}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<main>
<div class="content-wrap">
{{{ content }}}
</div>
<div class="sidetoc">
<nav class="pagetoc"></nav>
</div>
</main>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
</nav>
</div>
{{#if live_reload_endpoint}}
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
{{/if}}
{{#if playground_line_numbers}}
<script>
window.playground_line_numbers = true;
</script>
{{/if}}
{{#if playground_copyable}}
<script>
window.playground_copyable = true;
</script>
{{/if}}
{{#if playground_js}}
<script src="{{ path_to_root }}ace.js"></script>
<script src="{{ path_to_root }}editor.js"></script>
<script src="{{ path_to_root }}mode-rust.js"></script>
<script src="{{ path_to_root }}theme-dawn.js"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
{{/if}}
{{#if search_js}}
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
<script src="{{ path_to_root }}mark.min.js"></script>
<script src="{{ path_to_root }}searcher.js"></script>
{{/if}}
<script src="{{ path_to_root }}clipboard.min.js"></script>
<script src="{{ path_to_root }}highlight.js"></script>
<script src="{{ path_to_root }}book.js"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script src="{{ ../path_to_root }}{{this}}"></script>
{{/each}}
{{#if is_print}}
{{#if mathjax_support}}
<script>
window.addEventListener('load', function() {
MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100);
});
});
</script>
{{else}}
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
{{/if}}
{{/if}}
</div>
</body>
</html>
@@ -1,11 +1,11 @@
<!DOCTYPE HTML>
<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>{{ title }}</title>
{{#if is_print }}
<meta name="robots" content="noindex" />
<meta name="robots" content="noindex">
{{/if}}
{{#if base_url}}
<base href="{{ base_url }}">
@@ -17,7 +17,7 @@
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<meta name="theme-color" content="#ffffff">
{{#if favicon_svg}}
<link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -53,7 +53,8 @@
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
{{/if}}
</head>
<body>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "{{ path_to_root }}";
@@ -82,61 +83,127 @@
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
html.classList.add('js');
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var html = document.querySelector('html');
var sidebar = 'hidden';
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
</label>
<!-- TODO remove weird padding here -->
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Dark</button></li>
</ul>
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
{{/if}}
<!-- CUSTOM -->
<!-- TODO style when open -->
<select id="dropdown-menu" class="icon-button">
<option value="">SDKS</option>
<option value="https://nymtech.net/docs/sdk/rust/rust.html">Rust</option>
<option value="https://sdk.nymtech.net">Typescript</option>
</select>
<!-- TODO style when open -->
<select id="dropdown-menu2" class="icon-button">
<option value="">Doc Sites</option>
<option value="https://nymtech.net/developers">Dev Portal</option>
<option value="https://nymtech.net/docs/introduction.html">Network Docs</option>
<option value="https://https://nymtech.net/operators">Operator Docs</option>
</select>
<script>
document.getElementById('dropdown-menu').addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (selected.value !== '') {
window.location.href = selected.value;
}
});
</script>
<script>
document.getElementById('dropdown-menu2').addEventListener('change', function() {
const selected = this.options[this.selectedIndex];
if (selected.value !== '') {
window.location.href = selected.value;
}
});
</script>
<!-- END CUSTOM -->
</div>
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
{{/if}}
{{#if print_enable}}
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
@@ -180,16 +247,14 @@
<div id="content" class="content">
<main>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<main>
<div class="content-wrap">
{{{ content }}}
</div>
<div class="sidetoc">
<nav class="pagetoc"></nav>
</div>
</main>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
@@ -201,7 +266,7 @@
{{/previous}}
{{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
@@ -219,7 +284,7 @@
{{/previous}}
{{#next}}
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
@@ -246,25 +311,6 @@
</script>
{{/if}}
{{#if google_analytics}}
<!-- Google Analytics Tag -->
<script>
var localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is
// inspecting the book locally...
if (localAddrs.indexOf(document.location.hostname) === -1) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{google_analytics}}', 'auto');
ga('send', 'pageview');
}
</script>
{{/if}}
{{#if playground_line_numbers}}
<script>
window.playground_line_numbers = true;
@@ -318,5 +364,7 @@
{{/if}}
{{/if}}
</div>
</body>
</html>
@@ -0,0 +1,101 @@
:root {
--toc-width: 270px;
--center-content-toc-shift: calc(-1 * var(--toc-width) / 2);
}
.nav-chapters {
/* adjust width of buttons that bring to the previous or the next page */
min-width: 50px;
}
.previous {
/*
adjust the space between the left sidebar or the left side of the screen
and the button that leads to the previous page
*/
margin-left: var(--page-padding);
}
@media only screen {
main {
display: flex;
}
@media (max-width: 1179px) {
.sidebar-hidden .sidetoc {
display: none;
}
}
@media (max-width: 1439px) {
.sidebar-visible .sidetoc {
display: none;
}
}
@media (1180px <= width <= 1439px) {
.sidebar-hidden main {
position: relative;
left: var(--center-content-toc-shift);
}
}
@media (1440px <= width <= 1700px) {
.sidebar-visible main {
position: relative;
left: var(--center-content-toc-shift);
}
}
.content-wrap {
overflow-y: auto;
width: 100%;
}
.sidetoc {
margin-top: 20px;
margin-left: 10px;
margin-right: auto;
}
.pagetoc {
position: fixed;
/* adjust TOC width */
width: var(--toc-width);
height: calc(100vh - var(--menu-bar-height) - 0.67em * 4);
overflow: auto;
}
.pagetoc a {
border-left: 1px solid var(--sidebar-bg);
color: var(--fg) !important;
display: block;
padding-bottom: 5px;
padding-top: 5px;
padding-left: 10px;
text-align: left;
text-decoration: none;
}
.pagetoc a:hover,
.pagetoc a.active {
background: var(--sidebar-bg);
color: var(--sidebar-fg) !important;
}
.pagetoc .active {
background: var(--sidebar-bg);
color: var(--sidebar-fg);
}
.pagetoc .pagetoc-H2 {
padding-left: 20px;
}
.pagetoc .pagetoc-H3 {
padding-left: 40px;
}
.pagetoc .pagetoc-H4 {
padding-left: 60px;
}
}
@media print {
.sidetoc {
display: none;
}
}
@@ -0,0 +1,114 @@
function forEach(elems, fun) {
Array.prototype.forEach.call(elems, fun);
}
function getPagetoc(){
return document.getElementsByClassName("pagetoc")[0]
}
function getPagetocElems() {
return getPagetoc().children;
}
function getHeaders(){
return document.getElementsByClassName("header")
}
// Un-active everything when you click it
function forPagetocElem(fun) {
forEach(getPagetocElems(), fun);
}
function getRect(element) {
return element.getBoundingClientRect();
}
function overflowTop(container, element) {
return getRect(container).top - getRect(element).top;
}
function overflowBottom(container, element) {
return getRect(container).bottom - getRect(element).bottom;
}
var activeHref = location.href;
var updateFunction = function (elem = undefined) {
var id = elem;
if (!id && location.href != activeHref) {
activeHref = location.href;
forPagetocElem(function (el) {
if (el.href === activeHref) {
id = el;
}
});
}
if (!id) {
var elements = getHeaders();
let margin = window.innerHeight / 3;
forEach(elements, function (el, i, arr) {
if (!id && getRect(el).top >= 0) {
if (getRect(el).top < margin) {
id = el;
} else {
id = arr[Math.max(0, i - 1)];
}
}
// a very long last section
// its heading is over the screen
if (!id && i == arr.length - 1) {
id = el
}
});
}
forPagetocElem(function (el) {
el.classList.remove("active");
});
if (!id) return;
forPagetocElem(function (el) {
if (id.href.localeCompare(el.href) == 0) {
el.classList.add("active");
let pagetoc = getPagetoc();
if (overflowTop(pagetoc, el) > 0) {
pagetoc.scrollTop = el.offsetTop;
}
if (overflowBottom(pagetoc, el) < 0) {
pagetoc.scrollTop -= overflowBottom(pagetoc, el);
}
}
});
};
let elements = getHeaders();
if (elements.length > 1) {
// Populate sidebar on load
window.addEventListener("load", function () {
var pagetoc = getPagetoc();
var elements = getHeaders();
forEach(elements, function (el) {
var link = document.createElement("a");
link.appendChild(document.createTextNode(el.text));
link.href = el.hash;
link.classList.add("pagetoc-" + el.parentElement.tagName);
pagetoc.appendChild(link);
link.onclick = function () {
updateFunction(link);
};
});
updateFunction();
});
// Handle active elements on scroll
window.addEventListener("scroll", function () {
updateFunction();
});
} else {
document.getElementsByClassName("sidetoc")[0].remove();
}
+10 -6
View File
@@ -18,6 +18,16 @@
# User Manuals
- [NymVPN alpha](nymvpn/intro.md)
- [GUI](nymvpn/gui.md)
- [Linux](nymvpn/gui-linux.md)
- [MacOS](nymvpn/gui-mac.md)
- [CLI](nymvpn/cli.md)
- [Linux](nymvpn/cli-linux.md)
- [MacOS](nymvpn/cli-mac.md)
- [Testing](nymvpn/testing.md)
- [Troubleshooting](nymvpn/troubleshooting.md)
- [NymVPN FAQ](nymvpn/faq.md)
- [NymConnect X Monero](tutorials/monero.md)
- [NymConnect X Matrix](tutorials/matrix.md)
- [NymConnect X Telegram](tutorials/telegram.md)
@@ -68,12 +78,6 @@
# Events
- [CryptoTalk](events/cryptotalk/nym-vpn.md)
- [NymVPN Linux Guide](events/cryptotalk/nym-vpn-linux.md)
- [NymVPN MacOS Guide](events/cryptotalk/nym-vpn-mac.md)
- [NymVPN Testing](events/cryptotalk/nym-vpn-testing.md)
- [NymVPN Troubleshooting](events/cryptotalk/nym-vpn-troubleshooting.md)
- [NymVPN FAQ](events/cryptotalk/nym-vpn-faq.md)
- [Web3Privacy Now](./events/web3-privacy.md)
- [HCPP23-serinko](./events/hcpp23-serinko.md)
- [HCPP23-max](./events/hcpp23-max.md)
@@ -1,198 +0,0 @@
# NymVPN alpha: Guide for GNU/Linux
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn-testing.md) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2).
```
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
You can use our automated script of follow the steps below to download, verify, install and run NymVPN client.
### Automated Script CLI Installation
Open a terminal and follow these steps:
* Download the script
```sh
curl -o nym-vpn-cli-executor.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/99cea8f4d80f2d002802ed1cbedba288bfca4488/execute-nym-vpn-cli-binary.sh
```
* Make the script executable
```sh
chmod u+x nym-vpn-cli-executor.sh
```
* Run the script
```sh
./nym-vpn-cli-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
61a08de46881411e140f66cdb4940041150adb573c10e3f510a58a86a32f08fb nym-vpn-cli-ubuntu-22.04.zip
```
The script will automatically start the client. Follow the instructions:
* The script will print a JSON view of existing Gateways and prompt you to:
- ***(Make sure to use two different Gateways for entry and exit!)***
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
- `do you want five hop or two hop?`: type `five` or `two`
### Manual CLI and GUI Installation
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) to download the binary for Debian based Linux
* Open terminal in the same directory and check the the `sha256sum` by running:
```sh
# for CLI
sha256sum ./nym-vpn-cli_0.1.0_ubuntu-22.04_amd64.zip
# for GUI
sha256sum ./nym-vpn-ui_0.0.2_ubuntu-22.04_amd64.zip
```
* Compare the result with the sha256 hash shared on the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2)
* Extract files with `unzip` command or manually as you are used to
* If you prefer to run `.AppImage` make executable by running:
```sh
# for CLI
chmod +x ./nym-vpn-cli
# for GUI
chmod +x ./appimage/nym-vpn_0.0.2_amd64.AppImage
# make sure your path to package is correct and the package name as well
```
* If you prefer to use the `.deb` version for installation (Linux only), open terminal in the same directory and run:
```sh
cd deb
sudo dpkg -i ./nym-vpn_0.0.2_amd64.deb
# or
sudo apt-get install -f ./nym-vpn_0.0.2_amd64.deb
```
* **For CLI**: Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below.
### GUI configuration
* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
```sh
# for Linux
mkdir $HOME/.config/nym-vpn/
```
* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the directory `nym-vpn` you just created
* Create the main config file called `config.toml` in the same directory with this content:
```toml
# change <USER> to your username
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env"
entry_node_location = "DE" # two letters country code
```
## Run NymVPN
* **For NymVPN to work, all other VPNs must be switched off!**
* At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
### Run CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
* Find the entire command with all the needed arguments' values and your wireguard private key for testing purposes at [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha)
* Run it with `sudo` as root, the command will look like this with specified arguments:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities see [command explanation](#cli-commands-and-options) below
### Run GUI
In case you used `.deb` package and installed the client, you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
Make sure you went through the GUI configuration in the [preparation section](#gui-configuration). Then open terminal in the same directory where you [installed](#preparation) the binary and run:
```sh
# .AppImage must be run from the same directory
sudo -E ./nym-vpn_0.0.2_amd64.AppImage
# .deb installation shall be executable from anywhere as
sudo -E nym-vpn
```
In case of errors, see [troubleshooting section](./nym-vpn-troubleshooting.md).
### CLI Commands and Options
The basic syntax of `nym-vpn-cli` is:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities run with `--help` flag:
```sh
./nym-vpn-cli --help
```
~~~admonish example collapsible=true title="Console output"
```
Usage: nym-vpn-cli [OPTIONS]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
--mixnet-client-path <MIXNET_CLIENT_PATH>
Path to the data directory of a previously initialised mixnet client, where the keys reside
--entry-gateway-id <ENTRY_GATEWAY_ID>
Mixnet public ID of the entry gateway
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
Auto-select entry gateway by country ISO
--exit-router-address <EXIT_ROUTER_ADDRESS>
Mixnet recipient address
--exit-gateway-id <EXIT_GATEWAY_ID>
--exit-router-country <EXIT_ROUTER_COUNTRY>
Mixnet recipient address
--enable-wireguard
Enable the wireguard traffic between the client and the entry gateway
--private-key <PRIVATE_KEY>
Associated private key
--wg-ip <WG_IP>
The IP address of the wireguard interface
--ip <IP>
The IP address of the TUN device
--mtu <MTU>
The MTU of the TUN device
--disable-routing
Disable routing all traffic through the VPN TUN device
--enable-two-hop
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
--enable-poisson-rate
Enable Poisson process rate limiting of outbound traffic
-h, --help
Print help
-V, --version
Print version
```
~~~
**Fundamental commands and arguments**
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users:
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/alpha/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/api/gateways)
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/alpha)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
**Advanced options**
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
@@ -1,191 +0,0 @@
# NymVPN alpha: Guide for Mac OS
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn-testing.md) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2).
```
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
### CLI Installation
You can use our automated script of follow the steps below to download, verify, install and run NymVPN client.
#### Automated Script CLI Installation
Open a terminal and follow these steps:
* Download the script
```sh
curl -o nym-vpn-client-executor.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/99cea8f4d80f2d002802ed1cbedba288bfca4488/execute-nym-vpn-cli-binary.sh
```
* Make the script executable
```sh
chmod u+x nym-vpn-client-executor.sh
```
* Run the script
```sh
./nym-vpn-client-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
96623ccc69bc4cc0e4e3e18528b6dae6be69f645d0a592d926a3158ce2d0c269 nym-vpn-cli_0.1.0_macos_x86_64.zip
```
The script will automatically start the client. Follow the instructions:
* The script will print a JSON view of existing Gateways and prompt you to:
- ***(Make sure to use two different Gateways for entry and exit!)***
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
- `do you want five hop or two hop?`: type `five` or `two`
#### Manual CLI Installation
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) to download the binary for Debian based Linux
* Open terminal in the same directory and check the the `sha256sum` by running:
```sh
# x86_64
sha256sum ./nym-vpn-cli_0.1.0_macos_x86_64.zip
# aarch64
sha256sum ./nym-vpn-cli_0.1.0_macos_aarch64.zip
```
* Compare the result with the sha256 hash shared on the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2)
* Extract files with `unzip` command or manually as you are used to
```sh
# for CLI
chmod +x ./nym-vpn-cli
```
* Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below.
### GUI Installation
We created a [script](https://gist.github.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19) which does download, extraction, installation and configuration for MacOS users automatically following the steps below:
* To download the script, open a terminal in a directory where you want to download the script and run:
```sh
curl -o nym-vpn-client-executor.sh - L https://gist.githubusercontent.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19/raw/4397365b4cf74594c7f99c1ef5d690b2f5b41192/nym-vpn-client-macos-executor.sh
```
* Make executable
```sh
chmod u+x nym-vpn-client-macos-executor.sh
```
* Run
```sh
./nym-vpn-client-macos-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
06c7c82f032f230187da1002a9a9a88242d3bbf6c5c09dc961a71df151d768d0 nym-vpn-ui_0.0.2_macos_x86_64.zip
```
* The script will run the application and it will prompt you for a country code to exit, chose one of the offered options
In case of errors check out the [troubleshooting](./nym-vpn-troubleshooting.html#installing-gui-on-macos-not-working) section.
## Run NymVPN
* **For NymVPN to work, all other VPNs must be switched off!**
* At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
### Run CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
* Find the entire command with all the needed arguments' values and your wireguard private key for testing purposes at [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha)
* Run it with `sudo` as root, the command will look like this with specified arguments:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities see [command explanation](#cli-commands-and-options) below
### Run GUI
Make sure you went through the GUI configuration in the [preparation section](#gui-installation).
You may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
* Run GUI from terminal:
```sh
sudo $nym_vpn_dir/nym-vpn
```
In case of errors, see [troubleshooting section](./nym-vpn-troubleshooting.md#macos-alert-on-nymvpn-ui-startup).
### CLI Commands and Options
The basic syntax of `nym-vpn-cli` is:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities run with `--help` flag:
```sh
./nym-vpn-cli --help
```
~~~admonish example collapsible=true title="Console output"
```
Usage: nym-vpn-cli [OPTIONS]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
--mixnet-client-path <MIXNET_CLIENT_PATH>
Path to the data directory of a previously initialised mixnet client, where the keys reside
--entry-gateway-id <ENTRY_GATEWAY_ID>
Mixnet public ID of the entry gateway
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
Auto-select entry gateway by country ISO
--exit-router-address <EXIT_ROUTER_ADDRESS>
Mixnet recipient address
--exit-gateway-id <EXIT_GATEWAY_ID>
--exit-router-country <EXIT_ROUTER_COUNTRY>
Mixnet recipient address
--enable-wireguard
Enable the wireguard traffic between the client and the entry gateway
--private-key <PRIVATE_KEY>
Associated private key
--wg-ip <WG_IP>
The IP address of the wireguard interface
--ip <IP>
The IP address of the TUN device
--mtu <MTU>
The MTU of the TUN device
--disable-routing
Disable routing all traffic through the VPN TUN device
--enable-two-hop
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
--enable-poisson-rate
Enable Poisson process rate limiting of outbound traffic
-h, --help
Print help
-V, --version
Print version
```
~~~
**Fundamental commands and arguments**
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users:
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/alpha/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/api/gateways)
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/alpha)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
**Advanced options**
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.

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