Compare commits

..

73 Commits

Author SHA1 Message Date
benedettadavico 6c491545f4 Support mnemonic in the NS agent 2025-07-04 19:39:02 +03:00
Andrej Mihajlov 8d2cd48da5 test: remove file after closing for a test 2025-06-12 15:39:52 +02:00
Andrej Mihajlov f2eb97514a Fix missing await on self.close_pool_inner() 2025-06-12 15:12:29 +02:00
Andrej Mihajlov 735535d902 Hide tokio behind feature 2025-06-06 15:21:43 +02:00
Andrej Mihajlov f7603a9973 Hide tokio and sqlx behind not(wasm32) 2025-06-06 15:21:43 +02:00
Andrej Mihajlov 693b8d5519 Exclude sqlx-pool-guard from wasm builds 2025-06-06 15:21:43 +02:00
Andrej Mihajlov f96103ab97 Log all tracing output just in case 2025-06-04 11:41:09 +02:00
Andrej Mihajlov b599ededf3 Include proc_pidinfo on iOS 2025-06-04 10:58:08 +02:00
Andrej Mihajlov d2114d3c2e Switch to tracing 2025-06-03 17:27:22 +02:00
Andrej Mihajlov 0356f0c682 Use log here 2025-06-03 15:12:55 +02:00
Andrej Mihajlov 365b12c069 Expose database path 2025-06-03 14:49:35 +02:00
Andrej Mihajlov 032281dc00 Clean up 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 9cc57f8f63 Improve windows 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 64c940a12a Cleanup 2025-06-03 10:09:11 +02:00
Andrej Mihajlov d6f0d50760 Revert 2025-06-03 10:09:11 +02:00
Andrej Mihajlov f6f361299c Use sqlite pool guard 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 3c5677b4ff Remove logs 2025-06-03 10:09:11 +02:00
Andrej Mihajlov b243062695 Document 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 946b10cc30 Add Windows impl 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 1c8831ec17 Add Windows implementation 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 3e606be545 Update Linux impl 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 3f8c2c096b Open file watch 2025-06-03 10:09:11 +02:00
Andrej Mihajlov 3ede03e1d1 Close sqlite pool before erroring 2025-06-03 10:09:11 +02:00
benedetta davico ac12455f97 add comment 2025-05-27 16:35:51 +02:00
benedetta davico 1c6db86259 Merge pull request #5803 from nymtech/benny/change-rust-version
change rust version to fix ci
2025-05-27 12:11:13 +02:00
benedetta davico e126c1f7f1 Update publish-nym-binaries.yml 2025-05-27 11:45:53 +02:00
benedetta davico 31772019cd Update ci-contracts.yml 2025-05-27 11:44:01 +02:00
benedettadavico 5369e5eab9 update changelog 2025-05-27 10:03:22 +02:00
Andrej Mihajlov 2e634c59a7 Merge pull request #5801 from nymtech/am/backport-pr-5779 2025-05-26 21:03:29 +02:00
jmwample d7383d74f3 more relaxed usage of reqwest accept-encoding 2025-05-26 17:54:19 +02:00
dynco-nym e98d60d7ce Add node_bonded field to delegations (#5759)
* Add node_bonded field to delegations
- clarifies whether the delegation is to a bonded or unbonded node
- include delegations to unbonded nodes in the returned list

* PR feedback
2025-05-19 15:18:41 +02:00
benedettadavico f47650d6c8 bump binary versions 2025-05-16 13:03:37 +02:00
benedettadavico 3b2481e5a5 merge appenzeller to develop 2025-05-16 12:59:02 +02:00
import this de47982585 [DOCs/operators]: Updated tokenomics, reward calculator & release notes v2025.9-appenzeller (#5769)
* correct expression about node stake

* typo fix

* sharpen overview

* detail rewards formula

* make calculator into standalone jsx component and import it

* finish pr for review

* fix alpha example with correct formula

* work in comments
2025-05-16 08:34:10 +00:00
Jon Häggblad fafad41230 Skip refreshing the topology on startup as we already have an initial set (#5768) 2025-05-16 09:11:34 +02:00
Jon Häggblad 79df17710d Teach HttpClientError how to report its status code and timeout (#5770) 2025-05-16 08:54:41 +02:00
benedetta davico e039ea843c Merge pull request #5743 from nymtech/tommy/remove-old-tests
Remove old test directory - Update validator docker
2025-05-16 08:34:55 +02:00
Jon Häggblad e898f202b7 Fetch the topology from the nym-api concurrently (#5767)
* Fetch the topology from the nym-api concurrently

* Add path to get_json instrument
2025-05-15 15:00:41 +02:00
Jon Häggblad ca75fec048 Update dependabot assignees (#5762) 2025-05-15 12:48:44 +02:00
Jon Häggblad 87aab4e31e Instrument create_request (#5760)
In the vpn-api client we create requests directly, so let's instrument
them as well as the currently instrumented top-level function get_json
doesn't capture that.
2025-05-15 12:46:33 +02:00
Jędrzej Stuczyński 370a4a3a03 feat: use bincode by default in NymApiClient + remove feature-lock (#5761) 2025-05-14 17:33:10 +01:00
mfahampshire 9b6b2117dd Max/general abstraction updates (#5560)
- new instance of echo server with lib / cli split 
- echo server docs update 
- tcpproxy and echosever now listen for kill signal 
- ffi bindings of tcpproxy functions updated
2025-05-14 15:51:18 +00:00
Bogdan-Ștefan Neacşu ea90d7b558 Decrease default average packet delay to 15 ms (#5754)
* Decrease default average packet delay to 15 ms

* Add upgrade for config value

* Fix ip packet router too

* Fix clippy

* Remove message_sending_average_delay from template too
2025-05-14 16:24:34 +03:00
dependabot[bot] 52e06a7eb4 build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 (#5730)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.8 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.8...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 16:25:49 +01:00
dependabot[bot] e6250fa312 build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet (#5737)
Bumps [base-x](https://github.com/cryptocoinjs/base-x) from 3.0.9 to 3.0.11.
- [Commits](https://github.com/cryptocoinjs/base-x/compare/v3.0.9...v3.0.11)

---
updated-dependencies:
- dependency-name: base-x
  dependency-version: 3.0.11
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 16:25:26 +01:00
dependabot[bot] 6d9e6a0f38 build(deps): bump ammonia from 4.0.0 to 4.1.0 (#5739)
Bumps [ammonia](https://github.com/rust-ammonia/ammonia) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/rust-ammonia/ammonia/releases)
- [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-ammonia/ammonia/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: ammonia
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 16:25:09 +01:00
dependabot[bot] c8331f4cad build(deps): bump the patch-updates group across 1 directory with 12 updates (#5753)
Bumps the patch-updates group with 11 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [chrono](https://github.com/chronotope/chrono) | `0.4.40` | `0.4.41` |
| [clap](https://github.com/clap-rs/clap) | `4.5.37` | `4.5.38` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.47` | `4.5.50` |
| [hickory-resolver](https://github.com/hickory-dns/hickory-dns) | `0.25.1` | `0.25.2` |
| [sha2](https://github.com/RustCrypto/hashes) | `0.10.8` | `0.10.9` |
| [tokio-util](https://github.com/tokio-rs/tokio) | `0.7.14` | `0.7.15` |
| [toml](https://github.com/toml-rs/toml) | `0.8.20` | `0.8.22` |
| [uniffi](https://github.com/mozilla/uniffi-rs) | `0.29.1` | `0.29.2` |
| [tendermint](https://github.com/informalsystems/tendermint-rs) | `0.40.3` | `0.40.4` |
| [tendermint-rpc](https://github.com/informalsystems/tendermint-rs) | `0.40.3` | `0.40.4` |
| [indexed_db_futures](https://github.com/Alorel/rust-indexed-db) | `0.6.1` | `0.6.4` |



Updates `chrono` from 0.4.40 to 0.4.41
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.40...v0.4.41)

Updates `clap` from 4.5.37 to 4.5.38
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

Updates `clap_complete` from 4.5.47 to 4.5.50
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.47...clap_complete-v4.5.50)

Updates `hickory-resolver` from 0.25.1 to 0.25.2
- [Release notes](https://github.com/hickory-dns/hickory-dns/releases)
- [Changelog](https://github.com/hickory-dns/hickory-dns/blob/main/OLD-CHANGELOG.md)
- [Commits](https://github.com/hickory-dns/hickory-dns/compare/v0.25.1...v0.25.2)

Updates `sha2` from 0.10.8 to 0.10.9
- [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.8...sha2-v0.10.9)

Updates `tokio-util` from 0.7.14 to 0.7.15
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.14...tokio-util-0.7.15)

Updates `toml` from 0.8.20 to 0.8.22
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.20...toml-v0.8.22)

Updates `uniffi` from 0.29.1 to 0.29.2
- [Changelog](https://github.com/mozilla/uniffi-rs/blob/v0.29.2/CHANGELOG.md)
- [Commits](https://github.com/mozilla/uniffi-rs/compare/v0.29.1...v0.29.2)

Updates `uniffi_build` from 0.29.1 to 0.29.2
- [Changelog](https://github.com/mozilla/uniffi-rs/blob/v0.29.2/CHANGELOG.md)
- [Commits](https://github.com/mozilla/uniffi-rs/compare/v0.29.1...v0.29.2)

Updates `tendermint` from 0.40.3 to 0.40.4
- [Release notes](https://github.com/informalsystems/tendermint-rs/releases)
- [Changelog](https://github.com/cometbft/tendermint-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/informalsystems/tendermint-rs/compare/v0.40.3...v0.40.4)

Updates `tendermint-rpc` from 0.40.3 to 0.40.4
- [Release notes](https://github.com/informalsystems/tendermint-rs/releases)
- [Changelog](https://github.com/cometbft/tendermint-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/informalsystems/tendermint-rs/compare/v0.40.3...v0.40.4)

Updates `indexed_db_futures` from 0.6.1 to 0.6.4
- [Release notes](https://github.com/Alorel/rust-indexed-db/releases)
- [Commits](https://github.com/Alorel/rust-indexed-db/compare/v0.6.1...v0.6.4)

---
updated-dependencies:
- dependency-name: chrono
  dependency-version: 0.4.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap_complete
  dependency-version: 4.5.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: hickory-resolver
  dependency-version: 0.25.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: sha2
  dependency-version: 0.10.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tokio-util
  dependency-version: 0.7.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: toml
  dependency-version: 0.8.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: uniffi
  dependency-version: 0.29.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: uniffi_build
  dependency-version: 0.29.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tendermint
  dependency-version: 0.40.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tendermint-rpc
  dependency-version: 0.40.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: indexed_db_futures
  dependency-version: 0.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 16:24:53 +01:00
dependabot[bot] d5a2fc7b3a build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 (#5758)
Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.45.1 to 4.45.4.
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](https://github.com/mikefarah/yq/compare/v4.45.1...v4.45.4)

---
updated-dependencies:
- dependency-name: mikefarah/yq
  dependency-version: 4.45.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-13 16:23:46 +01:00
Jędrzej Stuczyński 6559fadf7f feat: expires header for /active nym-api responses (#5755)
* refactor FormattedResponse to allow attaching additional headers

* helper method for including expiration headers

* add expires header for /active nodes responses

* added additional 'with_expires_header_delta' builder to FormattedResponse to allow setting expiration header with time delta
2025-05-13 16:01:57 +01:00
Jon Häggblad b68f02be6a Upgrade prometheus crate to fix security warning (#5747)
Upgrade the `prometheus` crate to bump the version of the protobuf
crate, which is flagged by `cargo audit` as having a security issue
RUSTSEC-2024-0437.

VPN-3074
2025-05-13 14:11:13 +02:00
benedettadavico 3f6acbfd66 update changelog 2025-05-13 11:42:50 +02:00
Drazen Urch a830881ba5 Raw route submissions (#5756)
* Handle PG connection failures

* Readibility nit
2025-05-12 17:36:10 +02:00
Simon Wicky a3a234b41b [Feature] RememberMe is the new don't ForgetMe (#5742)
* move SessionType into statsitcis common crate

* add RememberMe to clients config

* change stats collection logic to handle remember me

* set up sdk client to send remember me message

* bump NS API version
2025-05-09 14:43:32 +02:00
Jędrzej Stuczyński 8730a84a8e feat: nym-api bincode + yaml support (#5745)
* introduce 'Bincode' variant for FormattedResponse

* allow nym-api to return responses in bincode (and also yaml)

* client parsing support

* cargo fmt

* missing changes to nym-api tests

* fixed node status api build + adjusted NymApiClient construction

* NMv2 fixes + further api changes

* feature-locking http-api-common to fix wasm build
2025-05-09 10:11:22 +01:00
Jon Häggblad 5bdda911a9 Downgrade deranged crate to 0.4.0 (#5746)
Downgrade the crate `deranged` from 0.4.1 to 0.4.0, as 0.4.1 was yanked
and is flagged by `cargo audit`.
2025-05-08 15:05:27 +02:00
Jon Häggblad 419e16eb31 Remove pretty_env_logger and switch remaining crates to use tracing (#5749)
* Remove pretty_env_logger dependency

* Switch remaining instances of pretty_env_logger to tracing
2025-05-08 15:05:08 +02:00
Jon Häggblad dcc663891a Update pretty_env_logger to latest to not depend on unmaintained crate atty (#5748)
The crate `atty` is flagged to be unmaintained and also having some
security issues.

https://rustsec.org/advisories/RUSTSEC-2021-0145
https://rustsec.org/advisories/RUSTSEC-2024-0375

Updating the dependency `pretty_env_logger` solves this
2025-05-08 11:29:12 +02:00
Tommy Verrall 9c85dc022d revert back to correct denoms for nym-cli usage 2025-05-07 18:10:06 +02:00
Tommy Verrall 5b4e386b21 fix up files
- run from root
- use colima to run from silicon based machines
- update readme
2025-05-07 17:30:26 +02:00
Simon Wicky f4e4f262ae fix parralel feature in ecash crate with send + sync (#5744) 2025-05-07 14:27:15 +02:00
Tommy Verrall 75c81b3206 clean up 2025-05-07 12:18:28 +02:00
Tommy Verrall b7657e488b un needed dir and contents 2025-05-07 12:17:46 +02:00
Tommy Verrall 546054615a typos 2025-05-07 12:14:19 +02:00
Tommy Verrall 6d4ba18d86 remove old non working docker files
- replace with just the validator
- all other operations can be derived from that
2025-05-07 12:12:44 +02:00
benedettadavico 899a2bfc8a bump binary versions 2025-05-07 11:22:45 +02:00
Tommy Verrall 57096bd86e remove and clean up 2025-05-07 10:24:18 +02:00
import this 3049abf4f1 [DOCs/operators]: Tokenomics hotfix 2025-05-05 12:20:44 +00:00
benedetta davico 1dc42df59c Merge pull request #5734 from nymtech/release/2025.8-tourist
Merge release/2025.8-tourist to develop
2025-05-05 12:11:28 +02:00
import this e2b85c91df [DOCs]: TimeNow and Vars value sync up (#5736) 2025-04-30 12:38:31 +00:00
import this 796a7fba0a [DOCs/operators]: Tokenomics updates & v2025.8-tourist release notes (#5732)
* initialise tokenomics update

* ready for review

* move info block lower down

* edit phrasing and add formulas

* delete extra syntax

* update syntax

* add release notes
2025-04-30 12:13:41 +00:00
benedettadavico e594630314 update changelog 2025-04-29 12:19:54 +02:00
benedettadavico 9c2595d9ef bump versions 2025-04-25 15:47:20 +02:00
Jędrzej Stuczyński 5ad1f0b61a add reserved byte to reply surb serialisation (#5731) 2025-04-25 10:02:32 +01:00
243 changed files with 6544 additions and 5272 deletions
-2
View File
@@ -31,5 +31,3 @@ updates:
update-types:
- "patch"
open-pull-requests-limit: 10
assignees:
- "octol"
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+2 -1
View File
@@ -27,7 +27,8 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
# pinned due to issues building contracts
toolchain: 1.86.0
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
+1 -1
View File
@@ -52,7 +52,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.86.0
override: true
- name: Build all binaries
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
@@ -31,7 +31,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+100
View File
@@ -4,6 +4,106 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.10-brie] (2025-05-27)
- Backport PR 5779 ([#5801])
- Expanded Accept Encoding for `reqwest` ([#5779])
- Teach HttpClientError how to report its status code and timeout ([#5770])
- Skip refreshing the topology on startup as we already have an initial set ([#5768])
- Fetch the topology from the nym-api concurrently ([#5767])
- feat: use bincode by default in NymApiClient + remove feature-lock ([#5761])
- Instrument create_request ([#5760])
- Add node_bonded field to delegations ([#5759])
- build(deps): bump mikefarah/yq from 4.45.1 to 4.45.4 ([#5758])
- Raw route submissions ([#5756])
- feat: expires header for `/active` nym-api responses ([#5755])
- Decrease default average packet delay to 15 ms ([#5754])
- build(deps): bump the patch-updates group across 1 directory with 12 updates ([#5753])
- Remove pretty_env_logger and switch remaining crates to use tracing ([#5749])
- Update pretty_env_logger to latest to not depend on unmaintained crate atty ([#5748])
- Upgrade prometheus crate to fix security warning ([#5747])
- Downgrade deranged crate to 0.4.0 ([#5746])
- feat: nym-api bincode + yaml support ([#5745])
- fix parallel feature in ecash crate with send + sync ([#5744])
- Remove old test directory - Update validator docker ([#5743])
- [Feature] `RememberMe` is the new don't `ForgetMe` ([#5742])
- build(deps): bump ammonia from 4.0.0 to 4.1.0 ([#5739])
- build(deps): bump base-x from 3.0.9 to 3.0.11 in /testnet-faucet ([#5737])
- build(deps): bump http-proxy-middleware from 2.0.8 to 2.0.9 ([#5730])
[#5801]: https://github.com/nymtech/nym/pull/5801
[#5779]: https://github.com/nymtech/nym/pull/5779
[#5770]: https://github.com/nymtech/nym/pull/5770
[#5768]: https://github.com/nymtech/nym/pull/5768
[#5767]: https://github.com/nymtech/nym/pull/5767
[#5761]: https://github.com/nymtech/nym/pull/5761
[#5760]: https://github.com/nymtech/nym/pull/5760
[#5759]: https://github.com/nymtech/nym/pull/5759
[#5758]: https://github.com/nymtech/nym/pull/5758
[#5756]: https://github.com/nymtech/nym/pull/5756
[#5755]: https://github.com/nymtech/nym/pull/5755
[#5754]: https://github.com/nymtech/nym/pull/5754
[#5753]: https://github.com/nymtech/nym/pull/5753
[#5749]: https://github.com/nymtech/nym/pull/5749
[#5748]: https://github.com/nymtech/nym/pull/5748
[#5747]: https://github.com/nymtech/nym/pull/5747
[#5746]: https://github.com/nymtech/nym/pull/5746
[#5745]: https://github.com/nymtech/nym/pull/5745
[#5744]: https://github.com/nymtech/nym/pull/5744
[#5743]: https://github.com/nymtech/nym/pull/5743
[#5742]: https://github.com/nymtech/nym/pull/5742
[#5739]: https://github.com/nymtech/nym/pull/5739
[#5737]: https://github.com/nymtech/nym/pull/5737
[#5730]: https://github.com/nymtech/nym/pull/5730
## [2025.9-appenzeller] (2025-05-13)
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
- build(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 in /wasm/mix-fetch/go-mix-conn ([#5720])
- build(deps-dev): bump http-proxy-middleware from 2.0.6 to 2.0.9 in /wasm/client/internal-dev ([#5719])
- Add /account/{address} ([#5673])
- Add contains ticketbook data db query ([#5670])
[#5722]: https://github.com/nymtech/nym/pull/5722
[#5720]: https://github.com/nymtech/nym/pull/5720
[#5719]: https://github.com/nymtech/nym/pull/5719
[#5673]: https://github.com/nymtech/nym/pull/5673
[#5670]: https://github.com/nymtech/nym/pull/5670
## [2025.8-tourist] (2025-04-29)
- add reserved byte to reply surb serialisation ([#5731])
- Remove inactive peers ([#5721])
- Update Hickory DNS "0.24.4" to "0.25" ([#5709])
- build(deps): bump the patch-updates group across 1 directory with 7 updates ([#5708])
- Peer handle should die more gracefully ([#5704])
- build(deps): bump crossbeam-channel from 0.5.14 to 0.5.15 ([#5702])
- build(deps): bump actions/checkout from 3 to 4 ([#5700])
- Feature/updated sphinx payload keys ([#5698])
- Bump the nym-vpn deb metapackage to 1.0 ([#5697])
- Make mix hops optional for Mixnet Client ([#5696])
- build(deps): bump tokio from 1.44.1 to 1.44.2 ([#5693])
- Feature/replay protection ([#5682])
- Adding fresh nym-api tests and workflow ([#5659])
- build(deps): bump next from 14.2.21 to 14.2.25 ([#5655])
- build(deps): bump pnpm/action-setup from 4.0.0 to 4.1.0 ([#5436])
[#5731]: https://github.com/nymtech/nym/pull/5731
[#5721]: https://github.com/nymtech/nym/pull/5721
[#5709]: https://github.com/nymtech/nym/pull/5709
[#5708]: https://github.com/nymtech/nym/pull/5708
[#5704]: https://github.com/nymtech/nym/pull/5704
[#5702]: https://github.com/nymtech/nym/pull/5702
[#5700]: https://github.com/nymtech/nym/pull/5700
[#5698]: https://github.com/nymtech/nym/pull/5698
[#5697]: https://github.com/nymtech/nym/pull/5697
[#5696]: https://github.com/nymtech/nym/pull/5696
[#5693]: https://github.com/nymtech/nym/pull/5693
[#5682]: https://github.com/nymtech/nym/pull/5682
[#5659]: https://github.com/nymtech/nym/pull/5659
[#5655]: https://github.com/nymtech/nym/pull/5655
[#5436]: https://github.com/nymtech/nym/pull/5436
## [2025.7-tex] (2025-04-14)
- Expand /v3/nym-nodes with geodata ([#5686])
Generated
+421 -243
View File
File diff suppressed because it is too large Load Diff
+13 -18
View File
@@ -122,7 +122,7 @@ members = [
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
"tools/echo-server",
"sqlx-pool-guard",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
@@ -161,12 +161,7 @@ default-members = [
"tools/nymvisor",
]
exclude = [
"explorer",
"contracts",
"nym-wallet",
"cpu-cycles",
]
exclude = ["explorer", "contracts", "nym-wallet", "cpu-cycles"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -209,9 +204,9 @@ celes = "2.6.0"
cfg-if = "1.0.0"
chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.40"
chrono = "0.4.41"
cipher = "0.4.3"
clap = "4.5.37"
clap = "4.5.38"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
@@ -286,8 +281,8 @@ pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.1"
pin-project-lite = "0.2.16"
pretty_env_logger = "0.4.0"
publicsuffix = "2.3.0"
proc_pidinfo = "0.1.3"
quote = "1"
rand = "0.8.5"
rand_chacha = "0.3"
@@ -310,7 +305,7 @@ serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
sha2 = "0.10.9"
si-scale = "0.2.3"
sphinx-packet = "=0.6.0"
sqlx = "0.7.4"
@@ -330,8 +325,8 @@ tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.14"
toml = "0.8.20"
tokio-util = "0.7.15"
toml = "0.8.22"
tower = "0.5.2"
tower-http = "0.5.2"
tracing = "0.1.41"
@@ -342,7 +337,7 @@ tracing-tree = "0.2.2"
tracing-indicatif = "0.3.9"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
uniffi = "0.29.1"
uniffi = "0.29.2"
uniffi_build = "0.29.0"
url = "2.5"
utoipa = "5.2"
@@ -355,7 +350,7 @@ wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.7.0"
prometheus = { version = "0.13.0" }
prometheus = { version = "0.14.0" }
# coconut/DKG related
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
@@ -385,15 +380,15 @@ bip32 = { version = "0.5.3", default-features = false }
cosmrs = { version = "0.21.1" }
tendermint = "0.40.3"
tendermint-rpc = "0.40.3"
tendermint = "0.40.4"
tendermint-rpc = "0.40.4"
prost = { version = "0.13", default-features = false }
# wasm-related dependencies
gloo-utils = "0.2.0"
gloo-net = "0.6.0"
indexed_db_futures = "0.6.1"
indexed_db_futures = "0.6.4"
js-sys = "0.3.76"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.53"
version = "1.1.56"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -46,6 +46,7 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = [
"output_format",
"clap",
"basic_tracing",
] }
nym-client-core = { path = "../../common/client-core", features = [
"fs-credentials-storage",
+1
View File
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
mod persistence;
mod template;
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::persistence::ClientPaths;
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
use crate::client::config::{default_config_filepath, Socket, SocketType};
use crate::error::ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
@@ -14,6 +14,8 @@ use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
use super::old_config_v1_1_54::ConfigV1_1_54;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPathsV1_1_33 {
#[serde(flatten)]
@@ -33,6 +35,21 @@ pub struct ConfigV1_1_33 {
pub logging: LoggingSettings,
}
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
type Error = ClientError;
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
Ok(ConfigV1_1_54 {
base: value.base.into(),
socket: value.socket.into(),
storage_paths: ClientPaths {
common_paths: value.storage_paths.common_paths.upgrade_default()?,
},
logging: value.logging,
})
}
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
@@ -41,17 +58,6 @@ impl ConfigV1_1_33 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, ClientError> {
Ok(Config {
base: self.base.into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
@@ -0,0 +1,41 @@
use std::{io, path::Path};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseConfigV1_1_54;
use nym_config::read_config_from_toml_file;
use serde::{Deserialize, Serialize};
use crate::error::ClientError;
use super::{default_config_filepath, persistence::ClientPaths, Config, Socket};
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
pub struct ConfigV1_1_54 {
#[serde(flatten)]
pub base: BaseConfigV1_1_54,
pub socket: Socket,
pub storage_paths: ClientPaths,
pub logging: LoggingSettings,
}
impl ConfigV1_1_54 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, ClientError> {
Ok(Config {
base: self.base.into(),
socket: self.socket,
storage_paths: self.storage_paths,
logging: self.logging,
})
}
}
@@ -92,10 +92,6 @@ host = '{{ socket.host }}'
[debug]
[debug.traffic]
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
[debug.acknowledgements]
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
+28 -4
View File
@@ -5,6 +5,7 @@ use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::client::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::client::config::old_config_v1_1_54::ConfigV1_1_54;
use crate::client::config::{BaseClientConfig, Config};
use crate::commands::ecash::Ecash;
use crate::error::ClientError;
@@ -177,7 +178,8 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
let old_paths = updated_step3.storage_paths.clone();
let updated = updated_step3.try_upgrade()?;
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
let updated = updated_step4.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -205,7 +207,8 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
let old_paths = updated_step2.storage_paths.clone();
let updated = updated_step2.try_upgrade()?;
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
let updated = updated_step3.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -229,7 +232,8 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
let (updated_step1, gateway_config) = old_config.upgrade()?;
let old_paths = updated_step1.storage_paths.clone();
let updated = updated_step1.try_upgrade()?;
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
let updated = updated_step2.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -252,7 +256,8 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
info!("It is going to get updated to the current specification.");
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
let updated = updated_step1.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -265,6 +270,22 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
Ok(true)
}
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, ClientError> {
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.54 config template.");
info!("It is going to get updated to the current specification.");
let updated = old_config.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_13_config(id).await? {
return Ok(());
@@ -278,6 +299,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_54_config(id).await? {
return Ok(());
}
Ok(())
}
+2 -2
View File
@@ -4,7 +4,7 @@
use std::error::Error;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
use nym_network_defaults::setup_env;
pub mod client;
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
setup_logging();
setup_tracing_logger();
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.53"
version = "1.1.56"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -27,6 +27,7 @@ zeroize = { workspace = true }
nym-bin-common = { path = "../../common/bin-common", features = [
"output_format",
"clap",
"basic_tracing",
] }
nym-client-core = { path = "../../common/client-core", features = [
"fs-credentials-storage",
+36 -10
View File
@@ -7,6 +7,7 @@ use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::config::old_config_v1_1_54::ConfigV1_1_54;
use crate::config::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
@@ -204,15 +205,16 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = updated_step3.storage_paths.clone();
let updated_step4: ConfigV1_1_33 = updated_step3.into();
let updated = updated_step4.try_upgrade()?;
let updated_step5: ConfigV1_1_54 = updated_step4.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step5.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step5.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -234,15 +236,16 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = updated_step2.storage_paths.clone();
let updated_step3: ConfigV1_1_33 = updated_step2.into();
let updated = updated_step3.try_upgrade()?;
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step4.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step4.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -261,15 +264,17 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientErro
let old_paths = updated_step1.storage_paths.clone();
let updated_step2: ConfigV1_1_33 = updated_step1.into();
let updated = updated_step2.try_upgrade()?;
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step3.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step3.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -287,15 +292,16 @@ async fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = old_config.storage_paths.clone();
let updated_step1: ConfigV1_1_33 = old_config.into();
let updated = updated_step1.try_upgrade()?;
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step2.storage_paths.common_paths,
None,
)
.await?;
let updated = updated_step2.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -312,15 +318,32 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step1.storage_paths.common_paths,
None,
)
.await?;
let updated = updated_step1.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.54 config template.");
info!("It is going to get updated to the current specification.");
let updated = old_config.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -341,6 +364,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_54_config(id).await? {
return Ok(());
}
Ok(())
}
+1
View File
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
mod persistence;
mod template;
+17 -11
View File
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{default_config_filepath, Config, SocksClientPaths};
use crate::config::{default_config_filepath, SocksClientPaths};
use crate::error::Socks5ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
@@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
use super::old_config_v1_1_54::ConfigV1_1_54;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SocksClientPathsV1_1_33 {
#[serde(flatten)]
@@ -28,6 +30,20 @@ pub struct ConfigV1_1_33 {
pub logging: LoggingSettings,
}
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
type Error = Socks5ClientError;
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
Ok(ConfigV1_1_54 {
core: value.core.into(),
storage_paths: SocksClientPaths {
common_paths: value.storage_paths.common_paths.upgrade_default()?,
},
logging: value.logging,
})
}
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
@@ -36,14 +52,4 @@ impl ConfigV1_1_33 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
Ok(Config {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
@@ -0,0 +1,39 @@
use std::{io, path::Path};
use nym_bin_common::logging::LoggingSettings;
use nym_config::read_config_from_toml_file;
use nym_socks5_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as CoreConfigV1_1_54;
use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::error::Socks5ClientError;
use super::{default_config_filepath, SocksClientPaths};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_54 {
pub core: CoreConfigV1_1_54,
pub storage_paths: SocksClientPaths,
pub logging: LoggingSettings,
}
impl ConfigV1_1_54 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
Ok(Config {
core: self.core.into(),
storage_paths: self.storage_paths,
logging: self.logging,
})
}
}
-4
View File
@@ -98,10 +98,6 @@ send_anonymously = {{ core.socks5.send_anonymously }}
[core.debug]
[core.debug.traffic]
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
[core.debug.acknowledgements]
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
+2 -2
View File
@@ -4,7 +4,7 @@
use std::error::Error;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
use nym_network_defaults::setup_env;
mod commands;
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
setup_logging();
setup_tracing_logger();
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
-1
View File
@@ -13,7 +13,6 @@ clap_complete = { workspace = true, optional = true }
clap_complete_fig = { workspace = true, optional = true }
const-str = { workspace = true }
log = { workspace = true }
pretty_env_logger = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
-23
View File
@@ -21,29 +21,6 @@ pub struct LoggingSettings {
// well, we need to implement something here at some point...
}
// I'd argue we should start transitioning from `log` to `tracing`
pub fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.init();
}
// don't call init so that we could attach additional layers
#[cfg(feature = "basic_tracing")]
pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
@@ -19,6 +19,7 @@ nym-pemstore = { path = "../../pemstore", optional = true }
# those are pulling so many deps T.T
nym-sphinx-params = { path = "../../nymsphinx/params" }
nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
nym-statistics-common = { path = "../../statistics" }
[features]
+60 -3
View File
@@ -5,6 +5,7 @@ use nym_config::defaults::NymNetworkDetails;
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -22,7 +23,7 @@ const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
@@ -375,14 +376,12 @@ pub struct Traffic {
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
@@ -718,6 +717,9 @@ pub struct DebugConfig {
/// Defines all configuration options related to the forget me flag.
pub forget_me: ForgetMe,
/// Defines all configuration options related to the remember me flag.
pub remember_me: RememberMe,
}
impl DebugConfig {
@@ -741,6 +743,7 @@ impl Default for DebugConfig {
reply_surbs: Default::default(),
stats_reporting: Default::default(),
forget_me: Default::default(),
remember_me: Default::default(),
}
}
}
@@ -806,3 +809,57 @@ impl ForgetMe {
}
}
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct RememberMe {
/// Signal that this client should be accounted for in the stats
stats: bool,
/// Type of the session to remember, if it should be remembered
session_type: SessionType,
}
impl RememberMe {
pub fn new_vpn() -> Self {
Self {
stats: true,
session_type: SessionType::Vpn,
}
}
pub fn new_mixnet() -> Self {
Self {
stats: true,
session_type: SessionType::Mixnet,
}
}
pub fn new_native() -> Self {
Self {
stats: true,
session_type: SessionType::Native,
}
}
pub fn new(stats: bool, session_type: SessionType) -> Self {
Self {
stats,
session_type,
}
}
pub fn new_none() -> Self {
Self {
stats: false,
session_type: SessionType::Unknown,
}
}
pub fn session_type(&self) -> SessionType {
self.session_type
}
pub fn stats(&self) -> bool {
self.stats
}
}
@@ -6,6 +6,7 @@ pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
pub mod v6;
// aliases for backwards compatibility
pub use v1 as old_config_v1_1_13;
@@ -13,3 +14,4 @@ pub use v2 as old_config_v1_1_20;
pub use v3 as old_config_v1_1_20_2;
pub use v4 as old_config_v1_1_30;
pub use v5 as old_config_v1_1_33;
pub use v6 as old_config_v1_1_54;
+12 -14
View File
@@ -1,16 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, ReplySurbs,
Topology, Traffic,
};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use super::v6::*;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
@@ -87,18 +85,18 @@ pub struct ConfigV5 {
pub debug: DebugConfigV5,
}
impl From<ConfigV5> for Config {
impl From<ConfigV5> for ConfigV6 {
fn from(value: ConfigV5) -> Self {
Config {
client: Client {
ConfigV6 {
client: ClientV6 {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
debug: DebugConfigV6 {
traffic: TrafficV6 {
average_packet_delay: value.debug.traffic.average_packet_delay,
message_sending_average_delay: value
.debug
@@ -113,7 +111,7 @@ impl From<ConfigV5> for Config {
packet_type: value.debug.traffic.packet_type,
..Default::default()
},
cover_traffic: CoverTraffic {
cover_traffic: CoverTrafficV6 {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
@@ -127,18 +125,18 @@ impl From<ConfigV5> for Config {
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_connection: GatewayConnectionV6 {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
acknowledgements: AcknowledgementsV6 {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology: TopologyV6 {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
@@ -148,7 +146,7 @@ impl From<ConfigV5> for Config {
.max_startup_gateway_waiting_period,
..Default::default()
},
reply_surbs: ReplySurbs {
reply_surbs: ReplySurbsV6 {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
@@ -0,0 +1,623 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, ForgetMe, GatewayConnection,
RememberMe, ReplySurbs, StatsReporting, Topology, Traffic,
};
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// the same values as our current (10.06.24) blacklist
const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// stats reporting related
/// Time interval between reporting statistics to the given provider if it exists
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
// aliases for backwards compatibility
pub type ConfigV1_1_54 = ConfigV6;
pub type ClientV1_1_54 = ClientV6;
pub type DebugConfigV1_1_54 = DebugConfigV6;
pub type TrafficV1_1_54 = TrafficV6;
pub type CoverTrafficV1_1_54 = CoverTrafficV6;
pub type GatewayConnectionV1_1_54 = GatewayConnectionV6;
pub type AcknowledgementsV1_1_54 = AcknowledgementsV6;
pub type TopologyV1_1_54 = TopologyV6;
pub type ReplySurbsV1_1_54 = ReplySurbsV6;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV6 {
pub client: ClientV6,
#[serde(default)]
pub debug: DebugConfigV6,
}
impl From<ConfigV6> for Config {
fn from(value: ConfigV6) -> Self {
Config {
client: Client {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: value
.debug
.traffic
.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.debug
.traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: value.debug.traffic.primary_packet_size,
secondary_packet_size: value.debug.traffic.secondary_packet_size,
packet_type: value.debug.traffic.packet_type,
deterministic_route_selection: value
.debug
.traffic
.deterministic_route_selection,
maximum_number_of_retransmissions: value
.debug
.traffic
.maximum_number_of_retransmissions,
use_legacy_sphinx_format: value.debug.traffic.use_legacy_sphinx_format,
disable_mix_hops: value.debug.traffic.disable_mix_hops,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value
.debug
.cover_traffic
.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value
.debug
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
max_startup_gateway_waiting_period: value
.debug
.topology
.max_startup_gateway_waiting_period,
minimum_mixnode_performance: value.debug.topology.minimum_mixnode_performance,
minimum_gateway_performance: value.debug.topology.minimum_gateway_performance,
use_extended_topology: value.debug.topology.use_extended_topology,
ignore_egress_epoch_role: value.debug.topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: value.debug.topology.ignore_ingress_epoch_role,
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value
.debug
.reply_surbs
.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
minimum_reply_surb_threshold_buffer: value
.debug
.reply_surbs
.minimum_reply_surb_threshold_buffer,
fresh_sender_tags: value.debug.reply_surbs.fresh_sender_tags,
},
stats_reporting: StatsReporting {
enabled: value.debug.stats_reporting.enabled,
provider_address: value.debug.stats_reporting.provider_address,
reporting_interval: value.debug.stats_reporting.reporting_interval,
},
forget_me: ForgetMe {
client: value.debug.forget_me.client,
stats: value.debug.forget_me.stats,
},
remember_me: RememberMe {
stats: value.debug.remember_me.stats,
session_type: value.debug.remember_me.session_type.into(),
},
},
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct ClientV6 {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrafficV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specify whether route selection should be determined by the packet header.
pub deterministic_route_selection: bool,
/// Specify how many times particular packet can be retransmitted
/// None - no limit
pub maximum_number_of_retransmissions: Option<u32>,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
/// Specify whether any constructed sphinx packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
/// this affects any forward packets, acks and reply surbs
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
/// and support updated format.
/// in the case of reply surbs, the recipient must also understand the new encoding
pub use_legacy_sphinx_format: bool,
pub packet_type: PacketType,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
pub disable_mix_hops: bool,
}
impl Default for TrafficV6 {
fn default() -> Self {
TrafficV6 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
deterministic_route_selection: false,
maximum_number_of_retransmissions: None,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
// we should use the legacy format until sufficient number of nodes understand the
// improved encoding
use_legacy_sphinx_format: true,
disable_mix_hops: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficV6 {
fn default() -> Self {
CoverTrafficV6 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV6 {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnectionV6 {
fn default() -> Self {
GatewayConnectionV6 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for AcknowledgementsV6 {
fn default() -> Self {
AcknowledgementsV6 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV6 {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies a minimum performance of a mixnode that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_mixnode_performance: u8,
/// Specifies a minimum performance of a gateway that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_gateway_performance: u8,
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
pub ignore_ingress_epoch_role: bool,
}
impl Default for TopologyV6 {
fn default() -> Self {
TopologyV6 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
ignore_egress_epoch_role: true,
ignore_ingress_epoch_role: true,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV6 {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
pub minimum_reply_surb_threshold_buffer: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl Default for ReplySurbsV6 {
fn default() -> Self {
ReplySurbsV6 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
fresh_sender_tags: false,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV6 {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficV6,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTrafficV6,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnectionV6,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: AcknowledgementsV6,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: TopologyV6,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbsV6,
/// Defines all configuration options related to stats reporting.
pub stats_reporting: StatsReportingV6,
/// Defines all configuration options related to the forget me flag.
pub forget_me: ForgetMeV6,
/// Defines all configuration options related to the remember me flag.
pub remember_me: RememberMeV6,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct StatsReportingV6 {
/// Is stats reporting enabled
pub enabled: bool,
/// Address of the stats collector. If this is none, no reporting will happen, regardless of `enabled`
#[serde(
serialize_with = "ser_maybe_stringified",
deserialize_with = "de_maybe_stringified"
)]
pub provider_address: Option<Recipient>,
/// With what frequence will statistics be sent
#[serde(with = "humantime_serde")]
pub reporting_interval: Duration,
}
impl Default for StatsReportingV6 {
fn default() -> Self {
StatsReportingV6 {
enabled: true,
provider_address: None,
reporting_interval: STATS_REPORT_INTERVAL_SECS,
}
}
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct ForgetMeV6 {
client: bool,
stats: bool,
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct RememberMeV6 {
/// Signal that this client should be accounted for in the stats
stats: bool,
/// Type of the session to remember, if it should be remembered
session_type: SessionTypeV6,
}
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Default, Debug)]
pub enum SessionTypeV6 {
Vpn,
Mixnet,
Wasm,
Native,
Socks5,
#[default]
Unknown,
}
impl From<SessionTypeV6> for SessionType {
fn from(value: SessionTypeV6) -> Self {
match value {
SessionTypeV6::Vpn => Self::Vpn,
SessionTypeV6::Mixnet => Self::Mixnet,
SessionTypeV6::Wasm => Self::Wasm,
SessionTypeV6::Native => Self::Native,
SessionTypeV6::Socks5 => Self::Socks5,
SessionTypeV6::Unknown => Self::Unknown,
}
}
}
@@ -2,8 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::BadGateway;
use std::io;
use std::path::PathBuf;
use std::{io, path::PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
@@ -19,7 +18,6 @@ pub enum StorageError {
#[error("failed to perform sqlx migration: {source}")]
MigrationError {
#[source]
#[from]
source: sqlx::migrate::MigrateError,
},
@@ -32,7 +30,6 @@ pub enum StorageError {
#[error("failed to run the SQL query: {source}")]
QueryError {
#[source]
#[from]
source: sqlx::error::Error,
},
@@ -36,7 +36,7 @@ use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_config_types::ForgetMe;
use nym_client_core_config_types::{ForgetMe, RememberMe};
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{ed25519, x25519};
@@ -238,6 +238,12 @@ where
self
}
#[must_use]
pub fn with_remember_me(mut self, remember_me: &RememberMe) -> Self {
self.config.debug.remember_me = *remember_me;
self
}
#[must_use]
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
@@ -930,6 +936,7 @@ where
task_handle: shutdown,
client_request_sender,
forget_me: self.config.debug.forget_me,
remember_me: self.config.debug.remember_me,
})
}
}
@@ -944,4 +951,5 @@ pub struct BaseClient {
pub client_request_sender: ClientRequestSender,
pub task_handle: TaskHandle,
pub forget_me: ForgetMe,
pub remember_me: RememberMe,
}
@@ -1,20 +1,18 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::replies::reply_storage::{
fs_backend, CombinedReplyStorage, ReplyStorageBackend,
use crate::{
client::replies::reply_storage::{fs_backend, CombinedReplyStorage, ReplyStorageBackend},
config,
config::Config,
error::ClientCoreError,
};
use crate::config;
use crate::config::Config;
use crate::error::ClientCoreError;
use log::{error, info, trace};
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::nyxd;
use nym_validator_client::QueryHttpRpcNyxdClient;
use std::path::Path;
use std::{fs, io};
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient};
use std::{io, path::Path};
use time::OffsetDateTime;
use url::Url;
@@ -22,11 +20,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
db_path: P,
surb_config: &config::ReplySurbs,
) -> Result<fs_backend::Backend, ClientCoreError> {
info!("creating fresh surb database");
info!("Creating fresh surb database");
let mut storage_backend = match fs_backend::Backend::init(db_path).await {
Ok(backend) => backend,
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}");
error!("setup_fresh_backend: Failed to setup persistent storage backend for our reply needs: {err}");
return Err(ClientCoreError::SurbStorageError {
source: Box::new(err),
});
@@ -40,14 +38,15 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
surb_config.minimum_reply_surb_storage_threshold,
surb_config.maximum_reply_surb_storage_threshold,
);
storage_backend
.init_fresh(&mem_store)
.await
.map_err(|err| ClientCoreError::SurbStorageError {
source: Box::new(err),
})?;
Ok(storage_backend)
match storage_backend.init_fresh(&mem_store).await {
Ok(()) => Ok(storage_backend),
Err(err) => {
storage_backend.shutdown().await;
Err(ClientCoreError::SurbStorageError {
source: Box::new(err),
})
}
}
}
// fn setup_inactive_backend(surb_config: &config::ReplySurbs) -> fs_backend::Backend {
@@ -58,12 +57,11 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
// )
// }
fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
let db_path = db_path.as_ref();
debug_assert!(db_path.exists());
let now = OffsetDateTime::now_utc().unix_timestamp();
let suffix = format!("_{now}.corrupted");
let new_extension =
@@ -72,11 +70,15 @@ fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()> {
} else {
suffix
};
let renamed = db_path.with_extension(new_extension);
let mut renamed = db_path.to_owned();
renamed.set_extension(new_extension);
fs::rename(db_path, renamed)
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
error!(
"Failed to rename corrupt database file: {} to {}",
db_path.display(),
renamed.display()
);
})
}
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
@@ -87,13 +89,12 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
// the existing one
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
info!("Loading existing surb database");
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
archive_corrupted_database(db_path)?;
error!("setup_fs_reply_surb_backend: Failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
archive_corrupted_database(db_path).await?;
setup_fresh_backend(db_path, surb_config).await
}
}
@@ -157,6 +157,12 @@ impl TopologyRefresher {
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
// We already have an initial topology, so no need to refresh it immediately.
// My understanding is that js setInterval does not fire immediately, so it's not
// needed there.
#[cfg(not(target_arch = "wasm32"))]
interval.next().await;
while !self.task_client.is_shutdown() {
tokio::select! {
_ = interval.next() => {
@@ -70,6 +70,10 @@ impl NymApiTopologyProvider {
}
}
pub fn disable_bincode(&mut self) {
self.validator_client.use_bincode = false;
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
@@ -82,20 +86,13 @@ impl NymApiTopologyProvider {
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let rewarded_set = self
.validator_client
.get_current_rewarded_set()
.await
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
.ok()?;
let rewarded_set_fut = self.validator_client.get_current_rewarded_set();
let mut topology = NymTopology::new_empty(rewarded_set);
let topology = if self.config.use_extended_topology {
let all_nodes_fut = self.validator_client.get_all_basic_nodes();
if self.config.use_extended_topology {
let all_nodes = self
.validator_client
.get_all_basic_nodes()
.await
// Join rewarded_set_fut and all_nodes_fut concurrently
let (rewarded_set, all_nodes) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
.inspect_err(|err| error!("failed to get network nodes: {err}"))
.ok()?;
@@ -103,26 +100,28 @@ impl NymApiTopologyProvider {
"there are {} nodes on the network (before filtering)",
all_nodes.len()
);
let mut topology = NymTopology::new_empty(rewarded_set);
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
n.performance.round_to_integer() >= self.config.min_node_performance()
}));
topology
} else {
// if we're not using extended topology, we're only getting active set mixnodes and gateways
let mixnodes = self
let mixnodes_fut = self
.validator_client
.get_all_basic_active_mixing_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network mixnodes: {err}"))
.ok()?;
.get_all_basic_active_mixing_assigned_nodes();
// TODO: we really should be getting ACTIVE gateways only
let gateways = self
.validator_client
.get_all_basic_entry_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network gateways: {err}"))
.ok()?;
let gateways_fut = self.validator_client.get_all_basic_entry_assigned_nodes();
let (rewarded_set, mixnodes, gateways) =
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
.inspect_err(|err| {
error!("failed to get network nodes: {err}");
})
.ok()?;
debug!(
"there are {} mixnodes and {} gateways in total (before performance filtering)",
@@ -130,12 +129,15 @@ impl NymApiTopologyProvider {
gateways.len()
);
let mut topology = NymTopology::new_empty(rewarded_set);
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_mixnode_performance
}));
topology.add_additional_nodes(gateways.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_gateway_performance
}));
topology
};
if !topology.is_minimally_routable() {
+1 -1
View File
@@ -4,6 +4,6 @@
pub use nym_client_core_config_types::disk_persistence;
pub use nym_client_core_config_types::old::{
old_config_v1_1_13, old_config_v1_1_20, old_config_v1_1_20_2, old_config_v1_1_30,
old_config_v1_1_33,
old_config_v1_1_33, old_config_v1_1_54,
};
pub use nym_client_core_config_types::*;
+12 -1
View File
@@ -17,15 +17,26 @@ nym-crypto = { path = "../../crypto", optional = true, default-features = false
nym-sphinx = { path = "../../nymsphinx" }
nym-task = { path = "../../task" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
workspace = true
features = ["fs"]
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
optional = true
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
path = "../../../sqlx-pool-guard"
[build-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
[features]
fs-surb-storage = ["sqlx", "nym-crypto", "nym-crypto/hashing"]
@@ -1,8 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::io;
use std::path::PathBuf;
use std::{io, path::PathBuf};
use thiserror::Error;
#[derive(Debug, Error)]
@@ -30,7 +29,6 @@ pub enum StorageError {
#[error("failed to perform sqlx migration: {source}")]
MigrationError {
#[source]
#[from]
source: sqlx::migrate::MigrateError,
},
@@ -43,7 +41,6 @@ pub enum StorageError {
#[error("failed to run the SQL query: {source}")]
QueryError {
#[source]
#[from]
source: sqlx::error::Error,
},
@@ -15,9 +15,11 @@ use sqlx::{
};
use std::path::Path;
use sqlx_pool_guard::SqlitePoolGuard;
#[derive(Debug, Clone)]
pub struct StorageManager {
pub connection_pool: sqlx::SqlitePool,
connection_pool: SqlitePoolGuard,
}
// all SQL goes here
@@ -37,7 +39,7 @@ impl StorageManager {
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path)
.filename(&database_path)
.create_if_missing(fresh)
.disable_statement_logging();
@@ -49,11 +51,15 @@ impl StorageManager {
}
};
let connection_pool =
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
.run(&connection_pool)
.run(&*connection_pool)
.await
{
error!("Failed to initialize SQLx database: {err}");
connection_pool.close().await;
return Err(err.into());
}
@@ -61,38 +67,43 @@ impl StorageManager {
Ok(StorageManager { connection_pool })
}
/// Close connection pool waiting for all connections to be closed.
pub async fn close_pool(&self) {
self.connection_pool.close().await;
}
#[allow(dead_code)]
pub async fn status_table_exists(&self) -> Result<bool, sqlx::Error> {
sqlx::query!("SELECT name FROM sqlite_master WHERE type='table' AND name='status'")
.fetch_optional(&self.connection_pool)
.fetch_optional(&*self.connection_pool)
.await
.map(|r| r.is_some())
}
pub async fn create_status_table(&self) -> Result<(), sqlx::Error> {
sqlx::query!("INSERT INTO status(flush_in_progress, previous_flush_timestamp, client_in_use) VALUES (0, 0, 1)")
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_flush_status(&self) -> Result<bool, sqlx::Error> {
sqlx::query!("SELECT flush_in_progress FROM status;")
.fetch_one(&self.connection_pool)
.fetch_one(&*self.connection_pool)
.await
.map(|r| r.flush_in_progress > 0)
}
pub async fn set_previous_flush_timestamp(&self, timestamp: i64) -> Result<(), sqlx::Error> {
sqlx::query!("UPDATE status SET previous_flush_timestamp = ?", timestamp)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_previous_flush_timestamp(&self) -> Result<i64, sqlx::Error> {
sqlx::query!("SELECT previous_flush_timestamp FROM status;")
.fetch_one(&self.connection_pool)
.fetch_one(&*self.connection_pool)
.await
.map(|r| r.previous_flush_timestamp)
}
@@ -100,14 +111,14 @@ impl StorageManager {
pub async fn set_flush_status(&self, in_progress: bool) -> Result<(), sqlx::Error> {
let in_progress_int = i64::from(in_progress);
sqlx::query!("UPDATE status SET flush_in_progress = ?", in_progress_int)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_client_in_use_status(&self) -> Result<bool, sqlx::Error> {
sqlx::query!("SELECT client_in_use FROM status;")
.fetch_one(&self.connection_pool)
.fetch_one(&*self.connection_pool)
.await
.map(|r| r.client_in_use > 0)
}
@@ -115,21 +126,21 @@ impl StorageManager {
pub async fn set_client_in_use_status(&self, in_use: bool) -> Result<(), sqlx::Error> {
let in_use_int = i64::from(in_use);
sqlx::query!("UPDATE status SET client_in_use = ?", in_use_int)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn delete_all_tags(&self) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM sender_tag;")
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_tags(&self) -> Result<Vec<StoredSenderTag>, sqlx::Error> {
sqlx::query_as!(StoredSenderTag, "SELECT * FROM sender_tag;",)
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
@@ -141,21 +152,21 @@ impl StorageManager {
stored_tag.recipient,
stored_tag.tag
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn delete_all_reply_keys(&self) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM reply_key;")
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_reply_keys(&self) -> Result<Vec<StoredReplyKey>, sqlx::Error> {
sqlx::query_as!(StoredReplyKey, "SELECT * FROM reply_key;",)
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
@@ -171,14 +182,14 @@ impl StorageManager {
stored_reply_key.reply_key,
stored_reply_key.sent_at_timestamp
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
pub async fn get_surb_senders(&self) -> Result<Vec<StoredSurbSender>, sqlx::Error> {
sqlx::query_as!(StoredSurbSender, "SELECT * FROM reply_surb_sender;",)
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
@@ -193,7 +204,7 @@ impl StorageManager {
stored_surb_sender.tag,
stored_surb_sender.last_sent_timestamp
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?
.last_insert_rowid();
Ok(id)
@@ -208,17 +219,17 @@ impl StorageManager {
"SELECT * FROM reply_surb WHERE reply_surb_sender_id = ?",
sender_id
)
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
pub async fn delete_all_reply_surb_data(&self) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM reply_surb;")
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
sqlx::query!("DELETE FROM reply_surb_sender;")
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
@@ -235,7 +246,7 @@ impl StorageManager {
stored_reply_surb.reply_surb_sender_id,
stored_reply_surb.reply_surb
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -249,7 +260,7 @@ impl StorageManager {
SELECT min_reply_surb_threshold as "min_reply_surb_threshold: u32", max_reply_surb_threshold as "max_reply_surb_threshold: u32" FROM reply_surb_storage_metadata;
"#,
)
.fetch_one(&self.connection_pool)
.fetch_one(&*self.connection_pool)
.await
}
@@ -263,7 +274,7 @@ impl StorageManager {
"#,
metadata.min_reply_surb_threshold,
metadata.max_reply_surb_threshold,
).execute(&self.connection_pool).await?;
).execute(&*self.connection_pool).await?;
Ok(())
}
}
@@ -1,18 +1,21 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::backend::fs_backend::manager::StorageManager;
use crate::backend::fs_backend::models::{
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag, StoredSurbSender,
};
use crate::surb_storage::ReceivedReplySurbs;
use crate::{
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
backend::fs_backend::{
manager::StorageManager,
models::{
ReplySurbStorageMetadata, StoredReplyKey, StoredReplySurb, StoredSenderTag,
StoredSurbSender,
},
},
surb_storage::ReceivedReplySurbs,
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys,
UsedSenderTags,
};
use async_trait::async_trait;
use log::{debug, error, info, warn};
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use std::fs;
use std::path::{Path, PathBuf};
use time::OffsetDateTime;
@@ -41,15 +44,17 @@ impl Backend {
}
let manager = StorageManager::init(database_path, true).await?;
manager.create_status_table().await?;
let backend = Backend {
temporary_old_path: None,
database_path: owned_path,
manager,
};
Ok(backend)
match manager.create_status_table().await {
Ok(()) => Ok(Backend {
temporary_old_path: None,
database_path: owned_path,
manager,
}),
Err(err) => {
manager.close_pool().await;
Err(err.into())
}
}
}
pub async fn try_load<P: AsRef<Path>>(
@@ -64,7 +69,28 @@ impl Backend {
}
let manager = StorageManager::init(database_path, false).await?;
match Self::try_load_inner(&manager, fresh_sender_tags).await {
Ok(()) => Ok(Backend {
temporary_old_path: None,
database_path: owned_path,
manager,
}),
Err(e) => {
manager.close_pool().await;
Err(e)
}
}
}
/// Gracefully close sqlite connection pool and drop backend.
pub async fn shutdown(self) {
self.manager.close_pool().await
}
async fn try_load_inner(
manager: &StorageManager,
fresh_sender_tags: bool,
) -> Result<(), StorageError> {
// the database flush wasn't fully finished and thus the data is in inconsistent state
// (we don't really know what's properly saved or what's not)
if manager.get_flush_status().await? {
@@ -126,20 +152,11 @@ impl Backend {
manager.delete_all_tags().await?;
}
Ok(Backend {
temporary_old_path: None,
database_path: owned_path,
// manager: StorageManagerState::Storage(manager),
manager,
})
}
async fn close_pool(&mut self) {
self.manager.connection_pool.close().await;
Ok(())
}
async fn rotate(&mut self) -> Result<(), StorageError> {
self.close_pool().await;
self.manager.close_pool().await;
let new_extension = if let Some(existing_extension) =
self.database_path.extension().and_then(|ext| ext.to_str())
@@ -152,7 +169,8 @@ impl Backend {
let mut temp_old = self.database_path.clone();
temp_old.set_extension(new_extension);
fs::rename(&self.database_path, &temp_old)
tokio::fs::rename(&self.database_path, &temp_old)
.await
.map_err(|err| StorageError::DatabaseRenameError { source: err })?;
self.manager = StorageManager::init(&self.database_path, true).await?;
self.manager.create_status_table().await?;
@@ -161,9 +179,10 @@ impl Backend {
Ok(())
}
fn remove_old(&mut self) -> Result<(), StorageError> {
async fn remove_old(&mut self) -> Result<(), StorageError> {
if let Some(old_path) = self.temporary_old_path.take() {
fs::remove_file(old_path)
tokio::fs::remove_file(old_path)
.await
.map_err(|err| StorageError::DatabaseOldFileRemoveError { source: err })
} else {
warn!("the old database file doesn't seem to exist!");
@@ -335,7 +354,7 @@ impl ReplyStorageBackend for Backend {
self.dump_reply_surb_storage_metadata(surbs_ref).await?;
self.dump_reply_surbs(surbs_ref).await?;
self.remove_old()?;
self.remove_old().await?;
self.end_storage_flush().await
}
@@ -10,7 +10,7 @@ use futures::channel::oneshot;
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
use nym_task::TaskClient;
use si_scale::helpers::bibytes2;
use std::os::raw::c_int as RawFd;
@@ -188,6 +188,34 @@ impl PartiallyDelegatedRouter {
}
}
}
ServerResponse::EncryptedResponse { ciphertext, nonce } => {
match SensitiveServerResponse::decrypt(
&ciphertext,
&nonce,
self.shared_key.as_ref(),
) {
Ok(response) => match response {
SensitiveServerResponse::ForgetMeAck {} => {
info!("received forget me acknowledgement");
}
SensitiveServerResponse::RememberMeAck {} => {
info!("received remember me acknowledgement");
}
SensitiveServerResponse::KeyUpgradeAck {} => {
warn!(
"received illegal key upgrade acknowledgement in an authenticated client"
);
}
_ => {
warn!("received unknown SensitiveServerResponse");
}
},
Err(e) => {
error!("failed to handle encrypted response: {e}");
}
}
Ok(())
}
other => {
let name = other.name();
warn!("received illegal message of type '{name}' in an authenticated client");
@@ -345,25 +345,47 @@ impl<C, S> Client<C, S> {
#[derive(Clone)]
pub struct NymApiClient {
pub use_bincode: bool,
pub nym_api: nym_api::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
// we could re-implement the communication with the REST API on port 1317
}
impl From<nym_api::Client> for NymApiClient {
fn from(nym_api: nym_api::Client) -> Self {
NymApiClient {
use_bincode: false,
nym_api,
}
}
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient { nym_api }
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout));
NymApiClient { nym_api }
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[must_use]
pub fn with_bincode(mut self, use_bincode: bool) -> Self {
self.use_bincode = use_bincode;
self
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
@@ -373,7 +395,10 @@ impl NymApiClient {
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
NymApiClient { nym_api }
NymApiClient {
use_bincode: false,
nym_api,
}
}
pub fn api_url(&self) -> &Url {
@@ -410,7 +435,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_entry_assigned_nodes(false, Some(page), None)
.get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -436,7 +461,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_active_mixing_assigned_nodes(false, Some(page), None)
.get_basic_active_mixing_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -462,7 +487,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_mixing_capable_nodes(false, Some(page), None)
.get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -485,7 +510,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_nodes(false, Some(page), None)
.get_basic_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -318,6 +318,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -333,7 +334,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -355,6 +360,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -370,7 +376,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -392,6 +402,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -407,7 +418,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -427,6 +442,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -442,7 +458,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
+9 -2
View File
@@ -20,6 +20,8 @@ nym-credentials = { path = "../credentials" }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" }
nym-ecash-time = { path = "../ecash-time" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx-pool-guard]
path = "../../sqlx-pool-guard"
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
workspace = true
@@ -31,8 +33,13 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
[build-dependencies]
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
"macros",
"migrate",
] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
[features]
persistent-storage = ["bincode", "serde"]
persistent-storage = ["bincode", "serde"]
@@ -7,10 +7,11 @@ use crate::models::{
};
use nym_ecash_time::Date;
use sqlx::{Executor, Sqlite, Transaction};
use sqlx_pool_guard::SqlitePoolGuard;
#[derive(Clone)]
pub struct SqliteEcashTicketbookManager {
connection_pool: sqlx::SqlitePool,
connection_pool: SqlitePoolGuard,
}
impl SqliteEcashTicketbookManager {
@@ -19,7 +20,7 @@ impl SqliteEcashTicketbookManager {
/// # Arguments
///
/// * `connection_pool`: database connection pool to use.
pub fn new(connection_pool: sqlx::SqlitePool) -> Self {
pub fn new(connection_pool: SqlitePoolGuard) -> Self {
SqliteEcashTicketbookManager { connection_pool }
}
@@ -33,7 +34,7 @@ impl SqliteEcashTicketbookManager {
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
deadline
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -60,7 +61,7 @@ impl SqliteEcashTicketbookManager {
data,
expiration_date,
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
@@ -90,7 +91,7 @@ impl SqliteEcashTicketbookManager {
epoch_id,
total_tickets,
used_tickets,
).execute(&self.connection_pool).await?;
).execute(&*self.connection_pool).await?;
Ok(())
}
@@ -106,7 +107,7 @@ impl SqliteEcashTicketbookManager {
"#,
)
.bind(data)
.fetch_optional(&self.connection_pool)
.fetch_optional(&*self.connection_pool)
.await?
.is_some();
@@ -122,7 +123,7 @@ impl SqliteEcashTicketbookManager {
FROM ecash_ticketbook
"#,
)
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
@@ -144,7 +145,7 @@ impl SqliteEcashTicketbookManager {
ticketbook_id,
expected_current_total_spent
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?
.rows_affected();
Ok(affected > 0)
@@ -154,7 +155,7 @@ impl SqliteEcashTicketbookManager {
&self,
) -> Result<Vec<StoredPendingTicketbook>, sqlx::Error> {
sqlx::query_as("SELECT * FROM pending_issuance")
.fetch_all(&self.connection_pool)
.fetch_all(&*self.connection_pool)
.await
}
@@ -166,7 +167,7 @@ impl SqliteEcashTicketbookManager {
"DELETE FROM pending_issuance WHERE deposit_id = ?",
pending_id
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -183,7 +184,7 @@ impl SqliteEcashTicketbookManager {
"#,
epoch_id
)
.fetch_optional(&self.connection_pool)
.fetch_optional(&*self.connection_pool)
.await
}
@@ -209,7 +210,7 @@ impl SqliteEcashTicketbookManager {
serialisation_revision,
epoch_id
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -226,7 +227,7 @@ impl SqliteEcashTicketbookManager {
"#,
epoch_id
)
.fetch_optional(&self.connection_pool)
.fetch_optional(&*self.connection_pool)
.await
}
@@ -252,7 +253,7 @@ impl SqliteEcashTicketbookManager {
serialisation_revision,
epoch_id,
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -270,7 +271,7 @@ impl SqliteEcashTicketbookManager {
"#,
expiration_date
)
.fetch_optional(&self.connection_pool)
.fetch_optional(&*self.connection_pool)
.await
}
@@ -299,7 +300,7 @@ impl SqliteEcashTicketbookManager {
serialisation_revision,
expiration_date
)
.execute(&self.connection_pool)
.execute(&*self.connection_pool)
.await?;
Ok(())
}
@@ -37,6 +37,7 @@ use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
ConnectOptions,
};
use sqlx_pool_guard::SqlitePoolGuard;
use std::path::Path;
use zeroize::Zeroizing;
@@ -62,7 +63,7 @@ impl PersistentStorage {
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(SqliteSynchronous::Normal)
.auto_vacuum(SqliteAutoVacuum::Incremental)
.filename(database_path)
.filename(&database_path)
.create_if_missing(true)
.disable_statement_logging();
@@ -74,13 +75,17 @@ impl PersistentStorage {
}
};
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
let connection_pool =
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await {
error!("Failed to perform migration on the SQLx database: {err}");
connection_pool.close().await;
return Err(err.into());
}
Ok(PersistentStorage {
storage_manager: SqliteEcashTicketbookManager::new(connection_pool.clone()),
storage_manager: SqliteEcashTicketbookManager::new(connection_pool),
})
}
}
+1
View File
@@ -28,6 +28,7 @@ nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
nym-pemstore = { path = "../pemstore" }
nym-sphinx = { path = "../nymsphinx" }
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
nym-statistics-common = { path = "../statistics" }
nym-task = { path = "../task" }
nym-credentials = { path = "../credentials" }
@@ -11,6 +11,7 @@ use crate::{
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::ed25519;
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use tungstenite::Message;
@@ -29,6 +30,9 @@ pub enum ClientRequest {
client: bool,
stats: bool,
},
RememberMe {
session_type: SessionType,
},
}
impl ClientRequest {
@@ -12,6 +12,7 @@ use tungstenite::Message;
pub enum SensitiveServerResponse {
KeyUpgradeAck {},
ForgetMeAck {},
RememberMeAck {},
}
impl SensitiveServerResponse {
-1
View File
@@ -22,7 +22,6 @@ thiserror = { workspace = true }
tracing = { workspace = true }
nym-sphinx = { path = "../nymsphinx" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
nym-statistics-common = { path = "../statistics" }
@@ -0,0 +1,7 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE sessions_active
ADD COLUMN remember INTEGER NOT NULL default 0;
+12 -1
View File
@@ -3,8 +3,9 @@
use error::StatsStorageError;
use models::StoredFinishedSession;
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::types::SessionType;
use sessions::SessionManager;
use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
@@ -147,6 +148,16 @@ impl PersistentStatsStorage {
.await?)
}
pub async fn remember_active_session(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StatsStorageError> {
Ok(self
.session_manager
.remember_active_session(client_address.as_base58_string())
.await?)
}
pub async fn update_active_session_type(
&self,
client_address: DestinationAddressBytes,
+4 -18
View File
@@ -1,12 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
use nym_statistics_common::types::SessionType;
use sqlx::prelude::FromRow;
use time::OffsetDateTime;
pub use nym_credentials_interface::TicketType;
#[derive(FromRow)]
pub struct StoredFinishedSession {
duration_ms: i64,
@@ -22,25 +21,11 @@ impl From<StoredFinishedSession> for FinishedSession {
}
}
pub trait ToSessionType {
fn to_session_type(&self) -> SessionType;
}
impl ToSessionType for TicketType {
fn to_session_type(&self) -> SessionType {
match self {
TicketType::V1MixnetEntry => SessionType::Mixnet,
TicketType::V1MixnetExit => SessionType::Mixnet,
TicketType::V1WireguardEntry => SessionType::Vpn,
TicketType::V1WireguardExit => SessionType::Vpn,
}
}
}
#[derive(FromRow)]
pub(crate) struct StoredActiveSession {
start_time: OffsetDateTime,
typ: String,
remember: u8,
}
impl From<StoredActiveSession> for ActiveSession {
@@ -48,6 +33,7 @@ impl From<StoredActiveSession> for ActiveSession {
ActiveSession {
start: value.start_time,
typ: SessionType::from_string(&value.typ),
remember: value.remember != 0,
}
}
}
+18 -6
View File
@@ -107,7 +107,7 @@ impl SessionManager {
typ: String,
) -> Result<()> {
sqlx::query!(
"INSERT INTO sessions_active (client_address, start_time, typ) VALUES (?, ?, ?)",
"INSERT INTO sessions_active (client_address, start_time, typ, remember) VALUES (?, ?, ?, 0)",
client_address_b58,
start_time,
typ
@@ -117,6 +117,16 @@ impl SessionManager {
Ok(())
}
pub(crate) async fn remember_active_session(&self, client_address_b58: String) -> Result<()> {
sqlx::query!(
"UPDATE sessions_active SET remember = 1 WHERE client_address = ?",
client_address_b58,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn update_active_session_type(
&self,
client_address_b58: String,
@@ -136,14 +146,16 @@ impl SessionManager {
&self,
client_address_b58: String,
) -> Result<Option<StoredActiveSession>> {
sqlx::query_as("SELECT start_time, typ FROM sessions_active WHERE client_address = ?")
.bind(client_address_b58)
.fetch_optional(&self.connection_pool)
.await
sqlx::query_as(
"SELECT start_time, typ, remember FROM sessions_active WHERE client_address = ?",
)
.bind(client_address_b58)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn get_all_active_sessions(&self) -> Result<Vec<StoredActiveSession>> {
sqlx::query_as("SELECT start_time, typ FROM sessions_active")
sqlx::query_as("SELECT start_time, typ, remember FROM sessions_active")
.fetch_all(&self.connection_pool)
.await
}
+4 -2
View File
@@ -12,7 +12,8 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip"] }
bincode = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd"] }
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
@@ -26,7 +27,7 @@ bytes = { workspace = true }
encoding_rs = { workspace = true }
mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
@@ -39,3 +40,4 @@ features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
+131 -38
View File
@@ -136,19 +136,23 @@
//! ```
#![warn(missing_docs)]
pub use reqwest::{IntoUrl, StatusCode};
use async_trait::async_trait;
use reqwest::header::HeaderValue;
use reqwest::{RequestBuilder, Response, StatusCode};
use reqwest::{RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::{instrument, warn};
use tracing::{debug, instrument, warn};
use url::Url;
use bytes::Bytes;
use http::header::CONTENT_TYPE;
use http::HeaderMap;
pub use reqwest::IntoUrl;
use mime::Mime;
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
@@ -210,17 +214,36 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")]
ResponseDecodeFailure {
source: serde_json::Error,
content: String,
},
#[error("failed to decode response body: {message} from {content}")]
ResponseDecodeFailure { message: String, content: String },
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
}
impl HttpClientError {
/// Returns true if the error is a timeout.
pub fn is_timeout(&self) -> bool {
match self {
HttpClientError::ReqwestClientError { source } => source.is_timeout(),
#[cfg(target_arch = "wasm32")]
HttpClientError::RequestTimeout => true,
_ => false,
}
}
/// Returns the HTTP status code if available.
pub fn status_code(&self) -> Option<StatusCode> {
match self {
HttpClientError::RequestFailure { status } => Some(*status),
HttpClientError::EmptyResponse { status } => Some(*status),
HttpClientError::EndpointFailure { status, .. } => Some(*status),
_ => None,
}
}
}
/// A `ClientBuilder` can be used to create a [`Client`] with custom configuration applied consistently
/// and state tracked across subsequent requests.
pub struct ClientBuilder {
@@ -265,14 +288,18 @@ impl ClientBuilder {
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
// Note: I believe the manual enable calls for the compression methods are extra
// as the various compression features for `reqwest` crate should be enabled
// just by including the feature which:
// `"Enable[s] auto decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
// I am going to leave these here anyways so that removing a decompression method
// from the features list will throw an error if it is not also removed here.
reqwest::ClientBuilder::new()
.gzip(true)
.deflate(true)
.brotli(true)
.zstd(true)
};
ClientBuilder {
@@ -519,6 +546,7 @@ pub trait ApiClientCore {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ApiClientCore for Client {
#[instrument(level = "debug", skip_all, fields(path=?path))]
fn create_request<B, K, V>(
&self,
method: reqwest::Method,
@@ -535,11 +563,6 @@ impl ApiClientCore for Client {
let mut request = self.reqwest_client.request(method.clone(), url);
// Indicate that compressed responses are preferred, but if not supported other encodings are fine.
// TODO: Down the road we can be more selective about adding this, but it's inclusion here guarantees
// that we use compression when available.
request = request.header(reqwest::header::ACCEPT_ENCODING, "gzip;q=1.0, *;q=0.5");
if let Some(body) = json_body {
request = request.json(body);
}
@@ -697,12 +720,30 @@ pub trait ApiClient: ApiClientCore {
/// 'get' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T`.
#[instrument(level = "debug", skip_all)]
#[instrument(level = "debug", skip_all, fields(path=?path))]
// TODO: deprecate in favour of get_response that works based on mime type in the response
async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.get_response(path, params).await
}
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T` based on the content type header
async fn get_response<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
@@ -877,14 +918,10 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url
}
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
use encoding_rs::{Encoding, UTF_8};
use mime::Mime;
let content_type = headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let content_type = try_get_mime_type(headers);
let encoding_name = content_type
.as_ref()
@@ -897,7 +934,7 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
text.into_owned()
}
/// Attempt to parse a json object from an HTTP response
/// Attempt to parse a response object from an HTTP response
#[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where
@@ -919,16 +956,7 @@ where
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text())
let full = res.bytes().await?;
match serde_json::from_slice(&full) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&full, headers);
Err(HttpClientError::ResponseDecodeFailure {
source: err,
content,
})
}
}
decode_raw_response(&headers, full)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
@@ -947,6 +975,71 @@ where
}
}
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
match serde_json::from_slice(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
use bincode::Options;
let opts = nym_http_api_common::make_bincode_serializer();
match opts.deserialize(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
// if content type header is missing, fallback to our old default, json
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
debug!("attempting to parse response as {mime}");
// unfortunately we can't use stronger typing for subtype as "bincode" is not a defined mime type
match (mime.type_(), mime.subtype().as_str()) {
(mime::APPLICATION, "json") => decode_as_json(headers, content),
(mime::APPLICATION, "bincode") => decode_as_bincode(headers, content),
(_, _) => {
debug!("unrecognised mime type {mime}. falling back to json decoding...");
decode_as_json(headers, content)
}
}
}
fn try_get_mime_type(headers: &HeaderMap) -> Option<Mime> {
headers
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok())
}
#[cfg(test)]
mod tests {
use super::*;
+35 -11
View File
@@ -11,20 +11,44 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum-client-ip.workspace = true
axum.workspace = true
bytes = { workspace = true }
colored.workspace = true
futures = { workspace = true }
mime = { workspace = true }
axum = { workspace = true, optional = true }
axum-client-ip = { workspace = true, optional = true }
bincode = { workspace = true }
bytes = { workspace = true, optional = true }
colored = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
mime = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_yaml = { workspace = true }
subtle.workspace = true
tower = { workspace = true }
serde_yaml = { workspace = true, optional = true }
subtle = { workspace = true, optional = true }
time = { workspace = true, optional = true, features = ["macros"] }
tower = { workspace = true, optional = true }
tracing.workspace = true
utoipa = { workspace = true, optional = true }
zeroize = { workspace = true }
zeroize = { workspace = true, optional = true }
[features]
default = []
output = [
"axum",
"bytes",
"mime",
"serde_yaml",
"time",
"time/formatting"
]
middleware = [
"axum",
"axum-client-ip",
"colored",
"futures",
"subtle",
"tower",
"zeroize"
]
utoipa = ["dep:utoipa"]
[lints]
workspace = true
+13 -85
View File
@@ -1,92 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use bytes::{BufMut, BytesMut};
use serde::{Deserialize, Serialize};
#[cfg(feature = "middleware")]
pub mod middleware;
#[derive(Debug, Clone)]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
}
#[cfg(feature = "output")]
pub mod response;
impl<T> IntoResponse for FormattedResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
}
}
}
// don't break existing imports
#[cfg(feature = "output")]
pub use response::*;
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum Output {
#[default]
Json,
Yaml,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml(data)),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Yaml<T>(pub T);
impl<T> From<T> for Yaml<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0) {
Ok(()) => (
[(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
)],
buf.into_inner().freeze(),
)
.into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response(),
}
}
// be explicit about those values because bincode uses different defaults in different places
pub fn make_bincode_serializer() -> impl ::bincode::Options {
use ::bincode::Options;
::bincode::DefaultOptions::new()
.with_little_endian()
.with_varint_encoding()
}
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Bincode<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Bincode<T> {
fn from(response: T) -> Self {
Bincode(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/bincode"),
))
}
}
impl<T> Bincode<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Bincode<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
use bincode::Options;
let mut buf = BytesMut::with_capacity(128).writer();
match crate::make_bincode_serializer().serialize_into(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
use utoipa::gen::serde_json;
// don't use axum's Json directly as we need to be able to define custom headers
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Json<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Json<T> {
fn from(response: T) -> Self {
Json(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
))
}
}
impl<T> Json<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_json::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::format_description::BorrowedFormatItem;
use time::macros::{format_description, offset};
use time::OffsetDateTime;
pub mod bincode;
pub mod json;
pub mod yaml;
pub use json::Json;
pub use yaml::Yaml;
pub use bincode::Bincode;
#[derive(Debug, Clone, Default)]
pub(crate) struct ResponseWrapper<T> {
data: T,
headers: HeaderMap,
}
impl<T> ResponseWrapper<T> {
pub(crate) fn new(response: T) -> ResponseWrapper<T> {
ResponseWrapper {
data: response,
headers: Default::default(),
}
}
#[must_use]
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.headers.insert(name, value.into());
self
}
}
#[derive(Debug, Clone)]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
Bincode(Bincode<T>),
}
impl<T> FormattedResponse<T> {
pub fn into_inner(self) -> T {
match self {
FormattedResponse::Json(inner) => inner.0.data,
FormattedResponse::Yaml(inner) => inner.0.data,
FormattedResponse::Bincode(inner) => inner.0.data,
}
}
#[must_use]
pub fn with_header(
self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> FormattedResponse<T> {
match self {
FormattedResponse::Json(inner) => {
FormattedResponse::Json(inner.with_header(name, value))
}
FormattedResponse::Yaml(inner) => {
FormattedResponse::Yaml(inner.with_header(name, value))
}
FormattedResponse::Bincode(inner) => {
FormattedResponse::Bincode(inner.with_header(name, value))
}
}
}
/// Set the `expires` header on the response to the provided expiration.
/// Internally it will perform conversions to make sure the value is set in GMT offset,
/// e.g. `Expires: Wed, 21 Oct 2015 07:28:00 GMT`
#[must_use]
pub fn with_expires_header(self, expiration: OffsetDateTime) -> FormattedResponse<T> {
// as per RFC-7234 (section 5.3) EXPIRES header has to use value formatted
// as defined in RFC-7231 (section 7.1.1.1)
// (preferred format (IMF-fixdate) uses RFC-5322 (section 3.3)
let formatted = format_rfc5352(expiration);
// SAFETY: our formatted datetime doesn't contain forbidden characters
#[allow(clippy::unwrap_used)]
self.with_header(header::EXPIRES, HeaderValue::try_from(formatted).unwrap())
}
/// Work similarly to `with_expires_header`, but rather than setting explicit expiration value,
/// it adds the provided time delta to the current time instead.
#[must_use]
pub fn with_expires_header_delta(self, expires_in: Duration) -> FormattedResponse<T> {
self.with_expires_header(OffsetDateTime::now_utc() + expires_in)
}
}
impl<T> IntoResponse for FormattedResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
FormattedResponse::Bincode(bincode_response) => bincode_response.into_response(),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum Output {
#[default]
Json,
Yaml,
Bincode,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json::from(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml::from(data)),
Output::Bincode => FormattedResponse::Bincode(Bincode::from(data)),
}
}
}
pub(crate) fn error_response<E: ToString>(err: E) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response()
}
// SAFETY: this hardcoded datetime formatter is valid
#[allow(clippy::unwrap_used)]
fn format_rfc5352(datetime: OffsetDateTime) -> String {
// the time must be using GMT (UTC) offset
let normalised = datetime.to_offset(offset!(UTC));
normalised.format(&rfc5322()).unwrap()
}
// NOTE: this function is purposely not made public as it cannot guarantee caller
// has correctly ensured their date is using correct GMT offset
fn rfc5322() -> &'static [BorrowedFormatItem<'static>] {
// D, d M Y H:i:s T
format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
)
}
#[cfg(test)]
mod tests {
use crate::response::format_rfc5352;
use time::macros::datetime;
#[test]
fn rfc5322_formatting() {
let utc_date = datetime!(2021-08-23 12:13:14 UTC);
let non_utc_date = datetime!(2021-08-23 12:13:14 -1);
assert_eq!("Mon, 23 Aug 2021 12:13:14 GMT", format_rfc5352(utc_date));
assert_eq!(
"Mon, 23 Aug 2021 13:13:14 GMT",
format_rfc5352(non_utc_date)
);
}
}
@@ -0,0 +1,47 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Yaml<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Yaml<T> {
fn from(response: T) -> Self {
Yaml(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
))
}
}
impl<T> Yaml<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
@@ -179,7 +179,7 @@ fn _aggregate_indices_signatures<B>(
validate_shares: bool,
) -> Result<Vec<CoinIndexSignature>>
where
B: Borrow<PartialCoinIndexSignature>,
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
{
// Check if all indices are unique
if signatures_shares
@@ -271,7 +271,7 @@ pub fn aggregate_indices_signatures<B>(
signatures_shares: &[CoinIndexSignatureShare<B>],
) -> Result<Vec<CoinIndexSignature>>
where
B: Borrow<PartialCoinIndexSignature>,
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
{
_aggregate_indices_signatures(params, vk, signatures_shares, true)
}
@@ -38,7 +38,7 @@ impl From<AnnotatedExpirationDateSignature> for ExpirationDateSignature {
pub struct ExpirationDateSignatureShare<B = PartialExpirationDateSignature>
where
B: Borrow<PartialExpirationDateSignature>,
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
pub index: SignerIndex,
pub key: VerificationKeyAuth,
@@ -205,7 +205,7 @@ fn _aggregate_expiration_signatures<B>(
validate_shares: bool,
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<ExpirationDateSignature>,
B: Borrow<ExpirationDateSignature> + Send + Sync,
{
// Check if all indices are unique
if signatures_shares
@@ -304,7 +304,7 @@ pub fn aggregate_expiration_signatures<B>(
signatures_shares: &[ExpirationDateSignatureShare<B>],
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<PartialExpirationDateSignature>,
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
_aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
}
@@ -13,6 +13,7 @@ use std::str::FromStr;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
@@ -1,7 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{Config, Socks5, Socks5Debug};
use super::old_config_v1_1_54::ConfigV1_1_54;
use super::{Socks5, Socks5Debug};
pub use nym_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as BaseClientConfigV1_1_33;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
@@ -23,9 +24,9 @@ pub struct ConfigV1_1_33 {
pub socks5: Socks5V1_1_33,
}
impl From<ConfigV1_1_33> for Config {
impl From<ConfigV1_1_33> for ConfigV1_1_54 {
fn from(value: ConfigV1_1_33) -> Self {
Config {
ConfigV1_1_54 {
base: value.base.into(),
socks5: value.socks5.into(),
}
@@ -0,0 +1,23 @@
use super::Config;
pub use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseClientConfigV1_1_54;
use serde::{Deserialize, Serialize};
use super::Socks5;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_54 {
#[serde(flatten)]
pub base: BaseClientConfigV1_1_54,
pub socks5: Socks5,
}
impl From<ConfigV1_1_54> for Config {
fn from(value: ConfigV1_1_54) -> Self {
Config {
base: value.base.into(),
socks5: value.socks5,
}
}
}
+1
View File
@@ -20,6 +20,7 @@ thiserror = { workspace = true }
time = { workspace = true }
tokio = { workspace = true }
si-scale = { workspace = true }
strum = { workspace = true }
nym-crypto = { path = "../crypto" }
nym-sphinx = { path = "../nymsphinx" }
+9 -19
View File
@@ -1,10 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::TicketType;
use nym_sphinx::DestinationAddressBytes;
use time::OffsetDateTime;
use crate::types::SessionType;
/// Channel for receiving incoming Stats events
pub type GatewayStatsReceiver = tokio::sync::mpsc::UnboundedReceiver<GatewayStatsEvent>;
@@ -51,15 +52,9 @@ pub enum GatewaySessionEvent {
/// Address of the remote client opening the connection
client: DestinationAddressBytes,
},
/// A new ecash ticket has been added / requested
EcashTicket {
/// Type of ecash ticket that has been created as part of the session
ticket_type: TicketType,
/// Address of the remote client opening the connection
client: DestinationAddressBytes,
},
SessionDelete {
/// Address of the remote client opening the connection
/// An active session should be given a type and remembered
SessionRemember {
session_type: SessionType,
client: DestinationAddressBytes,
},
}
@@ -81,18 +76,13 @@ impl GatewaySessionEvent {
}
}
/// A new ecash ticket has been added / requested
pub fn new_ecash_ticket(
pub fn new_session_remember(
session_type: SessionType,
client: DestinationAddressBytes,
ticket_type: TicketType,
) -> GatewaySessionEvent {
GatewaySessionEvent::EcashTicket {
ticket_type,
GatewaySessionEvent::SessionRemember {
session_type,
client,
}
}
pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent {
GatewaySessionEvent::SessionDelete { client }
}
}
+2
View File
@@ -22,6 +22,8 @@ pub mod error;
pub mod gateways;
/// Statistics reporting abstractions and implementations.
pub mod report;
/// Statistics related types.
pub mod types;
const CLIENT_ID_PREFIX: &str = "client_stats_id";
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(
PartialEq,
Copy,
Clone,
strum::Display,
strum::EnumString,
Serialize,
Deserialize,
Default,
Debug,
)]
pub enum SessionType {
Vpn,
Mixnet,
Wasm,
Native,
Socks5,
#[default]
Unknown,
}
impl SessionType {
pub fn from_string<S: AsRef<str>>(s: S) -> Self {
s.as_ref().parse().unwrap_or_default()
}
}
+27 -1
View File
@@ -5,9 +5,10 @@
#![allow(clippy::drop_non_drop)]
use crate::error::WasmCoreError;
use nym_client_core::config::ForgetMe;
use nym_client_core::config::{ForgetMe, RememberMe};
use nym_config::helpers::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use wasm_bindgen::prelude::*;
@@ -113,6 +114,8 @@ pub struct DebugWasm {
pub stats_reporting: StatsReportingWasm,
pub forget_me: ForgetMeWasm,
pub remember_me: RememberMeWasm,
}
impl Default for DebugWasm {
@@ -132,6 +135,7 @@ impl From<DebugWasm> for ConfigDebug {
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: debug.forget_me.into(),
remember_me: debug.remember_me.into(),
}
}
}
@@ -147,6 +151,7 @@ impl From<ConfigDebug> for DebugWasm {
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: ForgetMeWasm::from(debug.forget_me),
remember_me: RememberMeWasm::from(debug.remember_me),
}
}
}
@@ -597,6 +602,27 @@ impl From<ForgetMe> for ForgetMeWasm {
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct RememberMeWasm {
pub stats: bool,
}
impl From<RememberMeWasm> for RememberMe {
fn from(value: RememberMeWasm) -> Self {
RememberMe::new(value.stats, SessionType::Wasm)
}
}
impl From<RememberMe> for RememberMeWasm {
fn from(value: RememberMe) -> Self {
Self {
stats: value.stats(),
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
+21 -1
View File
@@ -9,7 +9,7 @@
use super::{
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, ForgetMeWasm, GatewayConnectionWasm,
ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
RememberMeWasm, ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
};
use crate::config::ConfigDebug;
use serde::{Deserialize, Serialize};
@@ -50,6 +50,9 @@ pub struct DebugWasmOverride {
#[tsify(optional)]
pub forget_me: Option<ForgetMeWasmOverride>,
#[tsify(optional)]
pub remember_me: Option<RememberMeWasmOverride>,
}
impl From<DebugWasmOverride> for DebugWasm {
@@ -63,6 +66,7 @@ impl From<DebugWasmOverride> for DebugWasm {
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
stats_reporting: value.stats_reporting.map(Into::into).unwrap_or_default(),
forget_me: value.forget_me.map(Into::into).unwrap_or_default(),
remember_me: value.remember_me.map(Into::into).unwrap_or_default(),
}
}
}
@@ -457,6 +461,22 @@ impl From<ForgetMeWasmOverride> for ForgetMeWasm {
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct RememberMeWasmOverride {
#[tsify(optional)]
pub stats: Option<bool>,
}
impl From<RememberMeWasmOverride> for RememberMeWasm {
fn from(value: RememberMeWasmOverride) -> Self {
RememberMeWasm {
stats: value.stats.unwrap_or_default(),
}
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
+119 -25
View File
@@ -1,45 +1,139 @@
## Build with Docker & Docker Compose
## Nym Validator
Currently you can build and run locally in a Docker containers the following components of the Nym Privacy Platform:
This contains the configuration needed to run a Nym validator using Docker
* One genesis validator
* Any number of secondary validators
* A contract uploader, that uploads the contract built from the local sources
* The web wallet application, accessible on port 3000
* The block explorer application, accessible on port 3080
* The network explorer application, accessible on port 3040, for registered users
> **SECURITY**: This runs the validator as the root user inside the container for simplicity and development purposes. This setup should NOT be used in any other fashion.
### Running
### Initial Setup
The following commands need to be run from the root of the Nym git project.
To build the entire dockerized environment, run the following command:
Before building and running the validator, you'll need to create the required directories:
```
PERSONAL_TOKEN=[network explorer token] docker-compose build
# Create required directories
mkdir -p data/validator data/addresses
chmod -R 777 data
```
or build each service separately, as changes are made to their relevant source code.
**Note** network-explorer build time is currently very high, so building it more than once is not advisable.
### Running on Apple (Mx) Macs with Colima
To start the dockerized environment, run the following command:
When running on Apple Silicon Macs, we are using Colima with x86_64 emulation to properly run the validator:
1. Install Colima
```
METEOR_SETTINGS=$(cat docker/block_explorer/settings.json) docker-compose up -d --scale=secondary_validator=3
brew install colima
```
**Note**: The `secondary_validator=3` can take any other number as value, depending on the desired setup.
The web wallet interface will become available at `localhost:3000`.
The mnemonic needed to connect to the admin user, which also has a pre-added number of tokens, can be obtained by running:
2. Set up a Colima VM with x86_64 architecture and Rosetta support:
```
docker logs nym_mnemonic_echo_1
# Create a Colima VM with x86_64 architecture
colima start nym-validator --arch x86_64 --cpu 4 --memory 8 --disk 20 --vm-type=vz --vz-rosetta --mount-type=virtiofs
```
To stop the dockerized environment, run:
3. Build and start the validator:
```
# Make sure you're using the Colima context
docker context use colima-nym-validator
# Build the validator
docker-compose build validator
# Start the validator
docker-compose up -d validator
```
### Standard Operation (Intel/AMD x86_64 Systems)
For standard x86_64 systems:
```
# Build the validator
docker-compose build validator
# Start the validator
docker-compose up -d validator
```
The genesis validator will be initialized with the network configuration defined in the `docker-compose.yml` file.
### Managing the Validator
To check the validator logs:
```
docker logs -f validator
```
To get the admin mnemonic:
```
docker exec validator cat /root/output/node_admin_mnemonic
```
To stop the validator:
```
docker-compose down
```
### Terminating and Cleaning Up Validator Data
If you need to completely terminate your validator and remove all associated data:
```
# Stop the containers
docker-compose down
# Remove the volumes
docker-compose down -v
# Delete the data directories
rm -rf data
# If you want to start fresh, recreate the directories
mkdir -p data/validator/config data/addresses
chmod -R 777 data
```
This will completely remove all blockchain state, keys, and configuration, allowing you to start with a clean validator instance
### Using nym-cli for Smart Contract Operations
The nym-cli utility can be used to manage and execute WASM smart contracts. You can access the CLI from within the validator container:
```
docker exec -it validator ./nym-cli cosmwasm --help
```
#### Available Commands:
- **upload**: Upload a smart contract WASM blob
- **init**: Init a WASM smart contract
- **generate-init-message**: Generate an instantiate message
- **migrate**: Migrate a WASM smart contract
- **execute**: Execute a WASM smart contract method
- **raw-contract-state**: Obtain raw contract state of a cosmwasm smart contract
#### Example Usage:
To upload a contract:
```
docker exec -it validator ./nym-cli cosmwasm upload \
--mnemonic $(cat /root/output/node_admin_mnemonic) \
--wasm-file /path/to/contract.wasm
```
To initialize a contract:
```
docker exec -it validator ./nym-cli cosmwasm init \
--mnemonic $(cat /root/output/node_admin_mnemonic) \
--code-id <CODE_ID> \
--init-msg '{"key": "value"}'
```
For more detailed options, use the help command:
```
docker exec -it validator ./nym-cli cosmwasm <COMMAND> --help
```
-69
View File
@@ -1,69 +0,0 @@
{
"public":{
"chainName": "Nym",
"chainId": "nymnet",
"slashingWindow": 10000,
"uptimeWindow": 250,
"initialPageSize": 30,
"secp256k1": false,
"bech32PrefixAccAddr": "punk",
"bech32PrefixAccPub": "punkpub",
"bech32PrefixValAddr": "punkvaloper",
"bech32PrefixValPub": "punkvaloperpub",
"bech32PrefixConsAddr": "punkvalcons",
"bech32PrefixConsPub": "punkvalconspub",
"bondDenom": "punk",
"powerReduction": 1000000,
"coins": [
{
"denom": "upunk",
"displayName": "PUNK",
"fraction": 1000000
},
{
"denom": "punk",
"displayName": "PUNK",
"fraction": 1000000
}
],
"ledger":{
"coinType": 118,
"appName": "punk",
"appVersion": "2.16.0",
"gasPrice": 0.025
},
"modules": {
"bank": true,
"supply": true,
"minting": false,
"gov": true,
"distribution": false
},
"coingeckoId": "punk",
"networks": "https://gist.githubusercontent.com/kwunyeung/8be4598c77c61e497dfc7220a678b3ee/raw/bd-networks.json",
"banners": false
},
"remote":{
"rpc":"http://genesis_validator:26657",
"api":"http://genesis_validator:1317"
},
"debug": {
"startTimer": true
},
"params":{
"startHeight": 0,
"defaultBlockTime": 5000,
"validatorUpdateWindow": 300,
"blockInterval": 15000,
"transactionsInterval": 18000,
"keybaseFetchingInterval": 18000000,
"consensusInterval": 1000,
"statusInterval":7500,
"signingInfoInterval": 1800000,
"proposalInterval": 5000,
"missedBlocksInterval": 60000,
"delegationInterval": 900000
}
}
-4
View File
@@ -1,4 +0,0 @@
FROM node:16
COPY ./setup.sh /setup.sh
CMD /setup.sh
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
git clone https://github.com/nymtech/nym.git
mkdir /explorer-copy
cp -r nym/explorer/* /explorer-copy
cd /explorer-copy
npm install
#baseUrl
sed -i 's/https:\/\/testnet-milhon-explorer.nymtech.net\//http:\/\/localhost:3080/' ./src/api/constants.ts
#master validator
sed -i 's/https:\/\/testnet-milhon-validator1.nymtech.net/http:\/\/localhost:26657/' ./src/api/constants.ts
#big dipper url
sed -i 's/https:\/\/testnet-milhon-blocks.nymtech.net\//http:\/\/localhost:3080/' ./src/api/constants.ts
nohup npm run start
-4
View File
@@ -1,4 +0,0 @@
FROM rust
RUN rustup target add wasm32-unknown-unknown
CMD cd nym/contracts/mixnet && RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
-3
View File
@@ -1,3 +0,0 @@
FROM alpine
COPY ./entrypoint.sh /entrypoint.sh
CMD /entrypoint.sh
-16
View File
@@ -1,16 +0,0 @@
#!/bin/sh
# Wait for the mnemonic(s) to be generated
while ! [ -s /genesis_volume/genesis_mnemonic ]; do
sleep 1
done
while ! [ -s /genesis_volume/secondary_mnemonic ]; do
sleep 1
done
echo "This is the current genesis mnemonic:"
cat /genesis_volume/genesis_mnemonic
echo "This is the current secondary mnemonic:"
cat /genesis_volume/secondary_mnemonic
-4
View File
@@ -1,4 +0,0 @@
FROM node
RUN apt update && apt install -y netcat
COPY entrypoint.sh /entrypoint.sh
CMD /entrypoint.sh
-21
View File
@@ -1,21 +0,0 @@
#!/bin/sh
cd /nym/clients/validator
yarn install
yarn build
cd /nym/docker/typescript_client/upload_contract
npm install
# Wait until the mnemonic is created
while ! [ -s /genesis_volume/genesis_mnemonic ]; do
sleep 1
done
# Wait until the validator opens its port
while ! nc -z genesis_validator 26657; do
sleep 1
done
chmod 777 /contract_volume
npx ts-node upload-wasm.ts
File diff suppressed because it is too large Load Diff
@@ -1,25 +0,0 @@
{
"name": "nym-driver-example",
"version": "1.0.0",
"main": "./dist/index.js",
"author": "Dave Hrycyszyn",
"license": "MIT",
"description": "The simplest cosmjs typescript client to work as an example driver program",
"repository": "https://github.com/nymtech/nym",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
"prettier": "^2.8.7",
"typescript": "^4.1.3"
},
"dependencies": {
"@types/node": "^14.14.22",
"nodemon": "^2.0.20",
"@nymproject/nym-validator-client" : "0.18.0",
"save-dev": "0.0.1-security",
"tasktimer": "^3.0.0",
"ts-node": "^9.1.1"
}
}
@@ -1,43 +0,0 @@
// npx ts-node upload-wasm.ts
import ValidatorClient from "@nymproject/nym-validator-client";
import * as fs from 'fs';
async function newClient(): Promise<ValidatorClient> {
let contract = "fakeContractAddress"; // we don't have one yet
let mnemonic = fs.readFileSync("/genesis_volume/genesis_mnemonic", "utf8").slice(0, -1);
let admin = ValidatorClient.connect(contract, mnemonic, "http://genesis_validator:26657", process.env["BECH32_PREFIX"]);
return admin;
}
async function main() {
let admin = await newClient();
console.log(`admin address: ${admin.address}`);
// check that we have actually connected to an account, query it to test
let balance = await admin.getBalance(admin.address);
console.log(`balance of admin account is: ${balance.amount}${balance.denom}`);
let wasm = fs.readFileSync("/nym/contracts/mixnet/target/wasm32-unknown-unknown/release/mixnet_contracts.wasm");
console.log("wasm loaded");
// dave can upload (note: nobody else can)
const uploadResult = await admin.upload(admin.address, wasm, undefined, "mixnet contract");//.then((uploadResult) => console.log("Upload from dave succeeded, codeId is: " + uploadResult.codeId)).catch((err) => console.log(err));
//todo
//add vesting contract?
// Instantiate the copy of the option contract
const { codeId } = uploadResult;
console.log("code id is", codeId)
const initMsg = {};
const options = { memo: "v0.1.0", transferAmount: [{ denom: "u" + process.env["BECH32_PREFIX"], amount: "1000000" }], admin: admin.address }
let instantiateResult = await admin.instantiate(admin.address, codeId, initMsg, "mixnet contract", options);
let contractAddress = instantiateResult.contractAddress;
console.log(`mixnet contract ${contractAddress} instantiated successfully`)
fs.writeFileSync("/contract_volume/contract_address", contractAddress);
}
main();
+25
View File
@@ -0,0 +1,25 @@
CONFIGURED=true
NETWORK_NAME=nymtestnetwork
RUST_LOG=info
RUST_BACKTRACE=1
BECH32_PREFIX=n
MIX_DENOM=unym
MIX_DENOM_DISPLAY=nym
STAKE_DENOM=unyx
STAKE_DENOM_DISPLAY=nyx
DENOMS_EXPONENT=6
MIXNET_CONTRACT_ADDRESS=
VESTING_CONTRACT_ADDRESS=
GROUP_CONTRACT_ADDRESS=
ECASH_CONTRACT_ADDRESS=
MULTISIG_CONTRACT_ADDRESS=
COCONUT_DKG_CONTRACT_ADDRESS=
REWARDING_VALIDATOR_ADDRESS=
NYXD="https://localhost:26656"
NYM_API="https://insertvalues.io"
NYXD_WS="wss://insertvalues.io"
+15
View File
@@ -0,0 +1,15 @@
# data
data/
data/validator/
data/addresses/
secrets/
# docker
.docker/
# access
*_mnemonic
*.key
genesis_mnemonic
+41 -11
View File
@@ -1,12 +1,42 @@
FROM golang:buster as go_builder
ARG BECH32_PREFIX
ARG WASMD_VERSION
RUN apt update && apt install -y git build-essential
COPY setup.sh .
RUN ./setup.sh
FROM ubuntu:24.04
FROM ubuntu:20.04
COPY --from=go_builder /go/wasmd/build/nymd /root/nymd
COPY --from=go_builder /go/wasmd/build/libwasmvm*.so /root
COPY init_and_start.sh .
ENTRYPOINT ["./init_and_start.sh"]
ARG WASMD_VERSION
ARG NYM_CLI_GIT_TAG
ARG RETAIN_BLOCKS
RUN apt-get update && apt-get install -y \
ca-certificates \
jq \
curl \
wget \
vim \
openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /root
RUN wget -O nym-cli "https://github.com/nymtech/nym/releases/download/nym-binaries-v${NYM_CLI_GIT_TAG:-2025.8-tourist}/nym-cli" && \
chmod 755 nym-cli || echo "nym-cli not available"
RUN wget "https://github.com/nymtech/nyxd/releases/download/${WASMD_VERSION:-v0.54.3}/nyxd-ubuntu-22.04.tar.gz" && \
tar -zxvf nyxd-ubuntu-22.04.tar.gz && \
mv libwasmvm.x86_64.so /lib/x86_64-linux-gnu/ && \
chmod 755 nyxd && \
rm nyxd-ubuntu-22.04.tar.gz
RUN mkdir -p "/root/.nyxd/config" "/root/output"
RUN chmod 755 "/root/.nyxd" "/root/.nyxd/config" "/root/output"
VOLUME /root/.nyxd
VOLUME /root/output
RUN touch .env
COPY start.sh .
RUN chmod 755 *.sh
ENV RETAIN_BLOCKS=${RETAIN_BLOCKS}
ENTRYPOINT ["./start.sh"]
CMD ["genesis"]
+30
View File
@@ -0,0 +1,30 @@
services:
validator:
build:
context: ./
args:
NYM_CLI_GIT_TAG: "2025.8-tourist"
WASMD_VERSION: "v0.54.3"
image: validator:latest
container_name: validator
ports:
- "127.0.0.1:26657:26657"
- "127.0.0.1:26656:26656"
- "127.0.0.1:1317:1317"
restart: unless-stopped
deploy:
resources:
limits:
cpus: '2'
memory: 4G
volumes:
- ./data/addresses:/root/output
- ./data/validator:/root/.nyxd
environment:
BECH32_PREFIX: "n"
DENOM: "nym"
STAKE_DENOM: "nyx"
WASMD_VERSION: "v0.54.3"
CHAIN_ID: "nymtestnetwork"
NYM_CLI_GIT_TAG: "2025.8-tourist"
RETAIN_BLOCKS: "no"
-77
View File
@@ -1,77 +0,0 @@
#!/bin/sh
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root
PASSPHRASE=passphrase
cd /root
if [ "$1" = "genesis" ]; then
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
./nyxd init nymnet --chain-id nymnet 2> /dev/null
# staking/governance token is hardcoded in config, change this
sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" /root/.nyxd/config/genesis.json
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${DENOM}"'"/' /root/.nyxd/config/app.toml
sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml
# create accounts
yes "${PASSPHRASE}" | ./nyxd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/mnemonic
yes "${PASSPHRASE}" | ./nyxd keys add secondary 2>&1 >/dev/null | tail -n 1 > /root/.nyxd/secondary_mnemonic
cp /root/.nyxd/mnemonic /genesis_volume/genesis_mnemonic
cp /root/.nyxd/secondary_mnemonic /genesis_volume/secondary_mnemonic
# add genesis accounts with some initial tokens
GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show node_admin -a)
SECONDARY_ADDRESS=$(yes "${PASSPHRASE}" | ./nyxd keys show secondary -a)
yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
yes "${PASSPHRASE}" | ./nyxd add-genesis-account "${SECONDARY_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
yes "${PASSPHRASE}" | ./nyxd gentx node_admin 1000000000u"${STAKE_DENOM}" --chain-id nymnet 2> /dev/null
./nyxd collect-gentxs 2> /dev/null
./nyxd validate-genesis > /dev/null
cp /root/.nyxd/config/genesis.json /genesis_volume/genesis.json
else
echo "Validator already initialized, starting with the existing configuration."
echo "If you want to re-init the validator, destroy the existing container"
fi
./nyxd start
elif [ "$1" = "secondary" ]; then
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
./nyxd init nymnet --chain-id nym-secondary 2> /dev/null
# Wait until the genesis node writes the genesis.json to the shared volume
while ! [ -s /genesis_volume/genesis.json ]; do
sleep 1
done
# wait for the actual validator to start up
sleep 5
cp /genesis_volume/genesis.json /root/.nyxd/config/genesis.json
GENESIS_PEER=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'"' -f 4)
GENESIS_IP=$(cat /root/.nyxd/config/genesis.json | grep '"memo"' | cut -d'@' -f2 | cut -d: -f1)
sed -i 's/persistent_peers = ""/persistent_peers = "'"${GENESIS_PEER}"'"/' /root/.nyxd/config/config.toml
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nyxd/config/app.toml
sed -i '0,/enable = false/s//enable = true/g' /root/.nyxd/config/app.toml
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nyxd/config/config.toml
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nyxd/config/config.toml
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nyxd/config/config.toml
# import mnemonic generated by the genesis validator (have a local copy for ease of use)
cp /genesis_volume/secondary_mnemonic /root/.nyxd/mnemonic
{ cat /root/.nyxd/mnemonic; echo "${PASSPHRASE}"; echo "${PASSPHRASE}"; } | ./nyxd keys add node_admin --recover #> /dev/null
./nyxd validate-genesis > /dev/null
# create validator
# don't even ask about those sleeps...
{ echo "${PASSPHRASE}"; sleep 10; yes; sleep 10; } | ./nyxd tx staking create-validator --amount=10000000u"${STAKE_DENOM}" --fees 100000u"${DENOM}" --pubkey="$(./nyxd tendermint show-validator)" --moniker="secondary" --commission-rate="0.10" --commission-max-rate="0.20" --commission-max-change-rate="0.01" --min-self-delegation="1" --chain-id=nymnet --from=node_admin -b async --node http://"${GENESIS_IP}":26657
else
echo "Validator already initialized, starting with the existing configuration."
echo "If you want to re-init the validator, destroy the existing container"
fi
./nyxd start
else
echo "Wrong command. Usage: ./$0 [genesis/secondary]"
fi
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
set -ue
git clone https://github.com/CosmWasm/wasmd.git
cd wasmd
git checkout "${WASMD_VERSION}"
WASMD_COMMIT_HASH=$(git rev-parse HEAD)
mkdir build
go build \
-o build/nyxd -mod=readonly -tags "netgo,ledger" \
-ldflags "-X github.com/cosmos/cosmos-sdk/version.Name=nymd \
-X github.com/cosmos/cosmos-sdk/version.AppName=nymd \
-X github.com/CosmWasm/wasmd/app.NodeDir=.nymd \
-X github.com/cosmos/cosmos-sdk/version.Version=${WASMD_VERSION} \
-X github.com/cosmos/cosmos-sdk/version.Commit=${WASMD_COMMIT_HASH} \
-X github.com/CosmWasm/wasmd/app.Bech32Prefix=${BECH32_PREFIX} \
-X 'github.com/cosmos/cosmos-sdk/version.BuildTags=netgo,ledger'" \
-trimpath ./cmd/wasmd
find .. -type f -name 'libwasm*.so' -exec cp {} build \;
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH":/root
PASSPHRASE=passphrase
APP_NAME=nyxd
OUTPUT_DIRECTORY="/root/output"
VALIDATOR_DATA_DIRECTORY="/root/.${APP_NAME}"
mkdir -p "${VALIDATOR_DATA_DIRECTORY}/config"
mkdir -p "${OUTPUT_DIRECTORY}"
if [ ! -f "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json" ]; then
# initialise the validator
./${APP_NAME} init "${CHAIN_ID}" --chain-id "${CHAIN_ID}" 2>/dev/null
echo "init chain successful"
sleep 5
echo "checking config files:"
ls -la ${VALIDATOR_DATA_DIRECTORY}/config/
echo "changing params"
sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json"
sed -i 's/minimum-gas-prices = "0stake"/minimum-gas-prices = "0.025u'"${DENOM}"'"/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
sed -i '0,/enable = false/s//enable = true/g' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
if [ "$RETAIN_BLOCKS" = "no" ]; then
# amending to say if min retain blocks should be set yes or no...
sed -i 's/min-retain-blocks = 0/min-retain-blocks = 70000/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
fi
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' "${VALIDATOR_DATA_DIRECTORY}/config/config.toml"
sed -i 's/address = "tcp:\/\/localhost:1317"/address = "tcp:\/\/0.0.0.0:1317"/' "${VALIDATOR_DATA_DIRECTORY}/config/app.toml"
echo "params changed"
echo "adding parent mnemonic account details"
yes "${PASSPHRASE}" | ./${APP_NAME} keys add node_admin 2>&1 >/dev/null | tail -n 1 >${OUTPUT_DIRECTORY}/node_admin_mnemonic
# add genesis accounts with some initial tokens
echo "adding genesis account details"
GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./${APP_NAME} keys show node_admin -a)
yes "${PASSPHRASE}" | ./${APP_NAME} genesis add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
echo "adding gentx time :)"
yes "${PASSPHRASE}" | ./${APP_NAME} genesis gentx node_admin 100000000000u"${STAKE_DENOM}" --chain-id "${CHAIN_ID}" 2>/dev/null
./${APP_NAME} genesis collect-gentxs 2>/dev/null
./${APP_NAME} genesis validate-genesis >/dev/null
# make a copy of the genesis file to the output directory
cp "${VALIDATOR_DATA_DIRECTORY}/config/genesis.json" "${OUTPUT_DIRECTORY}/genesis.json"
fi
./${APP_NAME} start &
sleep 10
sleep infinity
-3
View File
@@ -1,3 +0,0 @@
FROM rust
RUN rustup target add wasm32-unknown-unknown
CMD cd nym/contracts/vesting && RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
@@ -0,0 +1,120 @@
import React, { useState } from 'react'
export default function RewardsCalculator() {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const [c, setC] = useState(0)
const [d, setD] = useState(0)
const [e, setE] = useState(0)
const result =
e !== 0
? `${(
a * b * c * ((1 / 240) + 0.3 * ((d / e) / 240)) * 1 / (1 + 0.3)
).toFixed(4)} NYM`
: '—'
return (
<div
style={{
margin: '1.5em 0',
padding: '1em',
border: '1px solid #9ca3af',
borderRadius: '8px',
maxWidth: '600px',
backgroundColor: '#162B2C',
color: '#9ca3af',
fontSize: '0.95rem',
}}
>
<h3
style={{
marginTop: 0,
marginBottom: '0.75em',
color: '#9ca3af',
fontSize: '1.25rem',
fontWeight: '600',
}}
>
Epoch Reward Calculator
</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr auto',
gap: '0.75em 1em',
alignItems: 'center',
}}
>
<label htmlFor="a">
{' '}
<a
href="https://validator.nymtech.net/api/v1/epoch/reward_params"
target="_blank"
rel="noopener noreferrer"
style={{ color: 'inherit', textDecoration: 'underline' }}
>
Current epoch reward budget
</a>{' '}
(NYM):
</label>
<input
id="a"
type="number"
value={a}
onChange={(e) => setA(Number(e.target.value))}
/>
<label htmlFor="b">Node performance score:</label>
<input
id="b"
type="number"
value={b}
onChange={(e) => setB(Number(e.target.value))}
/>
<label htmlFor="c">Node stake saturation:</label>
<input
id="c"
type="number"
value={c}
onChange={(e) => setC(Number(e.target.value))}
/>
<label htmlFor="d">Node self bond size (NYM):</label>
<input
id="d"
type="number"
value={d}
onChange={(e) => setD(Number(e.target.value))}
/>
<label htmlFor="e">
<a
href="https://validator.nymtech.net/api/v1/epoch/reward_params"
target="_blank"
rel="noopener noreferrer"
style={{ color: 'inherit', textDecoration: 'underline' }}
>
Current stake saturation point
</a>{' '}
(NYM):
</label>
<input
id="e"
type="number"
value={e}
onChange={(e) => setE(Number(e.target.value))}
/>
</div>
<p style={{ marginTop: '1.5em' }}>
<strong>Node epoch rewards (if active):</strong>
<br />
{result}
</p>
</div>
)
}

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