Compare commits

..

134 Commits

Author SHA1 Message Date
Floriane TUERNAL SABOTINOV 8aa5e61ba6 wip 2025-10-27 19:11:13 +01:00
Floriane TUERNAL SABOTINOV 3bda29aed7 featurized InputMessage transformation 2025-10-24 16:50:47 +02:00
Floriane TUERNAL SABOTINOV 69bba68060 wip 2025-10-22 12:14:44 +02:00
Floriane TUERNAL SABOTINOV c2a06298d1 fmt 2025-10-22 09:17:40 +02:00
Floriane TUERNAL SABOTINOV 2831f4ae55 wip 2025-10-22 09:07:07 +02:00
Floriane TUERNAL SABOTINOV 2344a43f62 rebase reconciliation 2025-10-21 15:21:55 +02:00
Floriane TUERNAL SABOTINOV 8427b09ed7 cleanup final 2025-10-21 14:18:57 +02:00
Floriane TUERNAL SABOTINOV ec09c7e807 cleanup and PR ready 2025-10-21 14:18:57 +02:00
Floriane TUERNAL SABOTINOV 6eab87fe05 refactor init tracer and use of tracing_opentelemetry span for async tracing 2025-10-21 14:18:57 +02:00
Floriane TUERNAL SABOTINOV 9c13fe534b featurized otel 2025-10-21 14:18:55 +02:00
Floriane TUERNAL SABOTINOV ad431e8b09 cleanup and add guard to keep the tracer provider alive 2025-10-21 14:16:21 +02:00
Floriane TUERNAL SABOTINOV b292c4a624 continue trace)id propagation into sphinx 2025-10-21 14:16:19 +02:00
Floriane TUERNAL SABOTINOV 9a96be3d41 add trace_id to sphinx packet 2025-10-21 14:13:38 +02:00
Floriane TUERNAL SABOTINOV 8e771c3530 otel adaptation for sphinx instrumentation 2025-10-21 14:09:04 +02:00
Floriane TUERNAL SABOTINOV 47e1e912f5 traceparent injection into headers 2025-10-21 14:09:04 +02:00
Floriane TUERNAL SABOTINOV 4116bb321a add traceparent to http headers 2025-10-21 14:09:02 +02:00
Floriane TUERNAL SABOTINOV 60cde66451 websocket otel trace handling correction 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV be37715552 wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV f05c5d2eae wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV e9cbd0aff5 add instrument to sdk send message 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 36b07b1bcf wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 827591631d debug node 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 682feac75e add manual spans to handle_request functions 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 28058944f2 cleanup 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV ca9b1d656c use otel span to parent tracing span 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 2955e506de add context to message handling 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 254a414c49 change context propagation method to trace_id sharing to go accros async move barrier 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 7d087996b4 wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV c5e66687b6 wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV faaafbeae4 wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 32d7328c7b wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 6ba20e7f54 context extraction 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 74c064135b parent/chil propagation explicitation 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 1fef24a6ba try debug panic when using parent span 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 2ae73b7f31 rm clone because panic 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV b93779850b propagation to parent function 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 651d0bb578 wip 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 4af1835dbd add method to extract trace_id from ContextCarrier 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV f1bf9861cd use attach() instead of creating new span 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 15f56bccb1 forget to enter span 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 26da5da44f verify existence of context 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV b9a2efe0c9 use authenticatev2 and not authenticate 2025-10-21 14:08:12 +02:00
Floriane TUERNAL SABOTINOV 8492c7161c manual propagators for context from client to node 2025-10-21 14:08:10 +02:00
Floriane TUERNAL SABOTINOV 76770fa30c wip 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 9d774860e5 experiment do I preserve trace_id with with method 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 7d7bebacd2 try debug with otel specific tools 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV cfe8c08ca6 print debug trace 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 9d8c328ce1 establishing initial authentication common trace_id 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV c4543c83d5 context propagation helpers 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 1fa615991b Revert "broken: context won't satisfy spawn async move"
This reverts commit 2b11479ad4801e1efa8ab0aca4ca577bd3f195fe.
2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 9f3061a2eb broken: context won't satisfy spawn async move 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 953f930c4b try debug fresh handler context 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV c2b620de92 debug surb_reply client 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV d55fce1917 try debug client gets stuck 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV bda45b405c try keep context accross async call 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV bb9f0540e8 instrumentation of nym-node to nym-gateway workflow 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV e3569ccc16 fixed trace_id distibuted from sdk to gateway 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 5c9d58bad3 fix trace_id propagation from sdk to mix-node 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 6c03ab69fb try debug difference trace_id otel by use async for gateway and attach it to span in surb_reply 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV 89329ab3ee add context propagation 2025-10-21 14:07:01 +02:00
Floriane TUERNAL SABOTINOV fee2ae5964 change surb_reply to test relation with nym-node + basic instrumentation 2025-10-21 14:06:59 +02:00
Floriane TUERNAL SABOTINOV f56edf63b5 fix signoz ingestion 2025-10-21 13:59:59 +02:00
Floriane TUERNAL SABOTINOV dacd35470d configure format for sending logs to signoz 2025-10-21 13:59:59 +02:00
Floriane TUERNAL SABOTINOV acc11e8776 setup otel for surb_reply 2025-10-21 13:59:59 +02:00
Floriane TUERNAL SABOTINOV a11de8eb74 fix otel panic 2025-10-21 13:59:59 +02:00
Floriane TUERNAL SABOTINOV fa603be538 setup otel from run to forward_sphinx_packet 2025-10-21 13:59:59 +02:00
Floriane TUERNAL SABOTINOV 2447520010 revert changes from previous commit 2025-10-21 13:59:59 +02:00
Mark Sinclair 5888102359 wip: sdk surb-reply example add otel tracing 2025-10-21 13:59:59 +02:00
Mark Sinclair 7d7acc2691 wip: tracing in nym-node 2025-10-21 13:59:59 +02:00
Mark Sinclair 20e6243738 add opentelemetry for debugging and testing behind otel feature flag 2025-10-21 13:59:55 +02:00
Jędrzej Stuczyński 88c4e0ce6c bugfix: update stored epoch share when changing announce address (#6131)
* bugfix: update stored epoch share when changing announce address

* chore: remove placeholder legacy mixnode bonding test [mixnet contract]
2025-10-21 10:59:17 +01:00
Tommy Verrall 5a817e1df1 Merge pull request #6126 from nymtech/multiple-fall-back-urls
Changes:

Multiple URL fallback with configurable retries (defaults to 3)
Infallible URL conversion per Andrews feedback (Url::from() instead of parse().ok())
Non-breaking builder pattern for BuilderConfig per Andrej's "too many arguments" feedback
Reverted redundant node filtering per Andrew's clarification that API already filters by supported_roles.entry
2025-10-21 11:27:37 +02:00
Tommy Verrall a07a24db00 Fix CI issues 2025-10-21 11:01:04 +02:00
Tommy Verrall a0cb812eff Allow clippy::enum_variant_names for BuilderConfigError 2025-10-21 10:35:57 +02:00
Tommy Verrall 923c1fa184 Improve error handling
Changes:
- Replace String error with BuilderConfigError enum in BuilderConfigBuilder
- Update tests to use pattern matching instead of string assertions
2025-10-20 16:57:31 +02:00
Tommy Verrall 35ea7e4926 - Add DEFAULT_NYM_API_RETRIES constant (replaces magic number 3)
- Run cargo fmt on all affected packages
- All clippy warnings resolved
2025-10-20 16:51:07 +02:00
Tommy Verrall d1cb9afaf0 not sure what happened but it's fixed 2025-10-20 15:20:24 +02:00
Tommy Verrall 79d4b4b2e3 Merge branch 'develop' into multiple-fall-back-urls 2025-10-20 15:16:36 +02:00
Tommy Verrall 8460b33946 Merge branch 'multiple-fall-back-urls' of https://github.com/nymtech/nym into multiple-fall-back-urls 2025-10-20 15:16:17 +02:00
Tommy Verrall ae6539e07c Merge resolution 2025-10-20 15:14:48 +02:00
Tommy Verrall 18cebdfedc Add accessor methods for Url internals
Add inner_url() and fronts() accessor methods to nym_http_api_client::Url
for VPN client integration
2025-10-20 14:33:57 +02:00
Tommy Verrall c448ec823a Remove tests for removed with_nym_api_client method
These tests were referencing with_nym_api_client() which was removed when
cleaning domain fronting code from this branch
2025-10-20 11:52:04 +02:00
Tommy Verrall a266137278 Add optional builder pattern for BuilderConfig (non-breaking)
Addresses @jstuczyn's feedback about too many arguments by adding
BuilderConfigBuilder as an alternative to the existing new() method.
2025-10-20 11:39:50 +02:00
Tommy Verrall 6f4dfd1dab fix conversion type && make the retry count configurable 2025-10-20 11:15:31 +02:00
Andy Duplain 57719787db Merge pull request #6130 from nymtech/andy/url_fronts
VPN-4262: Update `Url` to return `url` and `front` fields.
2025-10-17 15:44:08 +01:00
Andy Duplain 29a57bf172 VPN-4262: Update Url to return url and front fields.
The VPN client is using the `Url` type alot now and in order to avoid
double URL-parsing we would like the content of the `Url` type exposed.
2025-10-17 15:37:07 +01:00
Tommy Verrall db813b6e3e Revert node filtering changes per Andrew's feedback
Andrew clarified that get_basic_entry_assigned_nodes_v2() already filters by
supported_roles.entry
2025-10-17 15:18:28 +02:00
Tommy Verrall 1be5ba310a Remove domain fronting code to keep gateway changes only
This branch now contains only gateway registration improvements:
- Multiple URL fallback support in gateways_for_init()
- Get all entry-capable nodes for registration
- Performance and code quality improvements
2025-10-17 14:27:31 +02:00
Tommy Verrall 41ff3f7824 Address PR feedback: simplify code and reduce log noise
- Reverted all changes to topology_control/nym_api_provider.rs
- Changed info/warn logs to debug for custom client messages
- Removed unused _rng parameter from gateways_for_init()
- Simplified URL builder to always use new_with_urls()
2025-10-17 14:20:12 +02:00
Tommy Verrall c9d4d62446 Fix clippy warnings: use arrays instead of vec! in tests 2025-10-17 13:30:30 +02:00
Tommy Verrall e839a0d80e Merge develop into multiple-fall-back-urls
Resolved conflicts:
- Added event_tx field to MixnetClientBuilder alongside custom_nym_api_client
- Both features are independent and coexist:
  * custom_nym_api_client: for domain fronting support
  * event_tx: for event handling
- Updated all constructors and methods to properly handle both fields
2025-10-17 13:23:04 +02:00
Tommy Verrall cd61f930bf feat: pass custom HTTP client through SDK stack for domain fronting
- Add with_nym_api_client() to BaseClientBuilder, MixnetClientBuilder, and RegistrationClientBuilderConfig

- Modify nym_api_provider to fetch all nodes then filter by supported_roles.entry (fixes metadata inconsistency)

- Update helpers.rs to build HTTP client with all nym_apis URLs and retries for fallback support

- Fix SDK to use entry_capable_nodes() instead of entry_gateways() for broader gateway selection

This enables domain fronting and URL rotation throughout the entire SDK stack, improving censorship resistance and connection reliability. All changes are backward compatible - custom client is optional.
2025-10-17 08:36:23 +02:00
Bogdan-Ștefan Neacşu 0674f31227 Introduce event backchannel (#6119)
* Introduce even backchannel

* Rust fmt

* Rename Event to MixnetClientEvent

* Use unbounded_send for events

* Remove unused file

* Remove mut borrow

* Event hierarchy and mixnet client intermediary

* Export MixTrafficEvent in sdk
2025-10-16 19:02:36 +03:00
Jędrzej Stuczyński 3e4f563dce Merge pull request #6099 from nymtech/bugfix/incompatibility-fixes
Bugfix/incompatibility fixes
2025-10-16 15:58:43 +01:00
Tommy Verrall edcf2b1204 enable URL rotation and retries for mixnet gateway init 2025-10-16 16:22:57 +02:00
Jędrzej Stuczyński b07fb18113 Merge pull request #6125 from nymtech/merge/release/2025.18-jarlsberg
Merge/release/2025.18 jarlsberg
2025-10-16 14:50:16 +01:00
benedettadavico 017dea4afd update changelog 2025-10-16 14:09:46 +01:00
Jędrzej Stuczyński 5a9ce13beb Bugfix/bloomfilters purge (#6089)
* remove all old bloomfilters upon starting binary

* remove old bloomfilter file upon purging secondary data
2025-10-16 14:09:45 +01:00
benedettadavico 514cf25c68 bump versions 2025-10-16 13:53:06 +01:00
Andrej Mihajlov 49ee0636e4 Merge pull request #6109 from nymtech/am/update-dirs-6
Update dirs to 6.0
2025-10-16 12:59:31 +02:00
Jędrzej Stuczyński bb971ce99c bugfix: nym-credential-proxy query params parsing regression (#6121) 2025-10-16 11:40:12 +01:00
Tommy Verrall 54de369c1e Skip ipv6 metadata endpoint request (#6118)
Co-authored-by: Tommy Verrall <tommy@nymtech.net>
2025-10-16 11:39:53 +01:00
Jędrzej Stuczyński 6d6ce284df bugfix: revert some dep updates introduced in #6043 (#6120) 2025-10-16 11:39:09 +01:00
Andrej Mihajlov 56ad1c6c8e Merge pull request #6115 from nymtech/am/revert-cancel-token
Revert "Propagate cancel token to mixnet client"
2025-10-15 16:54:49 +02:00
Jędrzej Stuczyński 10b4a288c8 chore: restore pending dkg contract state migration (#6116)
since it has not yet been run on mainnet
2025-10-15 14:18:03 +01:00
benedetta davico bbbb9486ce Merge pull request #6117 from nymtech/probe/remove-1mb-file
update to no longer use 1mb files
2025-10-15 15:17:01 +02:00
benedetta davico 16e86e1a07 Update lib.go 2025-10-15 15:15:20 +02:00
Jędrzej Stuczyński ca0c9898f0 bugfix: retrieve and update ticketbook in the same query (#6101)
* bugfix: retrieve and update ticketbook in the same query

* bump up NS version

* Update Cargo.toml

* remove SKIP LOCKED part of the query

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-15 13:53:07 +01:00
Andrej Mihajlov 8b73d4e615 Revert "Propagate cancel token to mixnet client"
This reverts commit 50a259d454.
2025-10-15 14:17:36 +02:00
mfahampshire 6a9a767ab4 DOCS Jarlsberg Release (#6111)
* First pass release notes

* build info
2025-10-15 09:20:03 +00:00
Andrej Mihajlov e03a9fa16f Merge pull request #6105 from nymtech/am/reg-client-mixnet-cancel-token
Propagate cancel token to mixnet client
2025-10-14 13:10:02 +02:00
Andrej Mihajlov a0fbd57d5b Update dirs to 6.0 2025-10-14 11:17:33 +02:00
mfahampshire 9856198356 Patch for operators to open wg metadata port (#6106) 2025-10-13 14:47:43 +00:00
Jędrzej Stuczyński 5c33846e57 bugfix: use custom topology provider for list of init gateways (#6092) 2025-10-13 12:01:51 +01:00
Andrej Mihajlov cfa7635ae1 Propagate cancel token to mixnet client 2025-10-13 12:25:54 +02:00
Jędrzej Stuczyński 5d45544c27 bugfix: include network name in the default gateway probe config path (#6100) 2025-10-13 10:05:54 +01:00
Jędrzej Stuczyński aa6a79cb3e feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet (#6083)
* feat: expose obtaining reference to Mnemonic from DirectSecp256k1HdWallet

* updated getters for stringified mnemonic
2025-10-13 09:22:15 +01:00
Georgio Nicolas b3a940770a Merge pull request #5919 from nymtech/georgio/dkg-fixes
Additional DKG Fixes
2025-10-10 17:47:11 +02:00
Mark Sinclair e980f76a81 ns-api: add descriptions to dVPN gateway responses (#6102)
* ns-api: add descriptions to dVPN gateway responses

* clippy

* fmt

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
2025-10-10 15:40:18 +01:00
import this 9b38fef28f [DOCs/operators] QUIC deployment script & docs (#6098)
* add quic_bridge_deployment.sh

* create a snippet with quick install steps

* add quic deployment to changelog

* add quic deployment to node config page

* add version compatibility callout

* last edits and scraped stats update

* correct name of QUIC snippet

* fix naming

* fix naming

* re-run python-prebuild.sh aka time-now updated

* attempt to fix vercel build the hard way

* rerun npm

* build with pnpm

* restore lock file and rebuild w pnpm

* chore: update pnpm lockfile

* attempt to fix build

* attempt to fix runtime builds

* update ci-docs run OS
2025-10-10 14:38:37 +00:00
Mark Sinclair 43910ca635 Update ci-docs.yml 2025-10-10 15:00:25 +01:00
Mark Sinclair d3ccd7575a NS API: use new probe download filesize and milliseconds field (#6097)
* use milliseconds field

* change score thresholds

* bump to version 4.0.8

* NS API: adjust score categories (#6103)

* testing scores

* test version

* Update Cargo.toml

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-10-10 14:47:36 +01:00
Jędrzej Stuczyński 422f889df7 bugfix: testnet manager 02sql migration (#6096) 2025-10-10 09:38:45 +01:00
Jędrzej Stuczyński c9e96edc35 chore: remove unnecessary closure in 'calculate_score' inside node-status-api 2025-10-09 15:46:15 +01:00
benedetta davico 7768317046 Merge pull request #6095 from nymtech/bugfix/ns-api-download-filesize
ns-api: use download files size from probes instead of parsing filenames
2025-10-08 18:14:00 +02:00
Mark Sinclair 0ebbb1a540 ns-api: use download files size from probes instead of parsing filenames 2025-10-08 17:05:56 +01:00
Jędrzej Stuczyński 827c13b69e moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-08 16:17:43 +01:00
Georgio Nicolas bb06a1b7a8 Another offering for Clippy 2025-09-12 20:34:50 +02:00
Georgio Nicolas e783a5fced Offerings for clippy 2025-09-12 20:28:49 +02:00
Georgio Nicolas 8a24b45b5d Precompute BSGS table 2025-09-12 20:21:57 +02:00
Georgio Nicolas 10e4eba727 Use LazyLock to precompute generators 2025-08-08 19:14:37 +02:00
Georgio Nicolas 8ebf482f36 Fix clippy suggestion 2025-07-29 16:33:25 +02:00
Georgio Nicolas 6940ca427e Fix zeroization 2025-07-29 15:42:23 +02:00
Georgio Nicolas 24f877fda5 replace unsafe static values by function calls 2025-07-29 15:04:11 +02:00
226 changed files with 5605 additions and 11300 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
args: --lib --manifest-path contracts/Cargo.toml
args: --lib --manifest-path contracts/Cargo.toml --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build:
runs-on: arc-ubuntu-22.04
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
defaults:
+44
View File
@@ -4,6 +4,50 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.18-jarlsberg] (2025-10-14)
- ns-api: add descriptions to dVPN gateway responses ([#6102])
- NS API: use new probe download filesize and milliseconds field ([#6097])
- ns-api: use download files size from probes instead of parsing filenames ([#6095])
- ns-api: add new fields for probe output for query_metadata and download file size and duration in ms ([#6091])
- Bugfix/bloomfilters purge ([#6089])
- Hotfix: Update API source in node ping tester script ([#6082])
- Get wireguard keypair as arg instead of reading it from disk ([#6078])
- Feature: Ping probe all nodes /described nodes from a server ([#6074])
- Node Status API: add bridge information to dVPN endpoint ([#6069])
- frontdoor typo fix ([#6067])
- Feature: Node rewards tracker ([#6064])
- [chore] Clippy fix ([#6060])
- Registration Client ([#6059])
- Bugfix: Nym node CLI download nym-node exception ([#6058])
- Feature: Nym node html landing page ([#6053])
- feat: DKG contract method for updating announce address ([#6050])
- feat: NS ticket faucet ([#6047])
- Bridge proto client params in Self-Described ([#6035])
- Node Status API: remove sqlite support ([#6004])
- Benny/ci contract fix ([#5962])
[#6102]: https://github.com/nymtech/nym/pull/6102
[#6097]: https://github.com/nymtech/nym/pull/6097
[#6095]: https://github.com/nymtech/nym/pull/6095
[#6091]: https://github.com/nymtech/nym/pull/6091
[#6089]: https://github.com/nymtech/nym/pull/6089
[#6082]: https://github.com/nymtech/nym/pull/6082
[#6078]: https://github.com/nymtech/nym/pull/6078
[#6074]: https://github.com/nymtech/nym/pull/6074
[#6069]: https://github.com/nymtech/nym/pull/6069
[#6067]: https://github.com/nymtech/nym/pull/6067
[#6064]: https://github.com/nymtech/nym/pull/6064
[#6060]: https://github.com/nymtech/nym/pull/6060
[#6059]: https://github.com/nymtech/nym/pull/6059
[#6058]: https://github.com/nymtech/nym/pull/6058
[#6053]: https://github.com/nymtech/nym/pull/6053
[#6050]: https://github.com/nymtech/nym/pull/6050
[#6047]: https://github.com/nymtech/nym/pull/6047
[#6035]: https://github.com/nymtech/nym/pull/6035
[#6004]: https://github.com/nymtech/nym/pull/6004
[#5962]: https://github.com/nymtech/nym/pull/5962
## [2025.17-isabirra] (2025-09-29)
- Bugfix | Fix the registration handshake ([#6062])
Generated
+505 -718
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -248,7 +248,7 @@ dashmap = "5.5.3"
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
digest = "0.10.7"
dirs = "5.0"
dirs = "6.0"
doc-comment = "0.3"
dotenvy = "0.15.6"
dyn-clone = "1.0.19"
@@ -300,8 +300,11 @@ nix = "0.27.1"
notify = "5.1.0"
okapi = "0.7.0"
once_cell = "1.21.3"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
opentelemetry = "0.30.0"
opentelemetry-otlp = "0.30.0"
opentelemetry-semantic-conventions = "0.30.0"
opentelemetry_sdk = "0.30.0"
opentelemetry-stdout = "0.30.0"
parking_lot = "0.12.3"
pem = "0.8"
petgraph = "0.6.5"
@@ -360,8 +363,10 @@ toml = "0.8.22"
tower = "0.5.2"
tower-http = "0.5.2"
tracing = "0.1.41"
tracing-core = "0.1.33"
tracing-log = "0.2"
tracing-opentelemetry = "0.19.0"
tracing-opentelemetry = "0.31.0"
tracing-serde = "0.2.0"
tracing-subscriber = "0.3.19"
tracing-tree = "0.2.2"
tracing-indicatif = "0.3.9"
+9 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.63"
version = "1.1.64"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -46,7 +46,6 @@ 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",
@@ -71,3 +70,11 @@ nym-client-websocket-requests = { path = "websocket-requests" }
nym-id = { path = "../../common/nym-id" }
[dev-dependencies]
[features]
default = []
otel = [
"nym-client-core/otel",
"nym-bin-common/otel",
"nym-gateway-requests/otel",
]
+1
View File
@@ -60,6 +60,7 @@ impl SocketClient {
let ClientInput {
connection_command_sender,
input_sender,
..
} = client_input;
let ClientOutput {
+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_tracing_logger};
use nym_bin_common::logging::{maybe_print_banner, setup_no_otel_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_tracing_logger();
setup_no_otel_logger().expect("failed to initialize logging");
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
+17 -3
View File
@@ -184,7 +184,14 @@ impl Handler {
});
// the ack control is now responsible for chunking, etc.
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
let input_msg = InputMessage::new_regular(
recipient,
message,
lane,
self.packet_type,
#[cfg(feature = "otel")]
None,
);
if let Err(err) = self.msg_input.send(input_msg).await {
if !self.shutdown_token.is_cancelled() {
error!("Failed to send message to the input buffer: {err}");
@@ -216,8 +223,15 @@ impl Handler {
TransmissionLane::ConnectionId(id)
});
let input_msg =
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
let input_msg = InputMessage::new_anonymous(
recipient,
message,
reply_surbs,
lane,
self.packet_type,
#[cfg(feature = "otel")]
None,
);
if let Err(err) = self.msg_input.send(input_msg).await {
if !self.shutdown_token.is_cancelled() {
error!("Failed to send anonymous message to the input buffer: {err}");
+5 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.63"
version = "1.1.64"
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,7 +27,6 @@ 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",
@@ -54,3 +53,7 @@ nym-validator-client = { path = "../../common/client-libs/validator-client", fea
[features]
default = []
eth = []
otel = [
"nym-socks5-client-core/otel",
"nym-bin-common/otel",
]
+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_tracing_logger};
use nym_bin_common::logging::{maybe_print_banner, setup_no_otel_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_tracing_logger();
setup_no_otel_logger().expect("failed to initialize logging");
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
+24 -14
View File
@@ -8,24 +8,30 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
chrono = { workspace = true, optional = true }
cfg-if = { workspace = true }
clap = { workspace = true, features = ["derive"], optional = true }
clap_complete = { workspace = true, optional = true }
clap_complete_fig = { workspace = true, optional = true }
const-str = { workspace = true }
log = { workspace = true }
opentelemetry = { workspace = true, optional = true }
opentelemetry-otlp = { workspace = true,features=["metrics", "grpc-tonic", "tls",
"tls-webpki-roots"], optional = true }
opentelemetry-semantic-conventions = { workspace = true, features = ["semconv_experimental"], optional = true }
opentelemetry-stdout = { workspace = true, features = ["trace", "metrics"], optional = true }
opentelemetry_sdk = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
## tracing
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
tracing-tree = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "isahc_collector_client"], optional = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-core = { workspace = true }
tracing-opentelemetry = { workspace = true, optional = true }
tracing-serde = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
tracing-tree = { workspace = true }
utoipa = { workspace = true, optional = true }
opentelemetry = { workspace = true, features = ["rt-tokio"], optional = true }
[build-dependencies]
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
@@ -35,13 +41,17 @@ default = []
openapi = ["utoipa"]
output_format = ["serde_json", "dep:clap"]
bin_info_schema = ["schemars"]
basic_tracing = ["dep:tracing", "tracing-subscriber"]
tracing = [
"basic_tracing",
"tracing-tree",
"opentelemetry-jaeger",
tokio-console = ["otel"]
otel = [
"chrono",
"tracing-opentelemetry",
"opentelemetry",
"opentelemetry-otlp",
"opentelemetry-semantic-conventions",
"opentelemetry-stdout",
"opentelemetry_sdk",
"serde_json",
"rand",
]
clap = ["dep:clap", "dep:clap_complete", "dep:clap_complete_fig"]
models = []
+3
View File
@@ -4,6 +4,9 @@
pub mod build_information;
pub mod logging;
#[cfg(feature = "otel")]
pub mod opentelemetry;
#[cfg(feature = "clap")]
pub mod completions;
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "otel")]
use opentelemetry_otlp::ExporterBuildError;
#[derive(thiserror::Error, Debug)]
pub enum TracingError {
#[error("tracing logger already initialised")]
TracingLoggerAlreadyInitialised,
#[error("Logging error: {0}")]
TracingTryInitError(tracing_subscriber::util::TryInitError),
#[cfg(feature = "otel")]
#[error("{0}")]
TracingExporterBuildError(#[from] ExporterBuildError),
#[error("{0}")]
TracingFilterParseError(#[from] tracing_subscriber::filter::ParseError),
}
+39 -47
View File
@@ -1,19 +1,12 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod error;
use error::TracingError;
use serde::{Deserialize, Serialize};
use std::io::IsTerminal;
#[cfg(feature = "tracing")]
pub use opentelemetry;
#[cfg(feature = "tracing")]
pub use opentelemetry_jaeger;
#[cfg(feature = "tracing")]
pub use tracing_opentelemetry;
#[cfg(feature = "tracing")]
pub use tracing_subscriber;
#[cfg(feature = "tracing")]
pub use tracing_tree;
use tracing_subscriber::{filter::Directive, layer::SubscriberExt, util::SubscriberInitExt};
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
@@ -22,7 +15,6 @@ pub struct LoggingSettings {
}
// 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 {
use tracing_subscriber::prelude::*;
@@ -31,7 +23,6 @@ pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
.with(default_tracing_env_filter())
}
#[cfg(feature = "basic_tracing")]
pub fn default_tracing_env_filter() -> tracing_subscriber::filter::EnvFilter {
if ::std::env::var("RUST_LOG").is_ok() {
tracing_subscriber::filter::EnvFilter::from_default_env()
@@ -43,7 +34,6 @@ pub fn default_tracing_env_filter() -> tracing_subscriber::filter::EnvFilter {
}
}
#[cfg(feature = "basic_tracing")]
pub fn default_tracing_fmt_layer<S, W>(
writer: W,
) -> impl tracing_subscriber::Layer<S> + Sync + Send + 'static
@@ -63,45 +53,47 @@ where
.with_target(false)
}
#[cfg(feature = "basic_tracing")]
pub fn setup_tracing_logger() {
use tracing_subscriber::util::SubscriberInitExt;
build_tracing_logger().init()
/// Creates a tracing filter that sets more granular log levels for specific crates.
/// This allows for finer control over logging verbosity.
pub(crate) fn granual_filtered_env() -> Result<tracing_subscriber::filter::EnvFilter, TracingError>
{
fn directive_checked(directive: impl Into<String>) -> Result<Directive, TracingError> {
directive.into().parse().map_err(From::from)
}
let mut filter = default_tracing_env_filter();
// these crates are more granularly filtered
let filter_crates = ["defguard_wireguard_rs"];
for crate_name in filter_crates {
filter = filter.add_directive(directive_checked(format!("{crate_name}=warn"))?);
}
Ok(filter)
}
pub fn setup_no_otel_logger() -> Result<(), TracingError> {
// Only set up if not already initialized
if tracing::dispatcher::has_been_set() {
// It shouldn't be - this is really checking that it is torn down between async command executions
return Err(TracingError::TracingLoggerAlreadyInitialised);
}
let registry = tracing_subscriber::registry()
.with(default_tracing_fmt_layer(std::io::stderr))
.with(granual_filtered_env()?);
registry
.try_init()
.map_err(|e| TracingError::TracingTryInitError(e))?;
Ok(())
}
// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason
#[cfg(feature = "tracing")]
#[macro_export]
macro_rules! setup_tracing {
($service_name: expr) => {
use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt;
use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt;
let registry = nym_bin_common::logging::tracing_subscriber::Registry::default()
.with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env())
.with(
nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4)
.with_targets(true)
.with_bracketed_fields(true),
);
let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline()
.with_endpoint("http://44.199.230.10:14268/api/traces")
.with_service_name($service_name)
.with_isahc()
.with_trace_config(
nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler(
nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased(
0.1,
),
),
)
.install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio)
.expect("Could not init tracer");
let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer);
registry.with(telemetry).init();
setup_no_otel_logger()
};
}
@@ -0,0 +1,47 @@
use opentelemetry::trace::{SpanId, TraceId};
use opentelemetry_sdk::trace::IdGenerator;
use rand::RngCore;
#[derive(Clone, Debug)]
pub struct Compact13BytesIdGenerator;
impl IdGenerator for Compact13BytesIdGenerator {
fn new_trace_id(&self) -> TraceId {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 16];
// Fill the first 13 bytes with random data
rng.fill_bytes(&mut bytes[0..12]);
// Set the last 4 bytes to zero
bytes[12] = 0;
bytes[13] = 0;
bytes[14] = 0;
bytes[15] = 0;
TraceId::from_bytes(bytes)
}
fn new_span_id(&self) -> SpanId {
let mut rng = rand::thread_rng();
let mut bytes = [0u8; 8];
rng.fill_bytes(&mut bytes);
SpanId::from_bytes(bytes)
}
}
pub fn compress_trace_id(trace_id: &TraceId) -> [u8; 12] {
let bytes = trace_id.to_bytes();
let mut compressed = [0u8; 12];
compressed.copy_from_slice(&bytes[0..12]);
compressed
}
pub fn decompress_trace_id(compressed: &[u8; 12]) -> [u8; 16] {
let mut bytes = [0u8; 16];
bytes[0..12].copy_from_slice(compressed);
bytes[12..].copy_from_slice(&[0u8; 4]);
bytes
}
@@ -0,0 +1,155 @@
use opentelemetry::propagation::{Extractor, Injector, TextMapPropagator};
use opentelemetry::trace::{SpanContext, TraceContextExt, TraceId};
use opentelemetry::{Context, TraceFlags};
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::IdGenerator};
use std::collections::HashMap;
use std::fmt::Display;
use tracing::instrument;
use tracing_opentelemetry::OpenTelemetrySpanExt;
/// Make a Carrier for context propagation
pub struct ContextCarrier {
data: HashMap<String, String>,
}
impl ContextCarrier {
pub fn new_empty() -> Self {
ContextCarrier {
data: HashMap::new(),
}
}
pub fn new_with_data(data: HashMap<String, String>) -> Self {
if data.is_empty() {
return ContextCarrier::new_empty();
}
ContextCarrier { data }
}
pub fn new_with_current_context(context: Context) -> Self {
let mut carrier = ContextCarrier::new_empty();
let propagator = TraceContextPropagator::new();
propagator.inject_context(&context, &mut carrier);
carrier
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
self.data.iter()
}
pub fn from_map(data: HashMap<String, String>) -> Self {
ContextCarrier { data }
}
pub fn into_map(self) -> HashMap<String, String> {
self.data
}
pub fn extract_trace_id(&self) -> Option<TraceId> {
self.get("traceparent").and_then(|tp| {
let parts: Vec<&str> = tp.split('-').collect();
if parts.len() == 4 {
TraceId::from_hex(parts[1]).ok()
} else {
None
}
})
}
pub fn extract_trace_id_into_bytes(&self) -> Option<[u8; 16]> {
self.extract_trace_id().map(|id| id.to_bytes())
}
pub fn extract_traceparent(&self) -> Option<String> {
self.get("traceparent").map(|s| s.to_string())
}
}
impl Injector for ContextCarrier {
fn set(&mut self, key: &str, value: String) {
self.data.insert(key.to_string(), value);
}
}
impl Extractor for ContextCarrier {
fn get(&self, key: &str) -> Option<&str> {
self.data.get(key).map(|s| s.as_str())
}
fn keys(&self) -> Vec<&str> {
self.data.keys().map(|k| k.as_str()).collect()
}
}
impl Display for ContextCarrier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.data)
}
}
pub struct ManualContextPropagator {
pub root_span: tracing::Span,
pub trace_id: TraceId,
}
impl ManualContextPropagator {
#[instrument(skip_all, level = "debug")]
pub fn new(name: &str, context: HashMap<String, String>) -> Self {
let carrier = ContextCarrier::new_with_data(context);
let trace_id = match carrier.extract_trace_id() {
Some(id) => id,
None => Context::current().span().span_context().trace_id(),
};
let root_span_builder = new_span_context_with_id(trace_id.clone());
let root_span = tracing::info_span!("trace_root", name = %name, trace_id = %trace_id);
root_span.set_parent(root_span_builder);
ManualContextPropagator {
root_span,
trace_id,
}
}
#[instrument(skip_all, level = "debug")]
pub fn new_from_tid(name: &str, trace_id: TraceId) -> Self {
let root_span_builder = new_span_context_with_id(trace_id.clone());
let root_span = tracing::info_span!("trace_root", name = %name, trace_id = %trace_id);
root_span.set_parent(root_span_builder);
ManualContextPropagator {
root_span,
trace_id,
}
}
pub fn root_span(&self) -> &tracing::Span {
&self.root_span
}
}
#[instrument(skip_all, level = "debug")]
pub fn new_span_context_with_id(trace_id: TraceId) -> Context {
let id_gen = opentelemetry_sdk::trace::RandomIdGenerator::default();
let span_id = id_gen.new_span_id();
let span_context = SpanContext::new(
trace_id,
span_id,
TraceFlags::SAMPLED,
true,
Default::default(),
);
Context::current().with_remote_span_context(span_context)
}
#[instrument(skip_all, level = "debug")]
pub fn extract_trace_id_from_tracing_cx() -> TraceId {
let cx = tracing::Span::current().context();
let binding = cx.span();
let trace_id = binding.span_context().trace_id();
trace_id
}
+186
View File
@@ -0,0 +1,186 @@
pub mod compact_id_generator;
pub mod context;
mod trace_id_format;
use tracing::{Level, info};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::fmt;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use crate::logging::default_tracing_env_filter;
use crate::logging::error::TracingError;
use crate::opentelemetry::compact_id_generator::Compact13BytesIdGenerator;
use opentelemetry::trace::TracerProvider;
use opentelemetry::{KeyValue, global};
use opentelemetry_otlp::tonic_types::metadata::MetadataMap;
use opentelemetry_otlp::tonic_types::transport::ClientTlsConfig;
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
use opentelemetry_sdk::metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::{Resource, trace::Sampler};
use opentelemetry_semantic_conventions::SCHEMA_URL;
use opentelemetry_semantic_conventions::resource::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_VERSION};
use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
use tracing_subscriber::fmt::format::FmtSpan;
pub struct TracerProviderGuard(Option<SdkTracerProvider>);
impl Drop for TracerProviderGuard {
fn drop(&mut self) {
if let Some(tracer_provider) = self.0.take() {
// Ensure all spans are flushed before exit
if let Err(e) = tracer_provider.shutdown() {
eprintln!("Error shutting down tracer provider: {:?}", e);
}
}
}
}
pub(crate) fn granual_filtered_env() -> Result<tracing_subscriber::filter::EnvFilter, TracingError>
{
fn directive_checked(directive: impl Into<String>) -> Result<Directive, TracingError> {
directive.into().parse().map_err(From::from)
}
let mut filter = default_tracing_env_filter();
// these crates are more granularly filtered
let filter_crates = ["defguard_wireguard_rs"];
for crate_name in filter_crates {
filter = filter.add_directive(directive_checked(format!("{crate_name}=warn"))?);
}
Ok(filter)
}
pub fn setup_tracing_logger(service_name: String) -> Result<TracerProviderGuard, TracingError> {
if tracing::dispatcher::has_been_set() {
// It shouldn't be - this is really checking that it is torn down between async command executions
return Err(TracingError::TracingLoggerAlreadyInitialised);
}
// define ingestion points
let endpoint = std::env::var("SIGNOZ_ENDPOINT").expect("SIGNOZ_ENDPOINT not set");
let key = std::env::var("SIGNOZ_INGESTION_KEY").expect("SIGNOZ_INGESTION_KEY not set");
let mut metadata = MetadataMap::new();
metadata.insert(
"signoz-ingestion-key",
key.parse().expect("Could not parse signoz ingestion key"),
);
// Build resources
let resource = build_resource(&service_name);
// Initialize tracer and meter providers
let tracer_provider = init_tracer_provider(&endpoint, metadata.clone(), resource.clone())?;
let meter_provider = init_meter_provider(&endpoint, metadata.clone(), resource.clone())?;
// Bridge tracing and opentelemetry
let tracer = tracer_provider.tracer("otel-subscriber");
let fmt_layer = fmt::layer()
.json()
.with_writer(std::io::stderr)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.with_span_list(false)
.with_current_span(true)
.event_format(trace_id_format::TraceIdFormat);
let registry = tracing_subscriber::registry()
.with(fmt_layer)
.with(granual_filtered_env()?)
.with(tracing_subscriber::filter::LevelFilter::from_level(
Level::INFO,
))
.with(MetricsLayer::new(meter_provider.clone()))
.with(OpenTelemetryLayer::new(tracer));
registry
.try_init()
.map_err(TracingError::TracingTryInitError)?;
global::set_tracer_provider(tracer_provider.clone());
global::set_meter_provider(meter_provider.clone());
info!("Tracing initialized with service name: {}", service_name);
Ok(TracerProviderGuard(Some(tracer_provider)))
}
fn build_resource(service_name: &str) -> Resource {
Resource::builder()
.with_service_name(service_name.to_string())
.with_schema_url(
[
KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")),
KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"),
],
SCHEMA_URL,
)
.build()
}
fn init_tracer_provider(
endpoint: &str,
metadata: MetadataMap,
resource: Resource,
) -> Result<SdkTracerProvider, TracingError> {
let mut exporter_builder = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_metadata(metadata)
.with_endpoint(endpoint);
if endpoint.starts_with("https://") {
exporter_builder =
exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
}
let exporter = exporter_builder.build()?;
let tracer = SdkTracerProvider::builder()
.with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
1.0,
))))
.with_id_generator(Compact13BytesIdGenerator)
.with_resource(resource)
.with_batch_exporter(exporter)
.build();
global::set_tracer_provider(tracer.clone());
Ok(tracer)
}
fn init_meter_provider(
endpoint: &str,
metadata: MetadataMap,
resource: Resource,
) -> Result<SdkMeterProvider, TracingError> {
let mut exporter_builder = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
.with_metadata(metadata)
.with_endpoint(endpoint)
.with_temporality(opentelemetry_sdk::metrics::Temporality::default());
if endpoint.starts_with("https://") {
exporter_builder =
exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
}
let exporter = exporter_builder.build()?;
let reader = PeriodicReader::builder(exporter)
.with_interval(std::time::Duration::from_secs(30))
.build();
let stdout_reader =
PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build();
let meter_provider = MeterProviderBuilder::default()
.with_resource(resource)
.with_reader(reader)
.with_reader(stdout_reader)
.build();
global::set_meter_provider(meter_provider.clone());
Ok(meter_provider)
}
@@ -0,0 +1,88 @@
use chrono::Utc;
use opentelemetry::trace::TraceContextExt;
use opentelemetry::{SpanId, TraceId};
use serde::ser::{SerializeMap, Serializer as _};
use std::io;
use tracing::{Event, Subscriber};
use tracing_opentelemetry::OpenTelemetrySpanExt;
use tracing_serde::AsSerde;
use tracing_serde::fields::AsMap;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::registry::LookupSpan;
pub struct WriteAdaptor<'a> {
fmt_write: &'a mut dyn std::fmt::Write,
}
impl<'a> WriteAdaptor<'a> {
pub fn new(fmt_write: &'a mut dyn std::fmt::Write) -> Self {
Self { fmt_write }
}
}
impl<'a> io::Write for WriteAdaptor<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let s =
std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.fmt_write
.write_str(s)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(s.as_bytes().len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub struct TraceIdFormat;
impl<S, N> FormatEvent<S, N> for TraceIdFormat
where
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn format_event(
&self,
_ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
let meta = event.metadata();
let mut visit = || {
let mut serializer = serde_json::Serializer::new(WriteAdaptor::new(&mut writer));
let mut serializer = serializer.serialize_map(None)?;
serializer.serialize_entry("timestamp", &Utc::now().to_rfc3339())?;
serializer.serialize_entry("level", &meta.level().as_serde())?;
serializer.serialize_entry("fields", &event.field_map())?;
serializer.serialize_entry("target", meta.target())?;
let current_span = tracing::Span::current();
let context = current_span.context();
let span_ref = context.span();
let span_context = span_ref.span_context();
let trace_id = span_context.trace_id();
if trace_id != TraceId::INVALID {
serializer.serialize_entry("trace_id", &trace_id.to_string())?;
let span_id = span_context.span_id();
if span_id != SpanId::INVALID {
serializer.serialize_entry("span_id", &span_id.to_string())?;
}
}
serializer.end()
};
visit().map_err(|_| std::fmt::Error)?;
writeln!(writer)
}
}
+1
View File
@@ -126,6 +126,7 @@ cli = ["clap", "comfy-table"]
fs-credentials-storage = ["nym-credential-storage/persistent-storage"]
fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"]
fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"]
otel = ["nym-sphinx/otel"]
wasm = ["nym-gateway-client/wasm"]
metrics-server = []
@@ -114,13 +114,12 @@ where
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
.await?
};
@@ -173,13 +173,12 @@ where
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::gateways_for_init(
&mut rng,
&core.client.nym_api_urls,
user_agent,
core.debug.topology.minimum_gateway_performance,
core.debug.topology.ignore_ingress_epoch_role,
None,
)
.await?
};
@@ -7,11 +7,12 @@ use super::statistics_control::StatisticsControl;
use crate::client::base_client::storage::helpers::store_client_keys;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
use crate::client::event_control::EventControl;
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
use crate::client::key_manager::persistence::KeyStore;
use crate::client::key_manager::ClientKeys;
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController, MixTrafficEvent};
use crate::client::real_messages_control;
use crate::client::real_messages_control::RealMessagesController;
use crate::client::received_buffer::{
@@ -66,7 +67,7 @@ use std::path::Path;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::mpsc::Sender;
use tracing::*;
use tracing::instrument;
use url::Url;
#[cfg(target_arch = "wasm32")]
@@ -83,10 +84,28 @@ pub mod non_wasm_helpers;
pub mod helpers;
pub mod storage;
#[derive(Clone, Copy, Debug)]
pub enum MixnetClientEvent {
Traffic(MixTrafficEvent),
}
pub type EventReceiver = mpsc::UnboundedReceiver<MixnetClientEvent>;
#[derive(Clone)]
pub struct EventSender(pub mpsc::UnboundedSender<MixnetClientEvent>);
impl EventSender {
pub fn send(&self, event: MixnetClientEvent) {
if let Err(err) = self.0.unbounded_send(event) {
tracing::warn!("Failed to send error event. The caller event reader was closed: {err}");
}
}
}
#[derive(Clone)]
pub struct ClientInput {
pub connection_command_sender: ConnectionCommandSender,
pub input_sender: InputMessageSender,
pub client_request_sender: ClientRequestSender,
}
impl ClientInput {
@@ -103,6 +122,7 @@ pub struct ClientOutput {
}
impl ClientOutput {
#[instrument(name = "ClientOutput::register_receiver", skip_all)]
pub fn register_receiver(
&mut self,
) -> Result<mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>, ClientCoreError> {
@@ -198,6 +218,7 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
shutdown: Option<ShutdownTracker>,
event_tx: Option<EventSender>,
user_agent: Option<UserAgent>,
setup_method: GatewaySetup,
@@ -226,6 +247,7 @@ where
custom_topology_provider: None,
custom_gateway_transceiver: None,
shutdown: None,
event_tx: None,
user_agent: None,
setup_method: GatewaySetup::MustLoad { gateway_id: None },
#[cfg(unix)]
@@ -288,6 +310,12 @@ where
self
}
#[must_use]
pub fn with_event_tx(mut self, event_tx: EventSender) -> Self {
self.event_tx = Some(event_tx);
self
}
#[must_use]
pub fn with_user_agent(mut self, user_agent: UserAgent) -> Self {
self.user_agent = Some(user_agent);
@@ -318,6 +346,18 @@ where
details.client_address()
}
fn start_event_control(
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
shutdown_tracker: &ShutdownTracker,
) {
let event_control = EventControl::new(parent_event_tx, children_event_rx);
shutdown_tracker.try_spawn_named_with_shutdown(
async move { event_control.run().await },
"EventControl",
);
}
// future constantly pumping loop cover traffic at some specified average rate
// the pumped traffic goes to the MixTrafficController
fn start_cover_traffic_stream(
@@ -329,7 +369,7 @@ where
stats_tx: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
info!("Starting loop cover traffic stream...");
tracing::info!("Starting loop cover traffic stream...");
let mut stream = LoopCoverTrafficStream::new(
ack_key,
@@ -346,6 +386,7 @@ where
}
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
fn start_real_traffic_controller(
controller_config: real_messages_control::Config,
key_rotation_config: KeyRotationConfig,
@@ -361,7 +402,7 @@ where
stats_tx: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
info!("Starting real traffic stream...");
tracing::info!("Starting real traffic stream...");
let real_messages_controller = RealMessagesController::new(
controller_config,
@@ -437,6 +478,7 @@ where
// buffer controlling all messages fetched from provider
// required so that other components would be able to use them (say the websocket)
#[instrument(skip_all)]
fn start_received_messages_buffer_controller(
local_encryption_keypair: Arc<x25519::KeyPair>,
query_receiver: ReceivedBufferRequestReceiver,
@@ -446,7 +488,7 @@ where
metrics_reporter: ClientStatsSender,
shutdown_tracker: &ShutdownTracker,
) {
info!("Starting received messages buffer controller...");
tracing::info!("Starting received messages buffer controller...");
let controller = ReceivedMessagesBufferController::<SphinxMessageReceiver>::new(
local_encryption_keypair,
query_receiver,
@@ -469,6 +511,7 @@ where
}
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
async fn start_gateway_client(
config: &Config,
initialisation_result: InitialisationResult,
@@ -557,7 +600,7 @@ where
details_store
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
.await.map_err(|err| {
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
tracing::error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
})?
}
@@ -575,6 +618,7 @@ where
}
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
async fn setup_gateway_transceiver(
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
config: &Config,
@@ -654,7 +698,7 @@ where
if topology_config.disable_refreshing {
// if we're not spawning the refresher, don't cause shutdown immediately
info!("The background topology refresher is not going to be started");
tracing::info!("The background topology refresher is not going to be started");
}
let mut topology_refresher = TopologyRefresher::new(
@@ -664,7 +708,7 @@ where
);
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
tracing::info!("Obtaining initial network topology");
topology_refresher.try_refresh().await;
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
@@ -690,13 +734,13 @@ where
.wait_for_gateway(local_gateway, waiting_timeout)
.await
{
error!(
tracing::error!(
"the gateway did not come back online within the specified timeout: {err}"
);
return Err(err.into());
}
} else {
error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
tracing::error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
return Err(err.into());
}
}
@@ -704,7 +748,7 @@ where
if !topology_config.disable_refreshing {
// don't spawn the refresher if we don't want to be refreshing the topology.
// only use the initial values obtained
info!("Starting topology refresher...");
tracing::info!("Starting topology refresher...");
shutdown_tracker.try_spawn_named_with_shutdown(
async move { topology_refresher.run().await },
"TopologyRefresher",
@@ -721,7 +765,7 @@ where
input_sender: Sender<InputMessage>,
shutdown_tracker: &ShutdownTracker,
) -> ClientStatsSender {
info!("Starting statistics control...");
tracing::info!("Starting statistics control...");
StatisticsControl::create_and_start(
config.debug.stats_reporting,
user_agent
@@ -732,14 +776,21 @@ where
shutdown_tracker,
)
}
#[instrument(skip_all)]
fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown_tracker: &ShutdownTracker,
event_tx: EventSender,
) -> (BatchMixMessageSender, ClientRequestSender) {
info!("Starting mix traffic controller...");
let (mut mix_traffic_controller, mix_tx, client_tx) =
MixTrafficController::new(gateway_transceiver, shutdown_tracker.clone_shutdown_token());
tracing::info!("Starting mix traffic controller...");
let mut mix_traffic_controller = MixTrafficController::new(
gateway_transceiver,
shutdown_tracker.clone_shutdown_token(),
event_tx,
);
let mix_tx = mix_traffic_controller.mix_rx();
let client_tx = mix_traffic_controller.client_tx();
shutdown_tracker.try_spawn_named(
async move { mix_traffic_controller.run().await },
@@ -803,7 +854,7 @@ where
{
// if client keys do not exist already, create and persist them
if key_store.load_keys().await.is_err() {
info!("could not find valid client keys - a new set will be generated");
tracing::info!("could not find valid client keys - a new set will be generated");
let mut rng = OsRng;
let keys = if let Some(derivation_material) = derivation_material {
ClientKeys::from_master_key(&mut rng, &derivation_material)
@@ -842,6 +893,7 @@ where
Ok(client.get_key_rotation_info().await?.into())
}
#[instrument(skip_all)]
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
where
S::ReplyStore: Send + Sync,
@@ -850,7 +902,7 @@ where
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
{
info!("Starting nym client");
tracing::info!("Starting nym client");
#[cfg(debug_assertions)]
#[cfg(target_arch = "wasm32")]
{
@@ -884,6 +936,9 @@ where
// channels responsible for controlling real messages
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
// channels responsible for event management
let (event_sender, event_receiver) = mpsc::unbounded();
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor =
@@ -896,6 +951,8 @@ where
None => nym_task::get_sdk_shutdown_tracker()?,
};
Self::start_event_control(self.event_tx, event_receiver, &shutdown_tracker);
// channels responsible for dealing with reply-related fun
let (reply_controller_sender, reply_controller_receiver) =
reply_controller::requests::new_control_channels();
@@ -986,6 +1043,7 @@ where
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
gateway_transceiver,
&shutdown_tracker.child_tracker(),
EventSender(event_sender),
);
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -1035,8 +1093,8 @@ where
);
}
debug!("Core client startup finished!");
debug!("The address of this client is: {self_address}");
tracing::debug!("Core client startup finished!");
tracing::debug!("The address of this client is: {self_address}");
#[cfg(debug_assertions)]
#[cfg(target_arch = "wasm32")]
@@ -1052,6 +1110,7 @@ where
client_input: ClientInput {
connection_command_sender: client_connection_tx,
input_sender,
client_request_sender,
},
},
client_output: ClientOutputStatus::AwaitingConsumer {
@@ -1067,7 +1126,6 @@ where
},
stats_reporter,
shutdown_handle: shutdown_tracker, // The primary tracker for this client
client_request_sender,
forget_me: self.config.debug.forget_me,
remember_me: self.config.debug.remember_me,
})
@@ -1081,7 +1139,6 @@ pub struct BaseClient {
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
pub stats_reporter: ClientStatsSender,
pub client_request_sender: ClientRequestSender,
pub shutdown_handle: ShutdownTracker,
pub forget_me: ForgetMe,
pub remember_me: RememberMe,
@@ -0,0 +1,40 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::StreamExt;
use crate::client::base_client::{EventReceiver, EventSender, MixnetClientEvent};
/// Launches and manages task events, propagating upwards what is not strictly internal.
pub(crate) struct EventControl {
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
}
impl EventControl {
pub(crate) fn new(
parent_event_tx: Option<EventSender>,
children_event_rx: EventReceiver,
) -> Self {
EventControl {
parent_event_tx,
children_event_rx,
}
}
fn is_internal(event: MixnetClientEvent) -> bool {
match event {
MixnetClientEvent::Traffic(_) => false,
}
}
pub(crate) async fn run(mut self) {
while let Some(event) = self.children_event_rx.next().await {
if let Some(parent_event_tx) = &self.parent_event_tx {
if !Self::is_internal(event) {
parent_event_tx.send(event);
}
}
}
}
}
@@ -29,6 +29,8 @@ pub enum InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
},
/// Creates a message used for a duplex anonymous communication where the recipient
@@ -45,6 +47,8 @@ pub enum InputMessage {
reply_surbs: u32,
lane: TransmissionLane,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
},
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
@@ -90,12 +94,16 @@ impl InputMessage {
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Self {
let message = InputMessage::Regular {
recipient,
data,
lane,
max_retransmissions: None,
#[cfg(feature = "otel")]
trace_id,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -110,6 +118,8 @@ impl InputMessage {
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Self {
let message = InputMessage::Anonymous {
recipient,
@@ -117,6 +127,8 @@ impl InputMessage {
reply_surbs,
lane,
max_retransmissions: None,
#[cfg(feature = "otel")]
trace_id,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -185,4 +197,14 @@ impl InputMessage {
self.set_max_retransmissions(max_retransmissions);
self
}
#[cfg(feature = "otel")]
pub fn trace_id(&self) -> Option<[u8; 12]> {
match self {
InputMessage::Regular { trace_id, .. } => *trace_id,
InputMessage::Anonymous { trace_id, .. } => *trace_id,
InputMessage::Premade { .. } | InputMessage::Reply { .. } => None,
InputMessage::MessageWrapper { message, .. } => message.trace_id(),
}
}
}
@@ -1,7 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::client::{
base_client::{EventSender, MixnetClientEvent},
mix_traffic::transceiver::GatewayTransceiver,
};
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
use nym_task::ShutdownToken;
@@ -22,28 +25,33 @@ const MAX_FAILURE_COUNT: usize = 100;
// that's also disgusting.
pub struct Empty;
#[derive(Clone, Copy, Debug)]
pub enum MixTrafficEvent {
FailedSendingSphinx,
}
pub struct MixTrafficController {
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
mix_tx: BatchMixMessageSender,
mix_rx: BatchMixMessageReceiver,
client_rx: ClientRequestReceiver,
client_tx: ClientRequestSender,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
consecutive_gateway_failure_count: usize,
shutdown_token: ShutdownToken,
event_tx: EventSender,
}
impl MixTrafficController {
pub fn new<T>(
gateway_transceiver: T,
shutdown_token: ShutdownToken,
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
)
event_tx: EventSender,
) -> MixTrafficController
where
T: GatewayTransceiver + Send + 'static,
{
@@ -52,41 +60,32 @@ impl MixTrafficController {
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
(
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
shutdown_token,
},
message_sender,
client_sender,
)
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_tx: message_sender,
mix_rx: message_receiver,
client_rx: client_receiver,
client_tx: client_sender,
consecutive_gateway_failure_count: 0,
shutdown_token,
event_tx,
}
}
pub fn new_dynamic(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown_token: ShutdownToken,
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
) {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
(
MixTrafficController {
gateway_transceiver,
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
shutdown_token,
},
message_sender,
client_sender,
)
event_tx: EventSender,
) -> MixTrafficController {
Self::new(gateway_transceiver, shutdown_token, event_tx)
}
pub fn client_tx(&self) -> ClientRequestSender {
self.client_tx.clone()
}
pub fn mix_rx(&self) -> BatchMixMessageSender {
self.mix_tx.clone()
}
async fn on_messages(
@@ -145,34 +144,26 @@ impl MixTrafficController {
trace!("MixTrafficController: Received shutdown");
break;
}
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
break;
}
// mix_rx should never error out as we're holding one instance of the sender
Some(mix_packets) = self.mix_rx.recv() => {
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
self.event_tx.send(MixnetClientEvent::Traffic(MixTrafficEvent::FailedSendingSphinx));
break;
}
},
None => {
trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
client_request = self.client_rx.recv() => match client_request {
Some(client_request) => {
self.on_client_request(client_request).await;
},
None => {
trace!("MixTrafficController, client request channel closed");
break
}
},
// client_rx should never error out as we're holding one instance of the sender
Some(client_request) = self.client_rx.recv() => {
self.on_client_request(client_request).await;
}
}
}
debug!("MixTrafficController: Exiting");
+1
View File
@@ -3,6 +3,7 @@
pub mod base_client;
pub mod cover_traffic_stream;
pub(crate) mod event_control;
pub(crate) mod helpers;
pub mod inbound_messages;
pub mod key_manager;
@@ -70,6 +70,7 @@ where
.send_reply(recipient_tag, data, lane, max_retransmissions);
}
#[instrument(skip_all)]
async fn handle_plain_message(
&mut self,
recipient: Recipient,
@@ -77,16 +78,27 @@ where
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions)
.try_send_plain_message(
recipient,
content,
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
{
warn!("failed to send a plain message - {err}")
}
}
#[instrument(skip_all)]
async fn handle_repliable_message(
&mut self,
recipient: Recipient,
@@ -95,6 +107,8 @@ where
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) {
if let Err(err) = self
.message_handler
@@ -105,6 +119,8 @@ where
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
{
@@ -113,20 +129,36 @@ where
}
#[allow(clippy::panic)]
#[instrument(skip_all)]
async fn on_input_message(&mut self, msg: InputMessage) {
#[cfg(feature = "otel")]
let trace_id = msg.trace_id();
#[cfg(feature = "otel")]
if let Some(tid) = trace_id {
tracing::info!("Processing input message with trace_id: {:?}", tid);
}
match msg {
InputMessage::Regular {
recipient,
data,
lane,
max_retransmissions,
..
} => {
#[cfg(feature = "otel")]
info!(
"Handling regular input message with trace_id: {:?}",
trace_id
);
self.handle_plain_message(
recipient,
data,
lane,
PacketType::Mix,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
}
@@ -136,7 +168,13 @@ where
reply_surbs,
lane,
max_retransmissions,
..
} => {
#[cfg(feature = "otel")]
tracing::info!(
"Handling anonymous input message with trace_id: {:?}",
trace_id
);
self.handle_repliable_message(
recipient,
data,
@@ -144,6 +182,8 @@ where
lane,
PacketType::Mix,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
}
@@ -153,6 +193,8 @@ where
lane,
max_retransmissions,
} => {
#[cfg(feature = "otel")]
info!("Handling reply input message with trace_id: {:?}", trace_id);
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
.await;
}
@@ -166,13 +208,21 @@ where
data,
lane,
max_retransmissions,
..
} => {
#[cfg(feature = "otel")]
tracing::info!(
"Handling regular input message with trace_id: {:?}",
trace_id
);
self.handle_plain_message(
recipient,
data,
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
}
@@ -182,6 +232,7 @@ where
reply_surbs,
lane,
max_retransmissions,
..
} => {
self.handle_repliable_message(
recipient,
@@ -190,6 +241,8 @@ where
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
}
@@ -213,6 +266,7 @@ where
};
}
#[instrument(skip_all)]
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
debug!("Started InputMessageListener with graceful shutdown support");
@@ -60,7 +60,13 @@ where
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.try_prepare_single_chunk_for_sending(
packet_recipient,
chunk_data,
packet_type,
#[cfg(feature = "otel")]
None
)
.await
}
@@ -27,7 +27,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tracing::{debug, error, info, trace, warn};
use tracing::{debug, error, info, instrument, trace, warn};
// TODO: move that error elsewhere since it seems to be contaminating different files
#[derive(Debug, Error)]
@@ -476,6 +476,7 @@ where
self.forward_messages(msgs, lane).await;
}
#[instrument(skip_all)]
pub(crate) async fn try_send_plain_message(
&mut self,
recipient: Recipient,
@@ -483,6 +484,8 @@ where
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(
@@ -491,10 +494,13 @@ where
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await
}
#[instrument(skip_all)]
pub(crate) async fn try_split_and_send_non_reply_message(
&mut self,
message: NymMessage,
@@ -502,6 +508,8 @@ where
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
@@ -534,6 +542,8 @@ where
&self.config.ack_key,
&recipient,
packet_type,
#[cfg(feature = "otel")]
trace_id,
)?;
let real_message = RealMessage::new(
@@ -585,6 +595,8 @@ where
TransmissionLane::AdditionalReplySurbs,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
None,
)
.await?;
@@ -602,6 +614,8 @@ where
lane: TransmissionLane,
packet_type: PacketType,
max_retransmissions: Option<u32>,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -625,6 +639,8 @@ where
lane,
packet_type,
max_retransmissions,
#[cfg(feature = "otel")]
trace_id,
)
.await?;
@@ -639,6 +655,8 @@ where
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
@@ -650,6 +668,8 @@ where
&self.config.ack_key,
&recipient,
packet_type,
#[cfg(feature = "otel")]
trace_id,
)?;
Ok(prepared_fragment)
@@ -80,6 +80,8 @@ impl StatisticsControl {
stats_report.into(),
TransmissionLane::General,
None,
#[cfg(feature = "otel")]
None,
);
if let Err(err) = self.report_tx.send(report_message).await {
tracing::error!("Failed to report client stats: {err:?}");
+77 -20
View File
@@ -45,6 +45,7 @@ type WsConn = JSWebsocket;
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
const MEASUREMENTS: usize = 3;
const DEFAULT_NYM_API_RETRIES: usize = 3;
#[cfg(not(target_arch = "wasm32"))]
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
@@ -132,25 +133,27 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
}
}
pub async fn gateways_for_init<R: Rng>(
rng: &mut R,
pub async fn gateways_for_init(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
retry_count: Option<usize>,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
// Build client with ALL URLs for fallback support
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
// Use the unified HTTP client directly with optional user agent
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
.map_err(|e| {
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(
e,
))
})?
.with_bincode(); // Use bincode for better performance
if nym_api_urls.is_empty() {
return Err(ClientCoreError::ListOfNymApisIsEmpty);
}
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())
.with_retries(retry_count)
.with_bincode();
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
@@ -160,7 +163,7 @@ pub async fn gateways_for_init<R: Rng>(
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
})?;
tracing::debug!("Fetching list of gateways from: {nym_api}");
tracing::debug!("Fetching list of gateways from: {:?}", nym_api_urls);
// Use our helper to handle pagination
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
@@ -172,17 +175,15 @@ pub async fn gateways_for_init<R: Rng>(
// filter out gateways below minimum performance and ones that could operate as a mixnode
// (we don't want instability)
let valid_gateways = gateways
let valid_gateways: Vec<RoutingNode> = gateways
.iter()
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<_>>();
tracing::debug!("After checking validity: {}", valid_gateways.len());
tracing::trace!("Valid gateways: {valid_gateways:#?}");
.collect();
tracing::info!(
"and {} after validity and performance filtering",
"Found {} valid gateways after filtering",
valid_gateways.len()
);
@@ -345,13 +346,20 @@ pub(super) fn get_specified_gateway(
must_use_tls: bool,
) -> Result<RoutingNode, ClientCoreError> {
tracing::debug!("Requesting specified gateway: {gateway_identity}");
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
let gateway = gateways
.iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
.ok_or_else(|| {
tracing::debug!(
"Gateway {gateway_identity} not found in {} available gateways",
gateways.len()
);
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
})?;
let Some(entry_details) = gateway.entry.as_ref() else {
return Err(ClientCoreError::UnsupportedEntry {
@@ -414,3 +422,52 @@ pub(super) async fn register_with_gateway(
authenticated_ephemeral_client: gateway_client,
})
}
#[cfg(test)]
mod tests {
use url::Url;
#[test]
fn test_single_url_builds_without_retries() {
let urls = [Url::parse("https://api.nym.com").unwrap()];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
}
#[test]
fn test_multiple_urls_prepared_for_retries() {
let urls = vec![
Url::parse("https://api1.nym.com").unwrap(),
Url::parse("https://api2.nym.com").unwrap(),
Url::parse("https://api3.nym.com").unwrap(),
];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
assert!(
nym_api_urls.len() > 1,
"Multiple URLs trigger retry behavior"
);
}
#[test]
fn test_empty_url_list_is_detected() {
let urls: Vec<Url> = vec![];
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
.iter()
.map(|url| nym_http_api_client::Url::from(url.clone()))
.collect();
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
}
}
@@ -1043,6 +1043,12 @@ impl<C, St> GatewayClient<C, St> {
}
// Note: this requires prior authentication
#[instrument(skip_all,
fields(
gateway = %self.gateway_identity,
gateway_address = %self.gateway_address
)
)]
pub fn start_listening_for_mixnet_messages(&mut self) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -267,6 +267,7 @@ impl Client {
}
impl SendWithoutResponse for Client {
#[instrument(skip(self, packet))]
fn send_without_response(&self, packet: MixPacket) -> io::Result<()> {
let address = packet.next_hop_address();
trace!("Sending packet to {address}");
@@ -10,7 +10,7 @@ use cosmrs::tx;
use cosmrs::tx::SignDoc;
use nym_config::defaults;
use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
type Secp256k1Keypair = (SigningKey, PublicKey);
@@ -128,9 +128,20 @@ impl DirectSecp256k1HdWallet {
Ok(accounts)
}
pub fn secret(&self) -> &bip39::Mnemonic {
&self.secret
}
#[deprecated(
note = "use either .secret() for obtaining &bip39::Mnemonic or .mnemonic_string() for Zeroizing wrapper around the String"
)]
pub fn mnemonic(&self) -> String {
self.secret.to_string()
}
pub fn mnemonic_string(&self) -> Zeroizing<String> {
Zeroizing::new(self.secret.to_string())
}
}
#[must_use]
@@ -18,6 +18,6 @@ pub fn create_account(args: Args, prefix: &str) {
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
// Output address and mnemonics into separate lines for easier parsing
println!("{}", wallet.mnemonic());
println!("{}", wallet.mnemonic_string().as_str());
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
}
-1
View File
@@ -17,7 +17,6 @@ nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common",
bs58 = { workspace = true }
lazy_static = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
rand_core = { workspace = true }
+16 -41
View File
@@ -13,9 +13,9 @@ use rand::CryptoRng;
use rand_core::RngCore;
use std::collections::HashMap;
use std::ops::Neg;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
@@ -164,8 +164,7 @@ impl Ciphertexts {
}
}
#[derive(Zeroize)]
#[zeroize(drop)]
#[derive(Zeroize, ZeroizeOnDrop)]
/// Randomness generated during ciphertext generation that is required for proofs of knowledge.
///
/// It must be handled with extreme care as its misuse might help malicious parties to recover
@@ -399,7 +398,7 @@ pub fn baby_step_giant_step(
#[cfg(test)]
mod tests {
use super::*;
use crate::bte::{keygen, setup, DEFAULT_BSGS_TABLE};
use crate::bte::{keygen, setup, BSGS_TABLE};
use rand_core::SeedableRng;
fn verify_hazmat_rand(ciphertext: &Ciphertexts, randomness: &HazmatRandomness) {
@@ -457,8 +456,6 @@ mod tests {
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let lookup_table = &DEFAULT_BSGS_TABLE;
for _ in 0..10 {
let m1 = Share::random(&mut rng);
let m2 = Share::random(&mut rng);
@@ -467,22 +464,12 @@ mod tests {
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 = decrypt_share(
&params,
&decryption_key1,
0,
&ciphertext,
Some(lookup_table),
)
.unwrap();
let recovered2 = decrypt_share(
&params,
&decryption_key2,
1,
&ciphertext,
Some(lookup_table),
)
.unwrap();
let recovered1 =
decrypt_share(&params, &decryption_key1, 0, &ciphertext, Some(&BSGS_TABLE))
.unwrap();
let recovered2 =
decrypt_share(&params, &decryption_key2, 1, &ciphertext, Some(&BSGS_TABLE))
.unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
@@ -498,8 +485,6 @@ mod tests {
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let lookup_table = &DEFAULT_BSGS_TABLE;
for _ in 0..10 {
let m1 = Share::random(&mut rng);
let m2 = Share::random(&mut rng);
@@ -508,22 +493,12 @@ mod tests {
let (ciphertext, hazmat) = encrypt_shares(shares, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 = decrypt_share(
&params,
&decryption_key1,
0,
&ciphertext,
Some(lookup_table),
)
.unwrap();
let recovered2 = decrypt_share(
&params,
&decryption_key2,
1,
&ciphertext,
Some(lookup_table),
)
.unwrap();
let recovered1 =
decrypt_share(&params, &decryption_key1, 0, &ciphertext, Some(&BSGS_TABLE))
.unwrap();
let recovered2 =
decrypt_share(&params, &decryption_key2, 1, &ciphertext, Some(&BSGS_TABLE))
.unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
+5 -5
View File
@@ -11,7 +11,7 @@ use group::GroupEncoding;
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
use rand::CryptoRng;
use rand_core::RngCore;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
// produces public key and a decryption key for the root of the tree
pub fn keygen(
@@ -48,7 +48,7 @@ pub fn keygen(
(dk, key_with_proof)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroize)]
pub struct PublicKey(pub(crate) G1Projective);
impl PublicKey {
@@ -57,7 +57,7 @@ impl PublicKey {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct PublicKeyWithProof {
pub(crate) key: PublicKey,
pub(crate) proof: ProofOfDiscreteLog,
@@ -136,8 +136,7 @@ impl PublicKeyWithProof {
}
}
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct DecryptionKey {
// g1^rho
@@ -242,6 +241,7 @@ impl DecryptionKey {
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KeyPair {
pub(crate) private_key: DecryptionKey,
pub(crate) public_key: PublicKeyWithProof,
+9 -9
View File
@@ -1,11 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::sync::LazyLock;
use crate::bte::encryption::BabyStepGiantStepLookup;
use crate::utils::hash_g2;
use crate::{Chunk, Share};
use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt};
use group::Curve;
use lazy_static::lazy_static;
pub mod encryption;
pub mod keys;
@@ -16,14 +18,12 @@ pub mod proof_sharing;
pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts};
pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof};
lazy_static! {
pub(crate) static ref PAIRING_BASE: Gt =
bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator());
pub(crate) static ref G2_GENERATOR_PREPARED: G2Prepared =
G2Prepared::from(G2Affine::generator());
pub(crate) static ref DEFAULT_BSGS_TABLE: encryption::BabyStepGiantStepLookup =
encryption::BabyStepGiantStepLookup::default();
}
pub(crate) static PAIRING_BASE: LazyLock<Gt> =
LazyLock::new(|| bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator()));
pub(crate) static G2_GENERATOR_PREPARED: LazyLock<G2Prepared> =
LazyLock::new(|| G2Prepared::from(G2Affine::generator()));
pub static BSGS_TABLE: LazyLock<BabyStepGiantStepLookup> =
LazyLock::new(BabyStepGiantStepLookup::default);
// Domain tries to follow guidelines specified by:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
+2 -1
View File
@@ -12,6 +12,7 @@ use ff::Field;
use group::{Group, GroupEncoding};
use rand::{CryptoRng, Rng};
use rand_core::{RngCore, SeedableRng};
use zeroize::{Zeroize, ZeroizeOnDrop};
const CHUNKING_ORACLE_DOMAIN: &[u8] =
b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_CHACHA20_CHUNKING_ORACLE";
@@ -67,7 +68,7 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct ProofOfChunking {
y0: G1Projective,
bb: Vec<G1Projective>,
+5 -5
View File
@@ -7,14 +7,14 @@ use ff::Field;
use group::GroupEncoding;
use rand::CryptoRng;
use rand_core::RngCore;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
// Domain tries to follow guidelines specified by:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
const DISCRETE_LOG_DOMAIN: &[u8] =
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct ProofOfDiscreteLog {
pub(crate) rand_commitment: G1Projective,
pub(crate) response: Scalar,
@@ -52,9 +52,9 @@ impl ProofOfDiscreteLog {
let public_bytes = public.to_bytes();
let rand_commit_bytes = rand_commit.to_bytes();
let mut bytes = Vec::with_capacity(96);
bytes.extend_from_slice(public_bytes.as_ref());
bytes.extend_from_slice(rand_commit_bytes.as_ref());
let mut bytes = [0u8; 96];
bytes[0..48].copy_from_slice(public_bytes.as_ref());
bytes[48..96].copy_from_slice(rand_commit_bytes.as_ref());
hash_to_scalar(bytes, DISCRETE_LOG_DOMAIN)
}
+2 -1
View File
@@ -12,6 +12,7 @@ use group::GroupEncoding;
use rand::CryptoRng;
use rand_core::RngCore;
use std::collections::BTreeMap;
use zeroize::{Zeroize, ZeroizeOnDrop};
// Domain tries to follow guidelines specified by:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
@@ -77,7 +78,7 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct ProofOfSecretSharing {
ff: G1Projective,
aa: G2Projective,
+2 -2
View File
@@ -18,7 +18,7 @@ use rand_core::RngCore;
use std::collections::BTreeMap;
use zeroize::Zeroize;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Zeroize)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct RecoveredVerificationKeys {
pub recovered_master: G2Projective,
@@ -83,7 +83,7 @@ impl RecoveredVerificationKeys {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Zeroize)]
pub struct Dealing {
pub public_coefficients: PublicCoefficients,
pub ciphertexts: Ciphertexts,
+3 -4
View File
@@ -9,9 +9,9 @@ use group::GroupEncoding;
use rand::CryptoRng;
use rand_core::RngCore;
use std::ops::{Add, Index, IndexMut};
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct PublicCoefficients {
pub(crate) coefficients: Vec<G2Projective>,
}
@@ -111,8 +111,7 @@ impl IndexMut<usize> for PublicCoefficients {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
#[zeroize(drop)]
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
pub struct Polynomial {
coefficients: Vec<Scalar>,
}
+3 -5
View File
@@ -6,14 +6,13 @@ use crate::error::DkgError;
use crate::interpolation::perform_lagrangian_interpolation_at_origin;
use crate::NodeIndex;
use bls12_381::Scalar;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
// if this type is changed, one must ensure all values can fit in it
pub type Chunk = u16;
#[derive(PartialEq, Eq, Debug, Zeroize)]
#[derive(PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(test, derive(Clone))]
#[zeroize(drop)]
pub struct Share(pub(crate) Scalar);
pub fn combine_shares(shares: Vec<Share>, node_indices: &[NodeIndex]) -> Result<Scalar, DkgError> {
@@ -66,9 +65,8 @@ impl From<Scalar> for Share {
}
}
#[derive(Default, Zeroize)]
#[derive(Default, Zeroize, ZeroizeOnDrop)]
#[cfg_attr(test, derive(Clone))]
#[zeroize(drop)]
pub(crate) struct ChunkedShare {
pub(crate) chunks: [Chunk; NUM_CHUNKS],
}
+15 -1
View File
@@ -19,11 +19,11 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
strum = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
time = { workspace = true }
subtle = { workspace = true }
zeroize = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
nym-pemstore = { path = "../pemstore" }
nym-sphinx = { path = "../nymsphinx" }
@@ -34,6 +34,11 @@ nym-task = { path = "../task" }
nym-credentials = { path = "../credentials" }
nym-credentials-interface = { path = "../credentials-interface" }
opentelemetry = { workspace = true, features = ["trace"], optional = true }
opentelemetry_sdk = { workspace = true, optional = true }
tracing-opentelemetry = { workspace = true, optional = true }
tracing = { workspace = true, features = ["std", "attributes", "tracing-attributes"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
workspace = true
features = ["time"]
@@ -51,3 +56,12 @@ anyhow = { workspace = true }
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
nym-test-utils = { path = "../test-utils" }
tokio = { workspace = true, features = ["full"] }
[features]
default = []
otel = [
"nym-bin-common/otel",
"opentelemetry",
"opentelemetry_sdk",
"tracing-opentelemetry",
]
@@ -4,6 +4,7 @@
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
use nym_crypto::asymmetric::ed25519;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::iter;
use std::time::Duration;
use subtle::ConstantTimeEq;
@@ -16,6 +17,9 @@ pub struct AuthenticateRequest {
pub content: AuthenticateRequestContent,
pub request_signature: ed25519::Signature,
#[serde(default)]
pub otel_context: Option<HashMap<String, String>>,
}
impl AuthenticateRequest {
@@ -23,6 +27,7 @@ impl AuthenticateRequest {
protocol_version: u8,
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
otel_context: Option<HashMap<String, String>>,
) -> Result<AuthenticateRequest, GatewayRequestsError> {
let content = AuthenticateRequestContent::new(
protocol_version,
@@ -35,6 +40,7 @@ impl AuthenticateRequest {
Ok(AuthenticateRequest {
content,
request_signature,
otel_context,
})
}
@@ -8,12 +8,16 @@ use crate::{
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
INITIAL_PROTOCOL_VERSION,
};
#[cfg(feature = "otel")]
use nym_bin_common::opentelemetry::context::ContextCarrier;
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::collections::HashMap;
use std::str::FromStr;
use tracing::{instrument, warn};
use tungstenite::Message;
pub mod authenticate;
@@ -76,6 +80,10 @@ pub enum ClientControlRequest {
address: String,
enc_address: String,
iv: String,
/// this is a trace id that is used in testing and performance verification
/// in mainnet, this will always be set to None
#[serde(default)]
otel_context: Option<HashMap<String, String>>,
},
AuthenticateV2(Box<AuthenticateRequest>),
@@ -127,14 +135,25 @@ impl ClientControlRequest {
let nonce = shared_key.random_nonce_or_iv();
let ciphertext = shared_key.encrypt_naive(address.as_bytes_ref(), Some(&nonce))?;
#[cfg(feature = "otel")]
let context_carrier = {
let context = opentelemetry::Context::current();
ContextCarrier::new_with_current_context(context).into_map()
};
Ok(ClientControlRequest::Authenticate {
protocol_version,
address: address.as_base58_string(),
enc_address: bs58::encode(&ciphertext).into_string(),
iv: bs58::encode(&nonce).into_string(),
#[cfg(feature = "otel")]
otel_context: Some(context_carrier),
#[cfg(not(feature = "otel"))]
otel_context: None,
})
}
#[instrument(skip_all)]
pub fn new_authenticate_v2(
shared_key: &SharedGatewayKey,
identity_keys: &ed25519::KeyPair,
@@ -142,8 +161,25 @@ impl ClientControlRequest {
// if we're using v2 authentication, we must announce at least that protocol version
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
#[cfg(feature = "otel")]
let context_carrier = {
use nym_bin_common::opentelemetry::context::extract_trace_id_from_tracing_cx;
use tracing_opentelemetry::OpenTelemetrySpanExt;
let current_span = tracing::Span::current();
let otel_context = current_span.context();
ContextCarrier::new_with_current_context(otel_context).into_map()
};
#[cfg(not(feature = "otel"))]
let context_carrier: HashMap<String, String> = HashMap::new();
Ok(ClientControlRequest::AuthenticateV2(Box::new(
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
AuthenticateRequest::new(
protocol_version,
shared_key,
identity_keys,
Some(context_carrier),
)?,
)))
}
+4 -1
View File
@@ -11,9 +11,11 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
# TODO: Remove otel from default before release
default=["tunneling"]
tunneling=[]
network-defaults = ["dep:nym-network-defaults"]
otel = ["nym-bin-common/otel", "opentelemetry", "opentelemetry_sdk"]
debug-inventory = ["nym-http-api-client-macro/debug-inventory"]
[dependencies]
@@ -24,6 +26,8 @@ reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
opentelemetry = { workspace = true, optional = true }
opentelemetry_sdk = { workspace = true, optional = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true}
@@ -53,4 +57,3 @@ features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
+15
View File
@@ -986,6 +986,21 @@ impl ApiClientCore for Client {
self.apply_hosts_to_req(&mut req);
// if opentelemetry is activated add the current trace context to the request
#[cfg(feature = "otel")]
{
use nym_bin_common::opentelemetry::context::ContextCarrier;
use opentelemetry::Context;
let carrier = ContextCarrier::new_with_current_context(Context::current());
if let Some(traceparent) = carrier.extract_traceparent() {
if let Ok(header_value) = HeaderValue::from_str(&traceparent) {
req.headers_mut().insert("traceparent", header_value);
}
}
}
let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req);
rb = rb
+10
View File
@@ -183,6 +183,11 @@ impl Url {
})
}
/// Returns the underlying URL
pub fn inner_url(&self) -> &url::Url {
&self.url
}
/// Returns true if the URL has a front domain set
pub fn has_front(&self) -> bool {
if let Some(fronts) = &self.fronts {
@@ -201,6 +206,11 @@ impl Url {
.and_then(|url| url.host_str())
}
/// Returns the fronts
pub fn fronts(&self) -> Option<&[url::Url]> {
self.fronts.as_deref()
}
/// Return the string representation of the host (domain or IP address) for this URL, if any.
pub fn host_str(&self) -> Option<&str> {
self.url.host_str()
+3
View File
@@ -18,6 +18,7 @@ bytes = { workspace = true, optional = true }
colored = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
mime = { workspace = true, optional = true }
nym-bin-common = { path = "../bin-common" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_yaml = { workspace = true, optional = true }
@@ -49,6 +50,8 @@ middleware = [
"zeroize"
]
otel = ["nym-bin-common/otel"]
utoipa = ["dep:utoipa"]
[lints]
@@ -56,6 +56,16 @@ async fn log_request(
let host = header_map(request.headers().get(HOST), "Unknown Host".to_string());
// Extract traceparent from headers if it exists
#[cfg(feature = "otel")]
let traceparent = request
.headers()
.get("traceparent")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
#[cfg(not(feature = "otel"))]
let traceparent: Option<String> = None;
let start = Instant::now();
// run request through all middleware, incl. extractors
let res = next.run(request).await;
@@ -82,10 +92,10 @@ async fn log_request(
match level {
LogLevel::Debug => debug!(
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}",
),
LogLevel::Info => info!(
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}"
),
}
+4
View File
@@ -30,3 +30,7 @@ workspace = true
## wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
path = "../wasm/utils"
[ features ]
default = []
otel = ["nym-sphinx/otel"]
+2
View File
@@ -231,6 +231,8 @@ where
&address,
&address,
PacketType::Mix,
#[cfg(feature = "otel")]
None,
)?)
}
+10
View File
@@ -8,12 +8,14 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
sphinx-packet = { workspace = true }
tracing = { workspace = true }
rand = { workspace = true }
rand_distr = { workspace = true }
rand_chacha = { workspace = true }
thiserror = { workspace = true }
nym-bin-common = { path = "../bin-common" }
nym-sphinx-acknowledgements = { path = "acknowledgements" }
nym-sphinx-addressing = { path = "addressing" }
nym-sphinx-anonymous-replies = { path = "anonymous-replies" }
@@ -55,3 +57,11 @@ outfox = [
"nym-sphinx-params/outfox",
"nym-sphinx-types/outfox",
]
otel = [
"nym-bin-common/otel",
"nym-sphinx-acknowledgements/otel",
"nym-sphinx-addressing/otel",
"nym-sphinx-cover/otel",
"nym-sphinx-anonymous-replies/otel",
]
@@ -24,3 +24,4 @@ nym-topology = { path = "../../topology" }
[features]
serde = ["dep:serde", "generic-array"]
otel = ["nym-sphinx-addressing/otel"]
@@ -61,7 +61,10 @@ impl SurbAck {
};
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
#[cfg(not(feature = "otel"))]
let destination = recipient.as_sphinx_destination();
#[cfg(feature = "otel")]
let destination = recipient.as_sphinx_destination(None);
let surb_ack_payload = prepare_identifier(rng, ack_key, marshaled_fragment_id);
let packet_size = match packet_type {
+7 -1
View File
@@ -8,14 +8,20 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
nym-bin-common = { path = "../../bin-common", features = ["opentelemetry"] } # for trace id compression/decompression
nym-crypto = { path = "../../crypto", features = ["asymmetric", "sphinx"] } # all addresses are expressed in terms on their crypto keys
nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate
serde = { workspace = true } # implementing serialization/deserialization for some types, like `Recipient`
thiserror = { workspace = true }
tracing = { workspace = true }
[dev-dependencies]
rand = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["rand"] }
bincode = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
[features]
default = []
otel = ["nym-bin-common/otel"]
+18 -2
View File
@@ -153,12 +153,28 @@ impl Recipient {
// TODO: Currently the `DestinationAddress` is equivalent to `ClientIdentity`, but perhaps
// it shouldn't be? Maybe it should be (for example) H(`ClientIdentity || ClientEncryptionKey`)
// instead? That is an open question.
pub fn as_sphinx_destination(&self) -> Destination {
pub fn as_sphinx_destination(
&self,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>
) -> Destination {
#[cfg(feature = "otel")]
use nym_bin_common::opentelemetry::compact_id_generator::decompress_trace_id;
#[cfg(feature = "otel")]
let trace_id_16 = if let Some(trace_id) = trace_id {
decompress_trace_id(&trace_id)
} else {
decompress_trace_id(&[0u8; 12])
};
// since the nym mix network differs slightly in design from loopix, we do not care
// about "surb_id" field at all and just use the default value.
Destination::new(
self.client_identity.derive_destination_address(),
Default::default(),
#[cfg(not(feature = "otel"))]
[0u8; 16],
#[cfg(feature = "otel")]
trace_id_16
)
}
@@ -25,3 +25,7 @@ workspace = true
[dev-dependencies]
rand_chacha = { workspace = true }
[ features ]
default = []
otel = ["nym-sphinx-addressing/otel"]
@@ -82,6 +82,9 @@ impl ReplySurb {
topology.random_route_to_egress(rng, recipient.gateway())?
};
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
#[cfg(feature = "otel")]
let destination = recipient.as_sphinx_destination(None);
#[cfg(not(feature = "otel"))]
let destination = recipient.as_sphinx_destination();
let mut surb_material = SURBMaterial::new(route, delays, destination);
+3
View File
@@ -20,3 +20,6 @@ nym-sphinx-params = { path = "../params" }
nym-sphinx-routing = { path = "../routing" }
nym-sphinx-types = { path = "../types" }
nym-topology = { path = "../../topology" }
[features]
otel = ["nym-sphinx-addressing/otel"]
+3
View File
@@ -125,7 +125,10 @@ where
let route = topology.random_route_to_egress(rng, full_address.gateway())?;
let delays = nym_sphinx_routing::generate_hop_delays(average_packet_delay, route.len());
#[cfg(not(feature = "otel"))]
let destination = full_address.as_sphinx_destination();
#[cfg(feature = "otel")]
let destination = full_address.as_sphinx_destination(None);
let rotation_id = topology.current_key_rotation();
let sphinx_key_rotation = SphinxKeyRotation::from(rotation_id);
+6
View File
@@ -11,8 +11,10 @@ repository = { workspace = true }
bytes = { workspace = true }
tokio-util = { workspace = true, features = ["codec"] }
thiserror = { workspace = true }
opentelemetry = { workspace = true }
tracing = { workspace = true }
nym-bin-common = { path = "../../bin-common" }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] }
nym-sphinx-forwarding = { path = "../forwarding" }
@@ -21,3 +23,7 @@ nym-sphinx-acknowledgements = { path = "../acknowledgements" }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
[features]
default = []
otel = ["nym-bin-common/otel"]
+42 -9
View File
@@ -2,6 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use crate::packet::FramedNymPacket;
#[cfg(feature = "otel")]
use nym_bin_common::opentelemetry::{
compact_id_generator::decompress_trace_id, context::ManualContextPropagator,
};
use nym_sphinx_acknowledgements::surb_ack::{SurbAck, SurbAckRecoveryError};
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError};
use nym_sphinx_forwarding::packet::MixPacket;
@@ -14,7 +19,9 @@ use nym_sphinx_types::{
};
use std::fmt::Display;
use thiserror::Error;
use tracing::{debug, error, info, trace};
#[cfg(feature = "otel")]
use tracing::warn_span;
use tracing::{debug, error, info, instrument, trace, warn};
#[derive(Debug)]
pub enum MixProcessingResultData {
@@ -154,6 +161,7 @@ impl PartiallyUnwrappedPacket {
})
}
#[instrument(skip_all)]
pub fn finalise_unwrapping(self) -> Result<MixProcessingResult, PacketProcessingError> {
let packet_size = self.received_data.packet_size();
let packet_type = self.received_data.packet_type();
@@ -236,6 +244,7 @@ fn perform_framed_packet_processing(
})
}
#[instrument(skip_all)]
fn wrap_processed_sphinx_packet(
packet: nym_sphinx_types::ProcessedPacket,
packet_size: PacketSize,
@@ -258,15 +267,38 @@ fn wrap_processed_sphinx_packet(
// sphinx all together?
ProcessedPacketData::FinalHop {
destination,
identifier: _,
#[cfg(feature = "otel")]
identifier,
#[cfg(not(feature = "otel"))]
identifier: _,
payload,
} => process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
key_rotation,
),
} => {
// if we have a trace id in the destination, we log it for easier correlation later on
#[cfg(feature = "otel")]
let span = match identifier[0..12].try_into().map(|b: [u8; 12]| b) {
Ok(trace_bytes) if !trace_bytes.iter().all(|b| *b == 0) => {
let full_trace_id_bytes = decompress_trace_id(&trace_bytes);
let full_trace_id =
opentelemetry::trace::TraceId::from_bytes(full_trace_id_bytes);
let context_propagator =
ManualContextPropagator::new_from_tid("final_hop", full_trace_id);
tracing::info_span!(parent: &context_propagator.root_span, "final_hop_processing", trace_id=%full_trace_id)
}
_ => {
tracing::debug_span!("final_hop_processing")
}
};
#[cfg(feature = "otel")]
let _entered_span = span.enter();
process_final_hop(
destination,
payload.recover_plaintext()?,
packet_size,
packet_type,
key_rotation,
)
}
}?;
Ok(MixProcessingResult {
@@ -312,6 +344,7 @@ fn wrap_processed_outfox_packet(
}
}
#[instrument(skip_all)]
fn perform_final_processing(
packet: NymProcessedPacket,
packet_size: PacketSize,
+17 -13
View File
@@ -163,19 +163,6 @@ pub trait FragmentPreparer {
})
}
/// Tries to convert this [`Fragment`] into a [`SphinxPacket`] that can be sent through the Nym mix-network,
/// such that it contains required SURB-ACK and public component of the ephemeral key used to
/// derive the shared key.
/// Also all the data, apart from the said public component, is encrypted with an ephemeral shared key.
/// This method can fail if the provided network topology is invalid.
/// It returns total expected delay as well as the [`SphinxPacket`] (including first hop address)
/// to be sent through the network.
///
/// The procedure is as follows:
/// For each fragment:
/// - compute SURB_ACK
/// - generate (x, g^x)
/// - compute k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
/// - compute v_b = AES-128-CTR(k, serialized_fragment)
/// - compute vk_b = g^x || v_b
/// - compute sphinx_plaintext = SURB_ACK || g^x || v_b
@@ -189,6 +176,8 @@ pub trait FragmentPreparer {
packet_sender: &Recipient,
packet_recipient: &Recipient,
packet_type: PacketType,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<PreparedFragment, NymTopologyError> {
debug!("Preparing chunk for sending");
// each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient
@@ -249,6 +238,16 @@ pub trait FragmentPreparer {
topology.random_route_to_egress(&mut rng, destination)?
};
#[cfg(feature = "otel")]
let destination = packet_recipient.as_sphinx_destination(trace_id);
#[cfg(feature = "otel")]
tracing::info!(
"Packet destination with trace id: {:?}",
&destination.identifier
);
#[cfg(not(feature = "otel"))]
let destination = packet_recipient.as_sphinx_destination();
// including set of delays
@@ -274,6 +273,7 @@ pub trait FragmentPreparer {
)?,
};
// - compute k = KDF(remote encryption key ^ x) this is equivalent to KDF( dh(remote, x) )
// from the previously constructed route extract the first hop
let first_hop_address =
NymNodeRoutingAddress::try_from(route.first().unwrap().address).unwrap();
@@ -428,6 +428,8 @@ where
ack_key: &AckKey,
packet_recipient: &Recipient,
packet_type: PacketType,
#[cfg(feature = "otel")]
trace_id: Option<[u8; 12]>,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
@@ -439,6 +441,8 @@ where
&sender,
packet_recipient,
packet_type,
#[cfg(feature = "otel")]
trace_id,
)
}
+2
View File
@@ -10,6 +10,8 @@ repository = { workspace = true }
[dependencies]
sphinx-packet = { workspace = true, optional = true }
nym-outfox = { path = "../../../nym-outfox", optional = true }
# TODO add optional
nym-bin-common = { path = "../../../common/bin-common" }
thiserror = { workspace = true }
[features]
-2
View File
@@ -32,8 +32,6 @@ tracing.workspace = true
url.workspace = true
# TEMP
#nym-bin-common = { path = "../bin-common", features = ["basic_tracing"]}
[build-dependencies]
+1
View File
@@ -37,3 +37,4 @@ nym-validator-client = { path = "../client-libs/validator-client" }
[features]
default = []
otel = ["nym-sphinx/otel"]
+1
View File
@@ -119,6 +119,7 @@ where
let ClientInput {
connection_command_sender,
input_sender,
..
} = client_input;
let ClientOutput {
@@ -350,6 +350,8 @@ impl SocksClient {
self.config.connection_start_surbs,
TransmissionLane::ConnectionId(self.connection_id),
self.packet_type,
#[cfg(feature = "otel")]
None,
);
self.input_sender
.send(input_message)
@@ -373,6 +375,8 @@ impl SocksClient {
msg.into_bytes(),
TransmissionLane::ConnectionId(self.connection_id),
self.packet_type,
#[cfg(feature = "otel")]
None,
);
self.input_sender
.send(input_message)
@@ -439,6 +443,8 @@ impl SocksClient {
per_request_surbs,
lane,
packet_type,
#[cfg(feature = "otel")]
None,
)
} else {
InputMessage::new_regular(
@@ -446,6 +452,8 @@ impl SocksClient {
provider_message.into_bytes(),
lane,
packet_type,
#[cfg(feature = "otel")]
None,
)
}
})
+1
View File
@@ -29,6 +29,7 @@ x25519-dalek = { workspace = true, features = ["static_secrets"] }
cosmwasm-std = { workspace = true }
cosmrs = { workspace = true }
nym-bin-common = { path = "../../common/bin-common" }
nym-validator-client = { path = "../../common/client-libs/validator-client" }
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
+2 -4
View File
@@ -160,13 +160,12 @@ pub async fn setup_gateway_from_api(
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = gateways_for_init(
&mut rng,
nym_apis,
None,
minimum_performance,
ignore_epoch_roles,
None,
)
.await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
@@ -178,13 +177,12 @@ pub async fn current_gateways_wasm(
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
let mut rng = thread_rng();
gateways_for_init(
&mut rng,
nym_apis,
user_agent,
minimum_performance,
ignore_epoch_roles,
None,
)
.await
}
+3 -1
View File
@@ -255,10 +255,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
crate::queued_migrations::introduce_historical_epochs(deps, env)?;
Ok(Response::new())
}
@@ -9,6 +9,7 @@ use crate::epoch_state::storage::{load_current_epoch, save_epoch};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::storage::STATE;
use crate::verification_key_shares::storage::vk_shares;
use crate::Dealer;
use cosmwasm_std::{Deps, DepsMut, Env, Event, MessageInfo, Response};
use nym_coconut_dkg_common::dealer::{DealerRegistrationDetails, OwnershipTransfer};
@@ -161,6 +162,14 @@ pub fn try_update_announce_address(
details.announce_address = new_address.clone();
EPOCH_DEALERS_MAP.save(deps.storage, (epoch.epoch_id, &info.sender), &details)?;
let mut contract_share = vk_shares().load(deps.storage, (&info.sender, epoch.epoch_id))?;
contract_share.announce_address = new_address.clone();
vk_shares().save(
deps.storage,
(&info.sender, epoch.epoch_id),
&contract_share,
)?;
Ok(Response::new().add_event(
Event::new("dkg-announce-address-update")
.add_attribute("dealer", info.sender)
@@ -228,9 +237,14 @@ pub(crate) mod tests {
#[cfg(feature = "testable-dkg-contract")]
mod tests_with_mock {
use super::*;
use crate::testable_dkg_contract::{init_contract_tester, DkgContractTesterExt};
use crate::testable_dkg_contract::{
init_contract_tester, init_contract_tester_with_group_members, DkgContractTesterExt,
};
use anyhow::Context;
use cosmwasm_std::testing::message_info;
use nym_contracts_common_testing::ContractOpts;
use nym_coconut_dkg_common::msg::QueryMsg;
use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse;
use nym_contracts_common_testing::{ChainOpts, ContractOpts};
#[test]
fn transferring_ownership() -> anyhow::Result<()> {
@@ -436,9 +450,91 @@ mod tests_with_mock {
assert_eq!(old_details1, new_details1);
assert_eq!(old_details2, new_details2);
// most recent entry is updated
// most recent entry is updated
assert_eq!(new_details3.announce_address, new_address);
Ok(())
}
#[test]
fn updating_announce_address_updates_vk_shares() -> anyhow::Result<()> {
let mut contract = init_contract_tester_with_group_members(3);
let group_member = contract.random_group_member();
contract.run_initial_dummy_dkg(); // => epoch 0
contract.run_reset_dkg(); // => epoch 1
// LEAVE DKG MEMBERSHIP
contract.remove_group_member(group_member.clone());
contract.run_reset_dkg(); // => epoch 2
// COME BACK
contract.add_group_member(group_member.clone());
contract.run_reset_dkg(); // => epoch 3
let old_address = EPOCH_DEALERS_MAP
.load(&contract, (3, &group_member))?
.announce_address;
let old_share0 = vk_shares().load(&contract, (&group_member, 0))?;
let old_share1 = vk_shares().load(&contract, (&group_member, 1))?;
let old_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
assert!(old_share2.is_none());
let old_share3 = vk_shares().may_load(&contract, (&group_member, 3))?;
assert!(old_share3.is_some());
let new_address = "https://new-address.com".to_string();
try_update_announce_address(
contract.deps_mut(),
message_info(&group_member, &[]),
new_address.clone(),
)?;
let new_share0 = vk_shares().load(&contract, (&group_member, 0))?;
let new_share1 = vk_shares().load(&contract, (&group_member, 1))?;
let new_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
assert!(new_share2.is_none());
let new_share3 = vk_shares().load(&contract, (&group_member, 3))?;
// old epoch data is unchanged
assert_eq!(old_share0, new_share0);
assert_eq!(old_share1, new_share1);
assert_eq!(old_share2, new_share2);
// most recent entry is updated
assert_eq!(new_share3.announce_address, new_address);
// finally an integration check against query endpoint
let epoch0_shares: PagedVKSharesResponse =
contract.query(&QueryMsg::GetVerificationKeys {
epoch_id: 0,
limit: None,
start_after: None,
})?;
assert_eq!(epoch0_shares.shares.len(), 3);
let member_share = epoch0_shares
.shares
.iter()
.find(|s| s.owner == group_member)
.context("failed to find member's share")?;
assert_eq!(member_share.announce_address, old_address);
let epoch0_shares: PagedVKSharesResponse =
contract.query(&QueryMsg::GetVerificationKeys {
epoch_id: 3,
limit: None,
start_after: None,
})?;
assert_eq!(epoch0_shares.shares.len(), 3);
let member_share = epoch0_shares
.shares
.iter()
.find(|s| s.owner == group_member)
.context("failed to find member's share")?;
assert_eq!(member_share.announce_address, new_address);
Ok(())
}
}
@@ -1,2 +1,21 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::HISTORICAL_EPOCH;
use crate::error::ContractError;
use cosmwasm_std::{DepsMut, Env};
pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> {
if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() {
return Err(ContractError::FailedMigration {
comment: "this migration has already been run before".to_string(),
});
}
#[allow(deprecated)]
let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?;
// we won't have information on intermediate states prior to now, but that's not the end of the world
HISTORICAL_EPOCH.save(deps.storage, &current, env.block.height)?;
Ok(())
}
@@ -62,12 +62,18 @@ impl TestableNymContract for DkgContract {
where
Self: Sized,
{
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
init_contract_tester()
}
}
pub fn init_contract_tester() -> ContractTester<DkgContract> {
DkgContract::init().with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
}
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
prepare_contract_tester_builder_with_group_members(members)
.build()
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
}
pub fn prepare_contract_tester_builder_with_group_members<C>(
@@ -137,12 +143,6 @@ where
builder
}
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
prepare_contract_tester_builder_with_group_members(members)
.build()
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
}
pub trait DkgContractTesterExt:
ContractOpts<ExecuteMsg = ExecuteMsg, QueryMsg = QueryMsg, ContractError = ContractError>
+ ChainOpts
-12
View File
@@ -1,12 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_mixnode_bonding() {
todo!()
}
}
-4
View File
@@ -2,7 +2,3 @@
// SPDX-License-Identifier: Apache-2.0
pub(crate) mod transactions;
// the purpose of that module is to keep track of tests of legacy features that will eventually be phased out
// such as standalone mixnode/gateway bonding
pub(crate) mod legacy;
@@ -11,6 +11,7 @@ import { Alert, AlertTitle } from '@mui/material';
import { Wallet } from '@cosmos-kit/core';
import { CosmosKitLedger } from './ledger';
import { CosmosKitSign } from './sign';
import type { SignerOptions } from '@cosmos-kit/core';
const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
const assetsFixedUp = React.useMemo(() => {
@@ -40,14 +41,16 @@ const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
return chains;
}, [chains]);
// components/cosmos-kit/index.tsx
return (
<ChainProvider
chains={chainsFixedUp}
assetLists={assetsFixedUp}
wallets={[...ledger, ...keplr]}
signerOptions={{
preferredSignType: () => 'amino',
}}
signerOptions={{ preferredSignType: () => 'amino' }}
throwErrors={false}
>
{children}
</ChainProvider>
@@ -0,0 +1,141 @@
import { Steps } from 'nextra/components';
import { Callout } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
<Callout type="info" emoji="️">
**QUIC bridge is a requirement for all nodes which enable Wireguard functionality. Note that it this feature is compatible with nodes from `v1.18.0` (platform release [`v2025.17-isabirra`](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)) and newer!**
</ Callout>
Nym Network uses various [transport bridges](https://github.com/nymtech/nym-bridges/blob/main/README.md) for routing the packets. Right now operators need to configure [our implementation](https://github.com/nymtech/nym-bridges/tree/main/nym-bridge) of general-purpose transport layer network protocol called [QUIC](https://en.wikipedia.org/wiki/QUIC).
Operators can use [Nym Bridge Configuration Tool](https://github.com/nymtech/nym-bridges/tree/main/bridge-cfg) and compile the [Bridge Runner binary](https://github.com/nymtech/nym-bridges/tree/main/nym-bridge) themselves.
**We recommend a more convenient QUIC bridge deployment using a script [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh), following the steps below.**
<Steps>
###### 1. Download [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh) script
- SSH to your server
- Download the script and make executable
```sh
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh && \
chmod +x quic_bridge_deployment.sh
```
###### 2. Run the script with `full_bridge_setup` command
- Optional: open `tmux` in case you will need to run another commands on the VPS
- Run the script with a command `full_bridge_setup`
```sh
./nym-node-setup/quic_bridge_deployment.sh full_bridge_setup
```
###### 3. Follow the interactive prompts
- When you are asked for bridge binary URL, look here for one to match your system: [builds.ci.nymte.ch/QUIC](https://builds.ci.nymte.ch/QUIC/)
- When you are asked to enter forward address, it's your IPv4 used for bonding the node, alongside port `:51822` (an example: `172.232.238.161:51822`)
- To find out your IP address you can always run:
- IPv4: `curl -4 https://ifconfig.co/ip`
- IPv6: `curl -6 https://ifconfig.co/ip`
- **For all prompts with default options, we highly recommend to stick to default (press enter)**
###### 4. Restart the node service
- When done with the deployment, please restart your node systemd service
```sh
service nym-node restart && journalctl -u nym-node.service -f --all
```
</Steps>
Congratulation, you deployed QUIC transport bridge! The script offers a standalone tweaks and checks, you can always run it without any argument to see all the options:
```sh
./quic_bridge_deployment.sh
```
<br/>
<AccordionTemplate name="Command output">
```shell
root@localhost:~# ./quic_bridge_deployment.sh
iptables-persistent is already installed.
Usage: ./quic_bridge_deployment.sh [command] [options]
Nym QUIC Bridge Deployment Helper Script
Bridge Installation & Configuration:
check_bridge_installation - Check bridge installation status
show_bridge_config - Display bridge configuration files
show_bridge_keys - Display bridge key information
show_bridge_info - Show comprehensive bridge information
verify_bridge_prerequisites - Verify all prerequisites are met
Bridge Setup Commands:
install_bridge_binary - Download and install nym-bridge binary
generate_bridge_keys - Generate ED25519 bridge identity keys
create_client_params - Create client_bridge_params.json
create_bridge_config - Create bridges.toml configuration
create_bridge_service - Create systemd service file
full_bridge_setup - Interactive full bridge setup wizard
Network Configuration Commands:
adjust_ip_forwarding - Enable IPv4 and IPv6 forwarding
apply_bridge_iptables_rules - Apply iptables rules for QUIC bridge (nymwg)
configure_dns_and_icmp - Allow ICMP ping tests and configure DNS
remove_duplicate_bridge_rules - Remove duplicate iptables rules for nymwg
Network Inspection Commands:
fetch_and_display_ipv6 - Show IPv6 on default network device
fetch_wg_ipv6_address - Fetch IPv6 for nymwg interface
check_bridge_iptables - Check iptables rules for nymwg
check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding status
check_ip_routing - Display IP routing tables
Testing Commands:
perform_pings - Test IPv4 and IPv6 connectivity
test_bridge_connectivity - Comprehensive bridge connectivity test
Service Management Commands:
check_bridge_service_status - Check nym-bridge and nym-node service status
show_bridge_logs [lines] - Show recent nym-bridge logs (default: 50 lines)
Quick Start:
1. Run 'verify_bridge_prerequisites' to check prerequisites
2. Run 'check_bridge_installation' to verify installation
3. Run 'test_bridge_connectivity' to test connectivity
```
</AccordionTemplate>
### Fixing Metadata Port Showing Not Open in Probe Results
If you have followed the steps outlined above, but the metadata port is not shown as open in either the Node Status API's probe results or an explorer that gets its data from the API, see below:
<Steps>
###### 1.
Ensure that in your `config.toml` file, this value is set to the default one - any other value here will cause the metadata endpoint to fail:
```
# Private IP address of the wireguard gateway.
# default: '10.1.0.1'
private_ipv4 = '10.1.0.1'
```
Then restart your node.
###### 2.
Run this command if not already done:
```
ufw allow in on nymwg to any port 51830 proto tcp
```
Check if the port is open with:
```
iptables -S | grep 51830
```
Then ensure the metadata endpoint is listening from the correct address with:
```
netstat -an | egrep LISTEN | egrep "51830"
```
###### 3.
Once the Node Status API has run a probe on your node, the probe results will reflect this - `can_query_metadata_v4` will have `true` as a value.
The quickest way to check this is by using the [NymVPN API](https://nymvpn.com/api/public/v1/directory/gateways?show_vpn_only=true) and checking the same field:
![](/images/operators/wg-metadata-port-api.png)
</Steps>
@@ -1 +1 @@
Sunday, October 5th 2025, 09:29:02 UTC
Tuesday, October 14th 2025, 11:34:14 UTC
+6 -3
View File
@@ -24,11 +24,11 @@
"@cosmjs/encoding": "^0.32.2",
"@cosmjs/proto-signing": "^0.32.2",
"@cosmjs/stargate": "^0.32.2",
"@cosmos-kit/core": "^2.8.9",
"@cosmos-kit/core": "^2.16.3",
"@cosmos-kit/keplr": "^2.6.9",
"@cosmos-kit/keplr-extension": "^2.7.9",
"@cosmos-kit/ledger": "^2.6.9",
"@cosmos-kit/react": "^2.10.11",
"@cosmos-kit/react": "^2.22.3",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@interchain-ui/react": "^1.8.0",
@@ -54,12 +54,15 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@next/swc-linux-x64-gnu": "15.5.0",
"@types/node": "18.11.10",
"@types/react": "^18.3.26",
"@types/react-dom": "^18.3.7",
"copy-webpack-plugin": "^11.0.0",
"eslint": "8.46.0",
"eslint-config-next": "13.4.13",
"raw-loader": "^4.0.2",
"typescript": "^4.9.3"
"typescript": "^5.9.3"
},
"private": false
}
+343 -25
View File
@@ -7,6 +7,7 @@ import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
export const TestingSteps = () => (
@@ -48,6 +49,323 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.18-jarlsberg`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.18-jarlsberg)
- [`nym-node`](nodes/nym-node.mdx) version `1.19.0`
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-10-15T09:04:32.043934599Z
Build Version: 1.19.0
Commit SHA: 2235a6e1477bea7368ee5443a298f544deb63504
Commit Date: 2025-10-15T10:22:16.000000000+02:00
Commit Branch: master
rustc Version: 1.92.0-nightly
rustc Channel: nightly
cargo Profile: release
```
### API Changes
There have been a few updates to the Node Status API (used by the NymVPN API) to do with Nodes' metadata endpoints, which are used to determine if they are running a QUIC bridge.
- [Node Status API: add bridge information to dVPN endpoint](https://github.com/nymtech/nym/pull/6069)
Scrape the `/api/v1/bridges/client-params` endpoint from nodes to get bridge information and add to the dVPN output:
```
{
"identity_key": "3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ",
"name": "middle winner wing",
"authenticator": {
"address": "6CQMtm9DqUj7mPVkSD9YarjUuPh7mJaZQnnHWxNpgByh.AGXiTivVieBULeDhL9tuyMKgRydoT67sFCjeoERDN84k@3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ"
},
"ip_packet_router": {
"address": "GA47h8294m7f6ciyFuDkjk3mmqrvALqboL2o22jkqFhi.22SdTGBWKFrrBM31hMgzjmgduSH1nosnbE9dgNcY2CXz@3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ"
},
"location": {
"two_letter_iso_country_code": "GB",
"latitude": 51.5085,
"longitude": -0.1257
},
"last_probe": {
"last_updated_utc": "2025-09-02T18:19:10Z",
"outcome": {
"as_entry": {
"can_connect": true,
"can_route": true
},
"as_exit": {
"can_connect": true,
"can_route_ip_external_v4": true,
"can_route_ip_external_v6": true,
"can_route_ip_v4": true,
"can_route_ip_v6": true
},
"wg": {
"can_handshake_v4": true,
"can_handshake_v6": true,
"can_register": true,
"can_resolve_dns_v4": true,
"can_resolve_dns_v6": true,
"download_duration_sec_v4": 0,
"download_duration_sec_v6": 5,
"download_error_v4": "",
"download_error_v6": "",
"downloaded_file_v4": "https://proof.ovh.net/files/1Mb.dat",
"downloaded_file_v6": "https://proof.ovh.net/files/10Mb.dat",
"ping_hosts_performance_v4": 1,
"ping_hosts_performance_v6": 1,
"ping_ips_performance_v4": 1,
"ping_ips_performance_v6": 0.6666667,
"can_handshake": true,
"can_resolve_dns": true,
"ping_hosts_performance": 1,
"ping_ips_performance": 1
}
}
},
"ip_addresses": [
"178.79.168.250",
"2a01:7e00::f03c:95ff:fef8:77f"
],
"mix_port": 1789,
"role": "EntryGateway",
"entry": {
"hostname": "nym-circ.anonym.tech",
"ws_port": 9000,
"wss_port": 9443
},
+ "bridges":{
+ "version": 0,
+ "transports": [
+ {
+ "transport_type": "quic_plain",
+ "args": {
+ "addresses": ["[2a01:7e00::f03c:95ff:fef8:77f]:4443", "178.79.168.250:4443"],
+ "id_pubkey": "gyKl6DN9hgdPGhEzdf9gY4Ha2GzrOwSzLCguxeTVTJU=",
+ "host": "netdna.bootstrapcdn.com"
+ }
+ }
+ ]
+ }
"performance": "1",
"build_information": {
"build_version": "1.16.0",
"commit_branch": "build",
"commit_sha": "7f97f13799342f864e1b106e8cafc9f6d6c24c0f"
}
}
```
- [ns-api: add new fields for probe output for query_metadata and download file size and duration in ms](https://github.com/nymtech/nym/pull/6091)
This PR adds new fields to the Node Status API:
```json
{
"node": "ByxGq9hpDQu6Wc8augEh22w7CRWJHPNfDshB1b8nfWkh",
"used_entry": "ByxGq9hpDQu6Wc8augEh22w7CRWJHPNfDshB1b8nfWkh",
"outcome": {
"as_entry": {
"can_connect": true,
"can_route": true
},
"as_exit": {
"can_connect": true,
"can_route_ip_v4": true,
"can_route_ip_external_v4": true,
"can_route_ip_v6": true,
"can_route_ip_external_v6": true
},
"wg": {
"can_register": true,
"can_query_metadata_v4": true, // <--------------------------------
"can_handshake_v4": true,
"can_resolve_dns_v4": true,
"ping_hosts_performance_v4": 1.0,
"ping_ips_performance_v4": 1.0,
"can_handshake_v6": true,
"can_resolve_dns_v6": true,
"ping_hosts_performance_v6": 1.0,
"ping_ips_performance_v6": 0.93333334,
"download_duration_sec_v4": 2,
"download_duration_milliseconds_v4": 2034, // <--------------------------------
"downloaded_file_size_bytes_v4": 1048576, // <--------------------------------
"downloaded_file_v4": "https://nym-bandwidth-monitoring.ops-d86.workers.dev/1mb.dat",
"download_error_v4": "",
"download_duration_sec_v6": 5,
"downloaded_file_size_bytes_v6": 1048576,
"download_duration_milliseconds_v6": 5501,
"downloaded_file_v6": "https://proof.ovh.net/files/1Mb.dat",
"download_error_v6": ""
}
}
}
```
- [ns-api: add descriptions to dVPN gateway responses](https://github.com/nymtech/nym/pull/6102)
This PR adds the `description` field to dVPN gateways in `/dvpn/v1/directory/gateways`.
- [NS API: use new probe download filesize and milliseconds field](https://github.com/nymtech/nym/pull/6097)
This PR uses the new fields in mainnet to calculate the probe download score.
- [ns-api: use download files size from probes instead of parsing filenames](https://github.com/nymtech/nym/pull/6095) This PR uses the new field in the probe results that says how many bytes were downloaded to calculate the speed of download. It only uses downloads on ipv4 and ignores ipv6 for now. This might change in the future.
- [Node Status API: remove sqlite support](https://github.com/nymtech/nym/pull/6004)
This PR removes sqlite support, requiring pgsql to run the NS API.
It also fixes the following issues:
- deserialisation of `NodeDescription`
- defaults for `WireguardDetails` for deserialisation
It also bumps the version to v4.0.0.
### Operators Updates & Tools
- [Node rewards tracker](https://github.com/nymtech/nym/pull/6064)
This PR introduces a script fetching operators rewards based on provided Nyx account addresses provided in `data/wallet-addresses.csv`.
<AccordionTemplate name="Info">
**Output is:**
1. Printed table in terminal
3. Sheet with complete info stored in `data/node-balances.csv`
4. Historical data yaml file stored in `data/data.yaml` - this file should not be changed manually, as
all values older than 30 days get auto-removed
**RUN**
Before you start fill first column of `data/wallet-addresses.csv` called `addresses` with your Nyx account addresses and (optionally) second column called `tag` with an entity, for example *"mysquad"* and *"personal"* to get sorted output per entity.
- Csv example with `tag`s:
```
n1foofoofoo, personal
n1barbarbar, personal
n1bazbazbaz, mysquad
n1lollollol, mysquad
```
- For operators having all nodes under one entity, the tag field will be left empty. Example:
```csv
n1foofoofoo
n1barbarbar
n1bazbazbaz
```
Documentation coming soon.
</AccordionTemplate>
- [Bugfix/bloomfilters purge](https://github.com/nymtech/nym/pull/6089)
This PR fixes bug where old replay protection bloomfilters were never getting removed.
### Features
- [Get wireguard keypair as arg instead of reading it from disk](https://github.com/nymtech/nym/pull/6078)
- [Registration Client](https://github.com/nymtech/nym/pull/6059)
This PR introduces the `RegistrationClient` whose eventual job will be to handle registration with gateway and bandwidth control. This is step 1, where it only handles registration and then hands back the control channel to the vpn-client.
<AccordionTemplate name="Info">
**nym-wg-gateway-client**
This crate has been smooshed with the nym-authenticator-client as they were doing the same thing : talking with the Authenticator.
**nym-authenticator-client**
The job of the `AuthenticatorClient` is to talk to the `Authenticator`s via the mixnet. They both make use of a `AuthClientMixnetListener` that handles interaction with the mixnet client. No more `SharedMixnetClient`, only clear owners. That component could be turned into an actual multiplexer, but that's out of scope.
It is designed to be able to shut down, since it won't be necessary for bandwidth top up in the future.
Lots of types and traits were copied in both repos, some of them are sadly still there. Further work could be done to improve messaging ( `ClientMessage` and `AuthenticatorRequests` for example)
**nym-ip-packet-client**
This crate has minor changes, focused on getting rid of the `SharedMixnetClient`. It still talks to the `IpPacketRouter` but it owns the `MixnetClient`
**Nym-registration-client**
Brand new crates, whose current job is to run a `MixnetClient` with the given options, register with the component related to the tunnel type, and hand back the necessary component for running the tunnel.
**authenticator-requests**
Mostly refactoring, lots of code was duplicated in the vpn-client repo
**misc**
The rest are qol changes that might not be needed right away but that is preparing the future improvements coming soon™
</AccordionTemplate>
- [Feature: Ping probe all nodes /described nodes from a server](https://github.com/nymtech/nym/pull/6074)
This script should be ran from a node hosting server. It pings all IPs listed in /described endpoint and returns a file with unreachable IPs. Such list gives operator an idea on IPs potentially blocking their IP.
- [Feature: Nym node html landing page](https://github.com/nymtech/nym/pull/6053)
This PR introduces a new landing page which contains:
- no more deprecated tornul
- new nym theme
- bold text about DMCA
- hook for nym-node-cli to use it and add $EMAIL prompted to the operator
- [feat: DKG contract method for updating announce address](https://github.com/nymtech/nym/pull/6050)
- [feat: NS ticket faucet](https://github.com/nymtech/nym/pull/6047)
Overview: modifies the Node Status API so that it keeps a buffer of tickets inside its storage that it gives out when new test runs get requested. it also slightly adjusts the ticketbook API in a bit hacky way to allow importing ticketbooks with specific index ranges. However, those changes also involve modifying cli arguments passed to both NS API and gateway probes. The associated vpn-client repo branch is `feature/ticket-faucet-probe` which for the same reason is not yet ready
<AccordionTemplate name="Info">
**Node Status API**
**Added**
- `--config-env-file` / `-c` (optional) - helper allowing testing locally on non-mainnet networks without passing everything through env variables
- `--mnemonic` (env: `NYM_NODE_STATUS_API_MNEMONIC`) - account used for obtaining ticketbooks
- `--max-concurrent-deposits` (env: `NYM_NODE_STATUS_API_MAX_CONCURRENT_DEPOSITS`) (optional; default: 5) - Specifies the maximum number of deposits the node status api can make in a single transaction. Note that each deposit batch is followed by the same number of sequential signing requests
- `--tickets-buffer-size` (env: `NYM_NODE_STATUS_API_TICKETS_BUFFER`) (optional; default: 50) - Specifies the size of the tickets buffer the node status api should have available at any time for each ticket type.
- `--tickets-buffer-check-interval` (env: `NYM_NODE_STATUS_API_TICKETS_CHECK_INTERVAL`) (optional; default: 1min) - Specifies interval at which the node status api should check if it has sufficient number of tickets buffered
- `--quorum-check-interval` (env: `NYM_NODE_STATUS_API_QUORUM_CHECK_INTERVAL`) (optional; default: 5min) - Specifies interval at which the node status api should check if signing quorum is available
- `--buffered-ticket-types` (env: `NYM_NODE_STATUS_BUFFERED_TICKET_TYPES`) (optional; default: `[V1MixnetEntry, V1WireguardEntry, V1WireguardExit]`) - Specifies types of tickets to buffer
- `--ecash-client-identifier-bs58` (env: `NYM_NODE_STATUS_API_ECASH_CLIENT_IDENTIFIER_BS58`) - Identifier used for deriving keys embedded in the issued ticketbooks (i.e. seed for the client identity). It can be a random string, but make sure it has sufficient entropy. it has to be base58 encoded.
**Node Status Agent**
**Removed**
- `--mnemonic` - no longer needed as tickets are obtained throught the faucet
**Gateway Probe (vpn-client repo)**
**Added**
- `--ticket-materials` - all the encoded generated tickets (and global data) needed by the probe
- `--ticket-materials-revision` - revision of the serialisation to help with decoding (not strictly needed, but it was already available)
**Removed**
- `--mnemonic` - no longer needed as tickets are obtained throught the faucet
</AccordionTemplate>
- [Bridge proto client params in Self-Described](https://github.com/nymtech/nym/pull/6035)
This PR gives the nym-node a way to expose information about the bridge protocols that the node supports, and the parameters that are necessary to connect using those protocols.
<AccordionTemplate name="Info">
This is meant to be usable by the node status API to be be included into node descriptors that are compiled for the vpn client.
- Adds a new field to the nym-node config `gateway_tasks.storage_paths.bridge_client_params`
- IF the new config field is present a new self-described endpoint is available at `/v1/bridges/client-params`
- IF the new config field is NOT present the endpoint is not exposed.
I arbitrarily chose config v8 as the oldest nym-node configuration version that supports the option. This can probably be propogated further backwards if necessary.
NOTE: The new `/bridges/client-params` endpoint does not have swagger / utopia docs associated. This interface will likely change in several upcoming iterations and serving from file (for now) means that the types are not defined internally.
tested as working on node `3wqfp9eb` both when file is provided in config (sucessful response) and when file is not specified in config (path gives 404).
</AccordionTemplate>
### Refactors & Maintenance
- [[chore] Clippy fix](https://github.com/nymtech/nym/pull/6060)
- [Bugfix: Nym node CLI download nym-node exception](https://github.com/nymtech/nym/pull/6058)
This PR fixes a case when the "Latest" platform release doesn't include `nym-node` by prompting user to insert binary URL instead of failing. Additionally it fixes fetching new landing page script in the CLI.
- [Benny/ci contract fix](https://github.com/nymtech/nym/pull/5962)
- [frontdoor typo fix](https://github.com/nymtech/nym/pull/6067)
- [Hotfix: Update API source in node ping tester script](https://github.com/nymtech/nym/pull/6082)
This PR fixes initial development bug where a wrong API endpoint was used.
`https://validator.nymtech.net/api/v1/nym-nodes/described` gets all nym nodes, not just gateways.
Code is simplified accordingly.
## QUIC Transport Bridge Deployment
<QuicDeploymentSteps />
## `v2025.17-isabirra`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)
@@ -103,7 +421,7 @@ All of the routes removed had already been deprecated over a year ago. This is m
<AccordionTemplate name="Removed API routes">
### Legacy mixnodes related:
- `/v1/mixnodes`
- `/v1/mixnodes/active`
- `/v1/mixnodes/active/detailed`
@@ -125,9 +443,9 @@ All of the routes removed had already been deprecated over a year ago. This is m
- `/v1/status/mixnodes/detailed-unfiltered`
- `/v1/status/mixnode/{mix_id}/report`
- `/v1/status/mixnode/{mix_id}/avg_uptime`
### Legacy gateways related:
- `/v1/gateways`
- `/v1/gateways/described`
- `/v1/gateways/blacklisted`
@@ -139,21 +457,21 @@ All of the routes removed had already been deprecated over a year ago. This is m
</AccordionTemplate>
#### Structs changes:
- `MixnodeUptimeHistoryResponse` no longer has `owner` field
- `GatewayUptimeHistoryResponse` no longer has `owner` field
#### New Routes Added
- `/v1/nym-nodes/stake-saturation/{node_id}` - as a better replacement for `/v1/status/mixnode/{mix_id}/stake-saturation` as this information might be potentially useful and can be applied to any nym-node, not just a legacy mixnode.
- `/v1/legacy/mixnodes` - returns a list of bonded legacy mixnodes that haven't migrated to nym-nodes
- `/v1/legacy/gateways` - returns a list of bonded legacy gateways that haven't migrated to nym-nodes
#### Node Status API
Furthermore the changes remove all scraping of legacy mixnodes from NS and the following routes are removed:
- `/v2/mixnodes/{mix_id}`
- `/v2/mixnodes`
@@ -224,7 +542,7 @@ Furthermore the changes remove all scraping of legacy mixnodes from NS and the f
### Operators Updates & Tools
<Callout type="info" emoji="️">
Nodes receiving stake as a part of [**Nym Delegation Program**](https://nym.com/network/DP) are updated weekly based on the [rules](https://forms.nym.com/form/#/2/form/view/BRh8QroXFinjOF4D3FHgYiX76zbiRvUV2Sy+czaoKFQ) without prior notification given to the operators.
Nodes receiving stake as a part of [**Nym Delegation Program**](https://nym.com/network/DP) are updated weekly based on the [rules](https://forms.nym.com/form/#/2/form/view/BRh8QroXFinjOF4D3FHgYiX76zbiRvUV2Sy+czaoKFQ) without prior notification given to the operators.
[**Nym Delegation account**](https://explorer.nym.spectredao.net/account/n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw) `n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw` is a single source of truth. If you expect your node to have Nym team stake and it doesn't, please reach out in in the [**Node Operators Matrix channel**](https://matrix.to/#/#operators:nymtech.chat).
</Callout>
@@ -247,32 +565,32 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
- [Feature/testing utils](https://github.com/nymtech/nym/pull/5963): This PR introduces a couple of general helpers, in particular some mocks for sending across values using Stream/Sink and AsyncRead/AsyncWrite without actual underlying networking. Example implementation are with NymNoise (which was the original inspiration) and gateway handshake.
- [Backport metadata endpoint](https://github.com/nymtech/nym/pull/6010)
- [Backport metadata endpoint](https://github.com/nymtech/nym/pull/6010)
### Bugfix
- [Fix rust `1.89` `clippy` issues](https://github.com/nymtech/nym/pull/5944)
- [`http api` client adjustment](https://github.com/nymtech/nym/pull/5953): It fixes missing `feature-lock` when cloning the client and adds helper macro for user agent creation
- [`http api` client adjustment](https://github.com/nymtech/nym/pull/5953): It fixes missing `feature-lock` when cloning the client and adds helper macro for user agent creation
- [Fix `ci-build` for linux (and use updated runner)](https://github.com/nymtech/nym/pull/5958): This PR fixes our build pipeline by using correct (updated) linux runner and updates all the conditional steps that were behind `ubuntu` runners (which no longer exist)
- [Fixing the ci for ns agent](https://github.com/nymtech/nym/pull/5965)
- [Fixing the ci for ns agent](https://github.com/nymtech/nym/pull/5965)
- [Manually calculate per node work on rewarded set changes](https://github.com/nymtech/nym/pull/5972): This PR fixes:
1. Nym rewarded set was set to X, for argument sake say 200
1. Nym rewarded set was set to X, for argument sake say 200
2. We sent transaction to update it to Y, say 100
3. This internally updated the interval rewarding parameters inside the mixnet contract including the default active and standby node work factors. Note that the rewarded set itself stayed the same, as it only changes after epoch rolls over and new one is assigned (by the `nym-api`)
4. Epoch has finished and `nym-api` wanted to do the rewarding. It grabbed the **current** rewarded set (of X, 200) and started calculating the total work in the system. But since the contract already had new parameters (adjusted for size of Y, 100), the result was greater than 1 thus `nym-api` was preventably blowing up.
To fix it we introduce additional checks, so that if the current rewarded set does not match the specification defined in the contract rewarding parameters, `nym-api` will attempt to do its best to manually calculate work factors for this epoch.
- [Fix the ns api ci workflow](https://github.com/nymtech/nym/pull/5981)
- [Fix the ns api ci workflow](https://github.com/nymtech/nym/pull/5981)
- [Make sure tables are removed in correct order to not trigger FK constraint issue](https://github.com/nymtech/nym/pull/5987)
- [Make sure tables are removed in correct order to not trigger FK constraint issue](https://github.com/nymtech/nym/pull/5987)
### Refactors & Maintenance
- [Move credential verifier in peer controller](https://github.com/nymtech/nym/pull/5938): This PR is to not duplicate the verifier code (minus the actual verification operation, which is harder to unit test because of expiration checks)
- [Move credential verifier in peer controller](https://github.com/nymtech/nym/pull/5938): This PR is to not duplicate the verifier code (minus the actual verification operation, which is harder to unit test because of expiration checks)
- [Remove unused import](https://github.com/nymtech/nym/pull/5942)
@@ -283,7 +601,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
- [Remove freshness check on testrun submit](https://github.com/nymtech/nym/pull/5977):
- Freshness is enforced by a background task that marks test runs as stale after a configured amount of time
- Make existing freshness period configurable to avoid code changes in the future
- Added `humantime` for parsing
- Added `humantime` for parsing
- [Move authenticator into gateway crate](https://github.com/nymtech/nym/pull/5982)
@@ -306,7 +624,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
- [Ecash liveness check](https://github.com/nymtech/nym/pull/5890)
- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending
- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending
- [`nym-node` debug command to reset providers db](https://github.com/nymtech/nym/pull/5914)
@@ -316,7 +634,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
### Refactors & Maintenance
- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots`
- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots`
- [Migrate strum to `0.27.2`](https://github.com/nymtech/nym/pull/5960): This PR migrates strum to the latest. Notably all macros' were moved into `strum_macros`. The rest stays the same.
@@ -341,9 +659,9 @@ cargo Profile: release
### Operators Updates & Tools
- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services.
- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services.
- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content).
- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content).
### Features
@@ -357,13 +675,13 @@ cargo Profile: release
- [`sqlx-pool-guard`: allocate more memory on windows](https://github.com/nymtech/nym/pull/5896):
- Allocate 1.5x more memory than reported by the system to provide a safety margin
- Increase number of retry attempts to 5
- [dkg epoch dealers query](https://github.com/nymtech/nym/pull/5899)
- [dkg snapshot epoch](https://github.com/nymtech/nym/pull/5900): In order to determine if signer quorum has been down at particular height, we need to know with certainty the dkg epoch id corresponding to given block height. This PR makes it possible. Every time epoch state is changed (due to DKG progress), snapshot is saved and can be queried. This doesn't work for past data, but given mainnet has only had a single DKG instance, that's not an issue.
- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905):
- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905):
### Refactors & Maintenance
@@ -419,7 +737,7 @@ cargo Profile: release
- [Remove old explorer references](https://github.com/nymtech/nym/pull/5846)
- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet
- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet
### Bugfixes
@@ -8,6 +8,7 @@ import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
export const ManagerIPOutput = () => (
@@ -534,6 +535,9 @@ You can validate the application of the IP tables routes on your `nym-node` by c
Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet).
## QUIC Transport Bridge Deployment
<QuicDeploymentSteps />
## Running `nym-node` as a non-root
@@ -21,13 +21,13 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-10-01T10:42:58.647419869Z
Build Version: 1.18.0
Commit SHA: bbea2ff9e913f49cb7bf6c7bafa9d9b158c80de5
Commit Date: 2025-10-01T12:06:07.000000000+02:00
Commit Branch: HEAD
rustc Version: 1.88.0
rustc Channel: stable
Build Timestamp: 2025-10-15T09:04:32.043934599Z
Build Version: 1.19.0
Commit SHA: 2235a6e1477bea7368ee5443a298f544deb63504
Commit Date: 2025-10-15T10:22:16.000000000+02:00
Commit Branch: master
rustc Version: 1.92.0-nightly
rustc Channel: nightly
cargo Profile: release
```
+208 -152
View File
@@ -36,11 +36,11 @@ importers:
specifier: ^0.32.2
version: 0.32.4
'@cosmos-kit/core':
specifier: ^2.8.9
specifier: ^2.16.3
version: 2.16.3
'@cosmos-kit/keplr':
specifier: ^2.6.9
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)
'@cosmos-kit/keplr-extension':
specifier: ^2.7.9
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
@@ -48,38 +48,41 @@ importers:
specifier: ^2.6.9
version: 2.14.3(@cosmjs/amino@0.32.4)(@cosmjs/crypto@0.32.4)(@cosmjs/encoding@0.32.4)(@cosmjs/proto-signing@0.32.4)
'@cosmos-kit/react':
specifier: ^2.10.11
version: 2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^2.22.3
version: 2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/react':
specifier: ^11.11.1
version: 11.14.0(react@18.3.1)
version: 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/styled':
specifier: ^11.11.0
version: 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@interchain-ui/react':
specifier: ^1.8.0
version: 1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/icons-material':
specifier: ^5.14.9
version: 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@mui/lab':
specifier: ^5.0.0-alpha.145
version: 5.0.0-alpha.177(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material':
specifier: ^5.14.8
version: 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/accordion':
specifier: ^2.0.40
version: 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/react':
specifier: ^2.4.8
version: 2.6.11(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)
version: 2.6.11(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)
'@nymproject/contract-clients':
specifier: '>=1.2.4-rc.2 || ^1'
version: 1.4.1
'@nymproject/mix-fetch-full-fat':
specifier: file:../../dist/wasm/mix-fetch
version: '@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch'
specifier: '>=1.2.4-rc.2 || ^1'
version: 1.2.3
'@nymproject/sdk-full-fat':
specifier: file:../../dist/wasm/client
version: '@nymproject/nym-client-wasm@file:../../dist/wasm/client'
specifier: '>=1.2.4-rc.2 || ^1'
version: 1.2.3
'@redocly/cli':
specifier: ^1.25.15
version: 1.34.5(ajv@8.17.1)
@@ -117,9 +120,18 @@ importers:
specifier: ^9.0.0
version: 9.0.1
devDependencies:
'@next/swc-linux-x64-gnu':
specifier: 15.5.0
version: 15.5.0
'@types/node':
specifier: 18.11.10
version: 18.11.10
'@types/react':
specifier: ^18.3.26
version: 18.3.26
'@types/react-dom':
specifier: ^18.3.7
version: 18.3.7(@types/react@18.3.26)
copy-webpack-plugin:
specifier: ^11.0.0
version: 11.0.0(webpack@5.101.3)
@@ -128,13 +140,13 @@ importers:
version: 8.46.0
eslint-config-next:
specifier: 13.4.13
version: 13.4.13(eslint@8.46.0)(typescript@4.9.5)
version: 13.4.13(eslint@8.46.0)(typescript@5.9.3)
raw-loader:
specifier: ^4.0.2
version: 4.0.2(webpack@5.101.3)
typescript:
specifier: ^4.9.3
version: 4.9.5
specifier: ^5.9.3
version: 5.9.3
packages:
@@ -1664,11 +1676,14 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
'@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch':
resolution: {directory: ../../dist/wasm/mix-fetch, type: directory}
'@nymproject/contract-clients@1.4.1':
resolution: {integrity: sha512-HuJZ4Hv+Rl6ZZEtCHKgurNLJapM+QQRJlGkevFH2a4UdqUqF9omUkUi3AVes4679dPoSFgvA7plyVSDBdbgV6w==}
'@nymproject/nym-client-wasm@file:../../dist/wasm/client':
resolution: {directory: ../../dist/wasm/client, type: directory}
'@nymproject/mix-fetch-full-fat@1.2.3':
resolution: {integrity: sha512-5yAQhw33LC0cNsyV0Rj+LIa2kvJY+8jsfgOSr1aeKNfQ7dG4fngWkKnGUbPGzhUTJjqgXPTwNVeJSd6hwGim+g==}
'@nymproject/sdk-full-fat@1.2.3':
resolution: {integrity: sha512-lheA1Zk020o2t95FPVZfdUR0aSI52aD0aJQs074fjH16QkoNNEMotzhJGAnsnnczVg3bpddKghNpCLyDOWvg9A==}
'@opentelemetry/api-logs@0.53.0':
resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==}
@@ -2844,13 +2859,18 @@ packages:
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
'@types/react-dom@18.3.7':
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
peerDependencies:
'@types/react': ^18.0.0
'@types/react-transition-group@4.4.12':
resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
peerDependencies:
'@types/react': '*'
'@types/react@19.1.10':
resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==}
'@types/react@18.3.26':
resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==}
'@types/stylis@4.2.5':
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
@@ -6386,9 +6406,9 @@ packages:
typeforce@1.18.0:
resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==}
typescript@4.9.5:
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
engines: {node: '>=4.2.0'}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
ufo@1.6.1:
@@ -7159,16 +7179,16 @@ snapshots:
- uploadthing
- utf-8-validate
'@cosmos-kit/keplr-mobile@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)':
'@cosmos-kit/keplr-mobile@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@chain-registry/keplr': 1.74.313
'@cosmjs/amino': 0.32.4
'@cosmjs/proto-signing': 0.32.4
'@cosmos-kit/core': 2.16.3
'@cosmos-kit/keplr-extension': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
'@cosmos-kit/walletconnect': 2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@4.9.5)(zod@3.25.76)
'@cosmos-kit/walletconnect': 2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@5.9.3)(zod@3.25.76)
'@keplr-wallet/provider-extension': 0.12.264(starknet@7.6.4)
'@keplr-wallet/wc-client': 0.12.264(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)
'@keplr-wallet/wc-client': 0.12.264(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -7199,10 +7219,10 @@ snapshots:
- utf-8-validate
- zod
'@cosmos-kit/keplr@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)':
'@cosmos-kit/keplr@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@cosmos-kit/keplr-extension': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
'@cosmos-kit/keplr-mobile': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)
'@cosmos-kit/keplr-mobile': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -7270,11 +7290,13 @@ snapshots:
- uploadthing
- utf-8-validate
'@cosmos-kit/react-lite@2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@cosmos-kit/react-lite@2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@chain-registry/types': 0.46.15
'@cosmos-kit/core': 2.16.3
'@dao-dao/cosmiframe': 1.0.0(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)
'@types/react': 18.3.26
'@types/react-dom': 18.3.7(@types/react@18.3.26)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
transitivePeerDependencies:
@@ -7304,13 +7326,15 @@ snapshots:
- uploadthing
- utf-8-validate
'@cosmos-kit/react@2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@cosmos-kit/react@2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@chain-registry/types': 0.46.15
'@cosmos-kit/core': 2.16.3
'@cosmos-kit/react-lite': 2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@interchain-ui/react': 1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@cosmos-kit/react-lite': 2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@interchain-ui/react': 1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-icons/all-files': 4.1.0(react@18.3.1)
'@types/react': 18.3.26
'@types/react-dom': 18.3.7(@types/react@18.3.26)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
transitivePeerDependencies:
@@ -7340,14 +7364,14 @@ snapshots:
- uploadthing
- utf-8-validate
'@cosmos-kit/walletconnect@2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@4.9.5)(zod@3.25.76)':
'@cosmos-kit/walletconnect@2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@cosmjs/amino': 0.32.4
'@cosmjs/proto-signing': 0.32.4
'@cosmos-kit/core': 2.16.3
'@walletconnect/sign-client': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/sign-client': 2.21.8(typescript@5.9.3)(zod@3.25.76)
'@walletconnect/types': 2.21.8
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
events: 3.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -7436,7 +7460,7 @@ snapshots:
'@emotion/memoize@0.9.0': {}
'@emotion/react@11.14.0(react@18.3.1)':
'@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@emotion/babel-plugin': 11.13.5
@@ -7447,6 +7471,8 @@ snapshots:
'@emotion/weak-memoize': 0.4.0
hoist-non-react-statics: 3.3.2
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
transitivePeerDependencies:
- supports-color
@@ -7460,16 +7486,18 @@ snapshots:
'@emotion/sheet@1.4.0': {}
'@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)':
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.3.1
'@emotion/react': 11.14.0(react@18.3.1)
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
'@emotion/utils': 1.4.2
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
transitivePeerDependencies:
- supports-color
@@ -7702,7 +7730,7 @@ snapshots:
'@img/sharp-win32-x64@0.34.3':
optional: true
'@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/core': 1.7.3
'@floating-ui/dom': 1.7.3
@@ -7731,7 +7759,7 @@ snapshots:
react-aria: 3.42.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-dom: 18.3.1(react@18.3.1)
react-stately: 3.40.0(react@18.3.1)
zustand: 4.5.7(immer@10.1.1)(react@18.3.1)
zustand: 4.5.7(@types/react@18.3.26)(immer@10.1.1)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
- babel-plugin-macros
@@ -7864,11 +7892,11 @@ snapshots:
big-integer: 1.6.52
utility-types: 3.11.0
'@keplr-wallet/wc-client@0.12.264(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)':
'@keplr-wallet/wc-client@0.12.264(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)':
dependencies:
'@keplr-wallet/provider': 0.12.264(starknet@7.6.4)
'@keplr-wallet/types': 0.12.264(starknet@7.6.4)
'@walletconnect/sign-client': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/sign-client': 2.21.8(typescript@5.9.3)(zod@3.25.76)
'@walletconnect/types': 2.21.8
buffer: 6.0.3
deepmerge: 4.3.1
@@ -7938,56 +7966,61 @@ snapshots:
'@mdx-js/react@2.3.0(react@18.3.1)':
dependencies:
'@types/mdx': 2.0.13
'@types/react': 19.1.10
'@types/react': 18.3.26
react: 18.3.1
'@msgpack/msgpack@3.1.2': {}
'@mui/base@5.0.0-beta.40-1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@mui/base@5.0.0-beta.40-1(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@floating-ui/react-dom': 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.24
'@mui/utils': 5.17.1(react@18.3.1)
'@mui/types': 7.2.24(@types/react@18.3.26)
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
'@popperjs/core': 2.11.8
clsx: 2.1.1
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@mui/core-downloads-tracker@5.18.0': {}
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/material': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/base': 5.0.0-beta.40-1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/system': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.24
'@mui/utils': 5.17.1(react@18.3.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@emotion/react': 11.14.0(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
'@types/react': 18.3.26
'@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@mui/core-downloads-tracker@5.18.0': {}
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@mui/types': 7.2.24(@types/react@18.3.26)
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
clsx: 2.1.1
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@types/react': 18.3.26
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/core-downloads-tracker': 5.18.0
'@mui/system': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.24
'@mui/utils': 5.17.1(react@18.3.1)
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@mui/types': 7.2.24(@types/react@18.3.26)
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
'@popperjs/core': 2.11.8
'@types/react-transition-group': 4.4.12
'@types/react-transition-group': 4.4.12(@types/react@18.3.26)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
@@ -7996,17 +8029,20 @@ snapshots:
react-is: 19.1.1
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies:
'@emotion/react': 11.14.0(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@types/react': 18.3.26
'@mui/private-theming@5.17.1(react@18.3.1)':
'@mui/private-theming@5.17.1(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/utils': 5.17.1(react@18.3.1)
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)':
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@emotion/cache': 11.14.0
@@ -8015,35 +8051,40 @@ snapshots:
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@emotion/react': 11.14.0(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@mui/system@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)':
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/private-theming': 5.17.1(react@18.3.1)
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.24
'@mui/utils': 5.17.1(react@18.3.1)
'@mui/private-theming': 5.17.1(@types/react@18.3.26)(react@18.3.1)
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(react@18.3.1)
'@mui/types': 7.2.24(@types/react@18.3.26)
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@emotion/react': 11.14.0(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
'@types/react': 18.3.26
'@mui/types@7.2.24': {}
'@mui/types@7.2.24(@types/react@18.3.26)':
optionalDependencies:
'@types/react': 18.3.26
'@mui/utils@5.17.1(react@18.3.1)':
'@mui/utils@5.17.1(@types/react@18.3.26)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.3
'@mui/types': 7.2.24
'@mui/types': 7.2.24(@types/react@18.3.26)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 18.3.1
react-is: 19.1.1
optionalDependencies:
'@types/react': 18.3.26
'@napi-rs/simple-git-android-arm-eabi@0.1.22':
optional: true
@@ -8133,8 +8174,7 @@ snapshots:
'@next/swc-linux-arm64-musl@15.5.0':
optional: true
'@next/swc-linux-x64-gnu@15.5.0':
optional: true
'@next/swc-linux-x64-gnu@15.5.0': {}
'@next/swc-linux-x64-musl@15.5.0':
optional: true
@@ -8199,12 +8239,12 @@ snapshots:
- '@nextui-org/theme'
- framer-motion
'@nextui-org/autocomplete@2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@nextui-org/autocomplete@2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@nextui-org/aria-utils': 2.2.7(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/button': 2.2.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/listbox': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/popover': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/react-utils': 2.1.3(react@18.3.1)
@@ -8516,7 +8556,7 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@nextui-org/input@2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@nextui-org/input@2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/react-utils': 2.1.3(react@18.3.1)
@@ -8534,7 +8574,7 @@ snapshots:
'@react-types/textfield': 3.10.0(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-textarea-autosize: 8.5.9(react@18.3.1)
react-textarea-autosize: 8.5.9(@types/react@18.3.26)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
@@ -8734,11 +8774,11 @@ snapshots:
'@nextui-org/shared-utils': 2.1.2
react: 18.3.1
'@nextui-org/react@2.6.11(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)':
'@nextui-org/react@2.6.11(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)':
dependencies:
'@nextui-org/accordion': 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/alert': 2.2.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/autocomplete': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/autocomplete': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/avatar': 2.2.6(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/badge': 2.2.5(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/breadcrumbs': 2.2.6(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -8756,7 +8796,7 @@ snapshots:
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/framer-utils': 2.1.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/image': 2.2.5(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/input-otp': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/kbd': 2.2.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@nextui-org/link': 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -9205,9 +9245,11 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
'@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch': {}
'@nymproject/contract-clients@1.4.1': {}
'@nymproject/nym-client-wasm@file:../../dist/wasm/client': {}
'@nymproject/mix-fetch-full-fat@1.2.3': {}
'@nymproject/sdk-full-fat@1.2.3': {}
'@opentelemetry/api-logs@0.53.0':
dependencies:
@@ -11169,10 +11211,17 @@ snapshots:
'@types/prop-types@15.7.15': {}
'@types/react-transition-group@4.4.12': {}
'@types/react@19.1.10':
'@types/react-dom@18.3.7(@types/react@18.3.26)':
dependencies:
'@types/react': 18.3.26
'@types/react-transition-group@4.4.12(@types/react@18.3.26)':
dependencies:
'@types/react': 18.3.26
'@types/react@18.3.26':
dependencies:
'@types/prop-types': 15.7.15
csstype: 3.1.3
'@types/stylis@4.2.5': {}
@@ -11184,16 +11233,16 @@ snapshots:
'@types/unist@3.0.3': {}
'@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5)':
'@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5)
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.1
eslint: 8.46.0
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -11204,7 +11253,7 @@ snapshots:
'@typescript-eslint/types@6.21.0': {}
'@typescript-eslint/typescript-estree@6.21.0(typescript@4.9.5)':
'@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/visitor-keys': 6.21.0
@@ -11213,9 +11262,9 @@ snapshots:
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.7.2
ts-api-utils: 1.4.3(typescript@4.9.5)
ts-api-utils: 1.4.3(typescript@5.9.3)
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -11314,7 +11363,7 @@ snapshots:
dependencies:
'@vanilla-extract/css': 1.17.4(babel-plugin-macros@3.1.0)
'@walletconnect/core@2.21.8(typescript@4.9.5)(zod@3.25.76)':
'@walletconnect/core@2.21.8(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@walletconnect/heartbeat': 1.2.2
'@walletconnect/jsonrpc-provider': 1.0.14
@@ -11328,7 +11377,7 @@ snapshots:
'@walletconnect/safe-json': 1.0.2
'@walletconnect/time': 1.0.2
'@walletconnect/types': 2.21.8
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
'@walletconnect/window-getters': 1.0.1
es-toolkit: 1.39.3
events: 3.3.0
@@ -11455,16 +11504,16 @@ snapshots:
dependencies:
tslib: 1.14.1
'@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76)':
'@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@walletconnect/core': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/core': 2.21.8(typescript@5.9.3)(zod@3.25.76)
'@walletconnect/events': 1.0.1
'@walletconnect/heartbeat': 1.2.2
'@walletconnect/jsonrpc-utils': 1.0.8
'@walletconnect/logger': 2.1.2
'@walletconnect/time': 1.0.2
'@walletconnect/types': 2.21.8
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
events: 3.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
@@ -11550,7 +11599,7 @@ snapshots:
- ioredis
- uploadthing
'@walletconnect/utils@2.21.8(typescript@4.9.5)(zod@3.25.76)':
'@walletconnect/utils@2.21.8(typescript@5.9.3)(zod@3.25.76)':
dependencies:
'@msgpack/msgpack': 3.1.2
'@noble/ciphers': 1.3.0
@@ -11571,7 +11620,7 @@ snapshots:
detect-browser: 5.3.0
query-string: 7.1.3
uint8arrays: 3.1.1
viem: 2.31.0(typescript@4.9.5)(zod@3.25.76)
viem: 2.31.0(typescript@5.9.3)(zod@3.25.76)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -11692,9 +11741,9 @@ snapshots:
fs-extra: 10.1.0
yargs: 17.7.2
abitype@1.0.8(typescript@4.9.5)(zod@3.25.76):
abitype@1.0.8(typescript@5.9.3)(zod@3.25.76):
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
zod: 3.25.76
abort-controller@3.0.0:
@@ -12710,20 +12759,20 @@ snapshots:
escape-string-regexp@5.0.0: {}
eslint-config-next@13.4.13(eslint@8.46.0)(typescript@4.9.5):
eslint-config-next@13.4.13(eslint@8.46.0)(typescript@5.9.3):
dependencies:
'@next/eslint-plugin-next': 13.4.13
'@rushstack/eslint-patch': 1.12.0
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
eslint: 8.46.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.46.0)
eslint-plugin-react: 7.37.5(eslint@8.46.0)
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.46.0)
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
transitivePeerDependencies:
- eslint-import-resolver-webpack
- eslint-plugin-import-x
@@ -12748,22 +12797,22 @@ snapshots:
tinyglobby: 0.2.14
unrs-resolver: 1.11.1
optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
eslint: 8.46.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0)
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0):
eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -12774,7 +12823,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.46.0
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -12786,7 +12835,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -14655,7 +14704,7 @@ snapshots:
object-keys: 1.1.1
safe-push-apply: 1.0.0
ox@0.7.1(typescript@4.9.5)(zod@3.25.76):
ox@0.7.1(typescript@5.9.3)(zod@3.25.76):
dependencies:
'@adraffy/ens-normalize': 1.11.0
'@noble/ciphers': 1.3.0
@@ -14663,10 +14712,10 @@ snapshots:
'@noble/hashes': 1.8.0
'@scure/bip32': 1.7.0
'@scure/bip39': 1.6.0
abitype: 1.0.8(typescript@4.9.5)(zod@3.25.76)
abitype: 1.0.8(typescript@5.9.3)(zod@3.25.76)
eventemitter3: 5.0.1
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
transitivePeerDependencies:
- zod
@@ -14978,12 +15027,12 @@ snapshots:
prop-types: 15.8.1
react: 18.3.1
react-textarea-autosize@8.5.9(react@18.3.1):
react-textarea-autosize@8.5.9(@types/react@18.3.26)(react@18.3.1):
dependencies:
'@babel/runtime': 7.28.3
react: 18.3.1
use-composed-ref: 1.4.0(react@18.3.1)
use-latest: 1.3.0(react@18.3.1)
use-composed-ref: 1.4.0(@types/react@18.3.26)(react@18.3.1)
use-latest: 1.3.0(@types/react@18.3.26)(react@18.3.1)
transitivePeerDependencies:
- '@types/react'
@@ -15710,9 +15759,9 @@ snapshots:
trough@2.2.0: {}
ts-api-utils@1.4.3(typescript@4.9.5):
ts-api-utils@1.4.3(typescript@5.9.3):
dependencies:
typescript: 4.9.5
typescript: 5.9.3
ts-dedent@2.2.0: {}
@@ -15776,7 +15825,7 @@ snapshots:
typeforce@1.18.0: {}
typescript@4.9.5: {}
typescript@5.9.3: {}
ufo@1.6.1: {}
@@ -15945,18 +15994,24 @@ snapshots:
url-template@2.0.8: {}
use-composed-ref@1.4.0(react@18.3.1):
use-composed-ref@1.4.0(@types/react@18.3.26)(react@18.3.1):
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
use-isomorphic-layout-effect@1.2.1(react@18.3.1):
use-isomorphic-layout-effect@1.2.1(@types/react@18.3.26)(react@18.3.1):
dependencies:
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.26
use-latest@1.3.0(react@18.3.1):
use-latest@1.3.0(@types/react@18.3.26)(react@18.3.1):
dependencies:
react: 18.3.1
use-isomorphic-layout-effect: 1.2.1(react@18.3.1)
use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.26)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.26
use-sync-external-store@1.5.0(react@18.3.1):
dependencies:
@@ -16010,18 +16065,18 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
viem@2.31.0(typescript@4.9.5)(zod@3.25.76):
viem@2.31.0(typescript@5.9.3)(zod@3.25.76):
dependencies:
'@noble/curves': 1.9.1
'@noble/hashes': 1.8.0
'@scure/bip32': 1.7.0
'@scure/bip39': 1.6.0
abitype: 1.0.8(typescript@4.9.5)(zod@3.25.76)
abitype: 1.0.8(typescript@5.9.3)(zod@3.25.76)
isows: 1.0.7(ws@8.18.2)
ox: 0.7.1(typescript@4.9.5)(zod@3.25.76)
ox: 0.7.1(typescript@5.9.3)(zod@3.25.76)
ws: 8.18.2
optionalDependencies:
typescript: 4.9.5
typescript: 5.9.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -16195,10 +16250,11 @@ snapshots:
zod@3.25.76: {}
zustand@4.5.7(immer@10.1.1)(react@18.3.1):
zustand@4.5.7(@types/react@18.3.26)(immer@10.1.1)(react@18.3.1):
dependencies:
use-sync-external-store: 1.5.0(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.26
immer: 10.1.1
react: 18.3.1
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+17
View File
@@ -50,6 +50,7 @@ zeroize = { workspace = true }
# internal
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common" }
nym-credentials = { path = "../common/credentials" }
nym-credentials-interface = { path = "../common/credentials-interface" }
nym-credential-verification = { path = "../common/credential-verification" }
@@ -82,9 +83,25 @@ nym-service-provider-requests-common = { path = "../common/service-provider-requ
defguard_wireguard_rs = { workspace = true }
opentelemetry = { workspace = true, optional = true }
opentelemetry_sdk = { workspace = true, optional = true }
tracing-opentelemetry = { workspace = true, optional = true }
[dev-dependencies]
nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] }
nym-wireguard = { path = "../common/wireguard", features = ["mock"] }
mock_instant = "0.6.0"
time = { workspace = true }
[features]
default = []
otel = [
"nym-bin-common/otel",
"nym-client-core/otel",
"nym-gateway-requests/otel",
"nym-sphinx/otel",
"nym-sdk/otel",
"opentelemetry",
"opentelemetry_sdk",
"tracing-opentelemetry",
]
@@ -14,6 +14,8 @@ use futures::{
future::{FusedFuture, OptionFuture},
FutureExt, StreamExt,
};
#[cfg(feature = "otel")]
use nym_bin_common::opentelemetry::context::ManualContextPropagator;
use nym_credential_verification::CredentialVerifier;
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, ClientBandwidth,
@@ -31,6 +33,8 @@ use nym_sphinx::forwarding::packet::MixPacket;
use nym_statistics_common::{gateways::GatewaySessionEvent, types::SessionType};
use nym_validator_client::coconut::EcashApiError;
use rand::{random, CryptoRng, Rng};
#[cfg(feature = "otel")]
use std::collections::HashMap;
use std::{process, time::Duration};
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
@@ -147,6 +151,8 @@ pub(crate) struct AuthenticatedHandler<R, S> {
// senders that are used to return the result of the ping to the handler requesting the ping.
is_active_request_receiver: IsActiveRequestReceiver,
is_active_ping_pending_reply: Option<(u64, IsActiveResultSender)>,
#[cfg(feature = "otel")]
pub otel_propagator: Option<ManualContextPropagator>,
}
// explicitly remove handle from the global store upon being dropped
@@ -189,6 +195,24 @@ impl<R, S> AuthenticatedHandler<R, S> {
client_address: client.address.as_base58_string(),
})?;
#[cfg(feature = "otel")]
let manual_ctx_propagator = {
let context = match client.otel_context {
Some(ref ctx) => ctx.clone(),
None => HashMap::new(),
};
let manual_ctx_propagator = if !context.is_empty() {
Some(ManualContextPropagator::new(
"upgrading_fresh_to_authenticated",
context,
))
} else {
None
};
manual_ctx_propagator
};
let handler = AuthenticatedHandler {
bandwidth_storage_manager: BandwidthStorageManager::new(
Box::new(fresh.shared_state.storage.clone()),
@@ -202,6 +226,8 @@ impl<R, S> AuthenticatedHandler<R, S> {
mix_receiver,
is_active_request_receiver,
is_active_ping_pending_reply: None,
#[cfg(feature = "otel")]
otel_propagator: manual_ctx_propagator,
};
handler.send_metrics(GatewaySessionEvent::new_session_start(
handler.client.address,
@@ -227,7 +253,19 @@ impl<R, S> AuthenticatedHandler<R, S> {
/// # Arguments
///
/// * `mix_packet`: packet received from the client that should get forwarded into the network.
#[instrument(skip_all)]
fn forward_packet(&self, mix_packet: MixPacket) {
#[cfg(feature = "otel")]
{
let span = match &self.otel_propagator {
Some(propagator) => {
info_span!(parent: &propagator.root_span, "forwarding_mix_packet")
}
None => info_span!("forwarding_mix_packet_no_otel"),
};
let _enter = span.enter();
}
if let Err(err) = self
.inner
.shared_state
@@ -287,11 +325,26 @@ impl<R, S> AuthenticatedHandler<R, S> {
&mut self,
mix_packet: MixPacket,
) -> Result<ServerResponse, RequestHandlingError> {
trace!("forwarding sphinx packet");
#[cfg(feature = "otel")]
let span = {
let span = match &self.otel_propagator {
Some(propagator) => {
info_span!(parent: &propagator.root_span, "handling_forward_sphinx")
}
None => debug_span!("handling_forward_sphinx_no_otel"),
};
span
};
#[cfg(feature = "otel")]
let _enter = span.enter();
let required_bandwidth = mix_packet.packet().len() as i64;
let remaining_bandwidth = self
.bandwidth_storage_manager
.try_use_bandwidth(required_bandwidth)
.in_current_span()
.await?;
self.forward_packet(mix_packet);
@@ -305,8 +358,22 @@ impl<R, S> AuthenticatedHandler<R, S> {
/// # Arguments
///
/// * `bin_msg`: raw message to handle.
#[instrument(skip_all)]
async fn handle_binary(&mut self, bin_msg: Vec<u8>) -> Message {
trace!("binary request");
#[cfg(feature = "otel")]
let span = {
let span = match &self.otel_propagator {
Some(propagator) => {
info_span!(parent: &propagator.root_span, "handling_binary_request")
}
None => info_span!("handling_binary_request_no_otel"),
};
span
};
#[cfg(feature = "otel")]
let _enter = span.enter();
// this function decrypts the request and checks the MAC
match BinaryRequest::try_from_encrypted_tagged_bytes(bin_msg, &self.client.shared_keys) {
Err(e) => {
@@ -316,9 +383,11 @@ impl<R, S> AuthenticatedHandler<R, S> {
Ok(request) => match request {
// currently only a single type exists
BinaryRequest::ForwardSphinx { packet }
| BinaryRequest::ForwardSphinxV2 { packet } => {
self.handle_forward_sphinx(packet).await.into_ws_message()
}
| BinaryRequest::ForwardSphinxV2 { packet } => self
.handle_forward_sphinx(packet)
.in_current_span()
.await
.into_ws_message(),
_ => RequestHandlingError::UnknownBinaryRequest.into_error_message(),
},
}
@@ -379,6 +448,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
Ok(SensitiveServerResponse::KeyUpgradeAck {}.encrypt(&self.client.shared_keys)?)
}
#[instrument(skip_all)]
async fn handle_encrypted_text_request(
&mut self,
ciphertext: Vec<u8>,
@@ -408,6 +478,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
/// # Arguments
///
/// * `raw_request`: raw message to handle.
#[instrument(skip_all)]
async fn handle_text(&mut self, raw_request: String) -> Message
where
R: Rng + CryptoRng,
@@ -552,6 +623,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
}
}
#[instrument(skip_all)]
async fn handle_is_active_request(
&mut self,
reply_tx: IsActiveResultSender,
@@ -582,20 +654,37 @@ impl<R, S> AuthenticatedHandler<R, S> {
/// Simultaneously listens for incoming client requests, which realistically should only be
/// binary requests to forward sphinx packets or increase bandwidth
/// and for sphinx packets received from the mix network that should be sent back to the client.
#[instrument(level = "debug", skip_all,
fields(
client = %self.client.address.as_base58_string()
)
)]
pub(crate) async fn listen_for_requests(mut self)
where
R: Rng + CryptoRng,
S: AsyncRead + AsyncWrite + Unpin,
{
trace!("Started listening for ALL incoming requests...");
// Ping timeout future used to check if the client responded to our ping request
let mut ping_timeout: OptionFuture<_> = None.into();
#[cfg(feature = "otel")]
let from_client_span = {
let span = match &self.otel_propagator {
Some(propagator) => {
info_span!(parent: &propagator.root_span, "authenticated_client_handler_listen")
}
None => tracing::debug_span!("authenticated_client_handler_listen_no_otel"),
};
span
};
#[cfg(feature = "otel")]
let _enter = from_client_span.enter();
loop {
tokio::select! {
// Received a request to ping the client to check if it's still active
tx = self.is_active_request_receiver.next() => {
tx = self.is_active_request_receiver.next().in_current_span() => {
match tx {
None => break,
Some(reply_tx) => {
@@ -609,10 +698,10 @@ impl<R, S> AuthenticatedHandler<R, S> {
},
// The ping timeout expired, meaning the client didn't respond to our ping request
_ = &mut ping_timeout, if !ping_timeout.is_terminated() => {
ping_timeout = None.into();
self.handle_ping_timeout().await;
ping_timeout = None.into();
self.handle_ping_timeout().await;
},
socket_msg = self.inner.read_websocket_message() => {
socket_msg = self.inner.read_websocket_message().in_current_span() => {
let socket_msg = match socket_msg {
None => break,
Some(Ok(socket_msg)) => socket_msg,
@@ -627,7 +716,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
}
if let Some(response) = self.handle_request(socket_msg).await {
if let Err(err) = self.inner.send_websocket_message(response).await {
if let Err(err) = self.inner.send_websocket_message(response).in_current_span().await {
debug!(
"Failed to send message over websocket: {err}. Assuming the connection is dead.",
);
@@ -635,7 +724,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
}
}
},
mix_messages = self.mix_receiver.next() => {
mix_messages = self.mix_receiver.next().in_current_span() => {
let mix_messages = match mix_messages {
None => {
debug!("mix receiver was closed! Assuming the connection is dead.");
@@ -13,6 +13,8 @@ use futures::{
channel::{mpsc, oneshot},
SinkExt, StreamExt,
};
#[cfg(feature = "otel")]
use nym_bin_common::opentelemetry::context::ManualContextPropagator;
use nym_credentials_interface::AvailableBandwidth;
use nym_crypto::aes::cipher::crypto_common::rand_core::RngCore;
use nym_crypto::asymmetric::ed25519;
@@ -34,6 +36,8 @@ use nym_node_metrics::events::MetricsEvent;
use nym_sphinx::DestinationAddressBytes;
use nym_task::ShutdownToken;
use rand::CryptoRng;
#[cfg(feature = "otel")]
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::Duration;
use thiserror::Error;
@@ -41,7 +45,9 @@ use time::OffsetDateTime;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::time::timeout;
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
use tracing::*;
#[cfg(feature = "otel")]
use tracing::info_span;
use tracing::{debug, error, info, instrument, warn, Instrument};
#[derive(Debug, Error)]
pub(crate) enum InitialAuthenticationError {
@@ -163,6 +169,7 @@ impl<R, S> FreshHandler<R, S> {
/// Attempts to perform websocket handshake with the remote and upgrades the raw TCP socket
/// to the framed WebSocket.
#[instrument(skip_all)]
pub(crate) async fn perform_websocket_handshake(&mut self) -> Result<(), WsError>
where
S: AsyncRead + AsyncWrite + Unpin,
@@ -186,6 +193,7 @@ impl<R, S> FreshHandler<R, S> {
/// # Arguments
///
/// * `init_msg`: a client handshake init message which should contain its identity public key as well as an ephemeral key.
#[instrument(skip_all)]
async fn perform_registration_handshake(
&mut self,
init_msg: Vec<u8>,
@@ -211,6 +219,7 @@ impl<R, S> FreshHandler<R, S> {
}
/// Attempts to read websocket message from the associated socket.
#[instrument(skip_all)]
pub(crate) async fn read_websocket_message(&mut self) -> Option<Result<Message, WsError>>
where
S: AsyncRead + AsyncWrite + Unpin,
@@ -226,6 +235,7 @@ impl<R, S> FreshHandler<R, S> {
/// # Arguments
///
/// * `msg`: WebSocket message to write back to the client.
#[instrument(skip_all)]
pub(crate) async fn send_websocket_message(
&mut self,
msg: impl Into<Message>,
@@ -242,6 +252,7 @@ impl<R, S> FreshHandler<R, S> {
}
}
#[instrument(skip_all)]
pub(crate) async fn send_error_response(
&mut self,
err: impl std::error::Error,
@@ -269,6 +280,7 @@ impl<R, S> FreshHandler<R, S> {
///
/// * `shared_keys`: keys derived between the client and gateway.
/// * `packets`: unwrapped packets that are to be pushed back to the client.
#[instrument(skip_all)]
pub(crate) async fn push_packets_to_client(
&mut self,
shared_keys: &SharedGatewayKey,
@@ -326,6 +338,7 @@ impl<R, S> FreshHandler<R, S> {
///
/// * `client_address`: address of the client that is going to receive the messages.
/// * `shared_keys`: shared keys derived between the client and the gateway used to encrypt and tag the messages.
#[instrument(skip_all)]
async fn push_stored_messages_to_client(
&mut self,
client_address: DestinationAddressBytes,
@@ -632,6 +645,8 @@ impl<R, S> FreshHandler<R, S> {
address,
shared_keys.key,
session_request_start,
#[cfg(feature = "otel")]
None,
)),
ServerResponse::Authenticate {
protocol_version: Some(negotiated_protocol),
@@ -641,9 +656,13 @@ impl<R, S> FreshHandler<R, S> {
))
}
#[instrument(skip_all, fields(
address = %request.content.client_identity.derive_destination_address(),
))]
async fn handle_authenticate_v2(
&mut self,
request: Box<AuthenticateRequest>,
#[cfg(feature = "otel")] otel_context: Option<HashMap<String, String>>,
) -> Result<InitialAuthResult, InitialAuthenticationError>
where
S: AsyncRead + AsyncWrite + Unpin,
@@ -718,6 +737,8 @@ impl<R, S> FreshHandler<R, S> {
address,
shared_key.key,
session_request_start,
#[cfg(feature = "otel")]
otel_context,
)),
ServerResponse::Authenticate {
protocol_version: Some(negotiated_protocol),
@@ -816,6 +837,8 @@ impl<R, S> FreshHandler<R, S> {
remote_address,
shared_keys,
OffsetDateTime::now_utc(),
#[cfg(feature = "otel")]
None,
);
Ok(InitialAuthResult::new(
@@ -846,6 +869,7 @@ impl<R, S> FreshHandler<R, S> {
}
}
#[instrument(skip_all)]
pub(crate) async fn handle_initial_client_request(
&mut self,
request: ClientControlRequest,
@@ -854,17 +878,58 @@ impl<R, S> FreshHandler<R, S> {
S: AsyncRead + AsyncWrite + Unpin + Send,
R: CryptoRng + RngCore + Send,
{
// we can handle stateless client requests without prior authentication, like `ClientControlRequest::SupportedProtocol`
// extract and set up opentelemetry context if provided
#[cfg(feature = "otel")]
let (context_propagator, otel_ctx) =
if let ClientControlRequest::AuthenticateV2(ref auth_req) = request {
if let Some(otel_context) = &auth_req.otel_context {
info!(
"=== OpenTelemetry context provided in the request: {otel_context:?} ==="
);
(
Some(ManualContextPropagator::new(
"handling_initial_client_request_with_otel",
otel_context.clone(),
)),
Some(otel_context.clone()),
)
} else {
debug!("No OpenTelemetry context provided in the request");
(None, None)
}
} else {
debug!("No OpenTelemetry context provided in the request");
(None, None)
};
#[cfg(feature = "otel")]
let child_span = match context_propagator {
Some(ref propagator) => {
let span = info_span!(parent: &propagator.root_span, "=== Handling initial client request with otel context ===");
span
}
None => {
tracing::debug_span!("=== Handling initial client request without otel context ===")
}
};
#[cfg(feature = "otel")]
let _enter = child_span.enter();
let auth_result = match request {
ClientControlRequest::Authenticate {
protocol_version,
address,
enc_address,
iv,
otel_context: _,
} => {
self.handle_legacy_authenticate(protocol_version, address, enc_address, iv)
.await
}
#[cfg(feature = "otel")]
ClientControlRequest::AuthenticateV2(req) => {
self.handle_authenticate_v2(req, otel_ctx).await
}
#[cfg(not(feature = "otel"))]
ClientControlRequest::AuthenticateV2(req) => self.handle_authenticate_v2(req).await,
ClientControlRequest::RegisterHandshakeInitRequest {
protocol_version,
@@ -889,7 +954,6 @@ impl<R, S> FreshHandler<R, S> {
}
other => debug!("authentication failure: {other}"),
}
self.send_and_forget_error_response(&err).await;
return Err(err);
}
@@ -912,9 +976,11 @@ impl<R, S> FreshHandler<R, S> {
warn!("could not establish client details");
return Err(InitialAuthenticationError::EmptyClientDetails);
};
Ok(Some(client_details))
}
#[instrument(skip_all)]
pub(crate) async fn handle_until_authenticated_or_failure(
mut self,
) -> Option<AuthenticatedHandler<R, S>>
@@ -953,7 +1019,7 @@ impl<R, S> FreshHandler<R, S> {
registration_details.session_request_timestamp,
);
return AuthenticatedHandler::upgrade(
let auth_handle = AuthenticatedHandler::upgrade(
self,
registration_details,
mix_receiver,
@@ -962,10 +1028,12 @@ impl<R, S> FreshHandler<R, S> {
.await
.inspect_err(|err| error!("failed to upgrade client handler: {err}"))
.ok();
return auth_handle;
}
}
}
#[instrument(skip_all)]
pub(crate) async fn wait_for_initial_message(
&mut self,
) -> Result<ClientControlRequest, InitialAuthenticationError>
@@ -1016,6 +1084,7 @@ impl<R, S> FreshHandler<R, S> {
.map_err(|_| InitialAuthenticationError::InvalidRequest)
}
#[instrument(skip_all)]
pub(crate) async fn start_handling(self)
where
S: AsyncRead + AsyncWrite + Unpin + Send,
@@ -1025,10 +1094,10 @@ impl<R, S> FreshHandler<R, S> {
let shutdown = self.shutdown.clone();
tokio::select! {
_ = shutdown.cancelled() => {
trace!("received cancellation")
tracing::trace!("received cancellation")
}
_ = super::handle_connection(self) => {
debug!("finished connection handler for {remote}")
_ = super::handle_connection(self).instrument(tracing::debug_span!("connection_handler", remote = %remote)) => {
tracing::debug!("finished connection handler for {remote}")
}
}
}
@@ -7,10 +7,13 @@ use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_gateway_requests::ServerResponse;
use nym_sphinx::DestinationAddressBytes;
use rand::{CryptoRng, Rng};
#[cfg(feature = "otel")]
use std::collections::HashMap;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::WebSocketStream;
use tracing::Instrument;
use tracing::{debug, instrument, trace, warn};
pub(crate) use self::authenticated::AuthenticatedHandler;
@@ -48,6 +51,8 @@ pub(crate) struct ClientDetails {
// note, this does **NOT ALWAYS** indicate timestamp of when client connected
// it is (for v2 auth) timestamp the client **signed** when it created the request
pub(crate) session_request_timestamp: OffsetDateTime,
#[cfg(feature = "otel")]
pub(crate) otel_context: Option<HashMap<String, String>>,
}
impl ClientDetails {
@@ -56,12 +61,15 @@ impl ClientDetails {
address: DestinationAddressBytes,
shared_keys: SharedGatewayKey,
session_request_timestamp: OffsetDateTime,
#[cfg(feature = "otel")] otel_context: Option<HashMap<String, String>>,
) -> Self {
ClientDetails {
address,
id,
shared_keys,
session_request_timestamp,
#[cfg(feature = "otel")]
otel_context,
}
}
}
@@ -92,7 +100,7 @@ impl InitialAuthResult {
}
// imo there's no point in including the peer address in anything higher than debug
#[instrument(level = "debug", skip_all, fields(peer = %handle.peer_address))]
#[instrument(skip_all)]
pub(crate) async fn handle_connection<R, S>(mut handle: FreshHandler<R, S>)
where
R: Rng + CryptoRng + Send,
@@ -117,8 +125,25 @@ where
trace!("managed to perform websocket handshake!");
if let Some(auth_handle) = handle.handle_until_authenticated_or_failure().await {
auth_handle.listen_for_requests().await
if let Some(auth_handle) = handle.handle_until_authenticated_or_failure().in_current_span().await {
#[cfg(feature = "otel")]
{
let from_client_span = {
let parent = match auth_handle.otel_propagator.as_ref() {
Some(propagator) => propagator.root_span(),
None => &tracing::Span::current(), // fallback to current span if no propagator
};
tracing::info_span!(parent: parent, "listening for requests")
};
auth_handle
.listen_for_requests()
.instrument(from_client_span)
.await
}
#[cfg(not(feature = "otel"))]
{
auth_handle.listen_for_requests().await;
}
}
trace!("the handler is done!");
@@ -53,6 +53,7 @@ impl Listener {
)
}
#[instrument(skip_all)]
fn try_handle_accepted_connection(&self, accepted: io::Result<(TcpStream, SocketAddr)>) {
match accepted {
Ok((socket, remote_address)) => {
@@ -90,7 +91,7 @@ impl Listener {
let metrics_ref = handle.shared_state.metrics.clone();
// 4.1. handle all client requests until connection gets terminated
handle.start_handling().await;
handle.start_handling().in_current_span().await;
// 4.2. decrement the connection counter
metrics_ref.network.disconnected_ingress_websocket_client();
@@ -104,6 +105,7 @@ impl Listener {
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
#[instrument(skip_all)]
pub async fn run(&mut self) {
info!("Starting websocket listener at {}", self.address);
let tcp_listener = match tokio::net::TcpListener::bind(self.address).await {
@@ -122,7 +124,7 @@ impl Listener {
trace!("client_handling::Listener: received shutdown");
break
}
connection = tcp_listener.accept() => {
connection = tcp_listener.accept().in_current_span() => {
self.try_handle_accepted_connection(connection)
}
}
@@ -977,6 +977,8 @@ fn create_input_message(
response_packet,
lane,
packet_type,
#[cfg(feature = "otel")]
None,
))
} else {
tracing::error!("No nym-address or sender tag provided");
+6 -2
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.66"
version = "1.1.67"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -95,7 +95,7 @@ nym-topology = { path = "../common/topology" }
nym-api-requests = { path = "nym-api-requests" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] }
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi"] }
nym-node-tester-utils = { path = "../common/node-tester-utils" }
nym-node-requests = { path = "../nym-node/nym-node-requests" }
nym-types = { path = "../common/types" }
@@ -110,6 +110,10 @@ nym-ecash-signer-check = { path = "../common/ecash-signer-check" }
no-reward = []
v2-performance = []
generate-ts = ["ts-rs"]
otel = [
"nym-bin-common/otel",
"nym-node-tester-utils/otel",
]
[build-dependencies]
anyhow = { workspace = true }
+1 -1
View File
@@ -31,7 +31,7 @@ async fn main() -> Result<(), anyhow::Error> {
// instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
console_subscriber::init();
} else {
nym_bin_common::logging::setup_tracing_logger();
nym_bin_common::logging::setup_no_otel_logger().expect("failed to initialize logging");
}}
info!("Starting nym api...");
+1
View File
@@ -124,6 +124,7 @@ pub struct Config {
pub node_status_api: NodeStatusAPI,
#[serde(alias = "topology_cacher")]
#[serde(default)]
pub describe_cache: DescribeCache,
#[serde(default)]
-24
View File
@@ -70,30 +70,6 @@ per_node_test_packets = {{ network_monitor.debug.per_node_test_packets }}
# Path to the database file containing uptime statuses for all mixnodes and gateways.
database_path = '{{ node_status_api.storage_paths.database_path }}'
[node_status_api.debug]
caching_interval = '{{ node_status_api.debug.caching_interval }}'
##### topology cacher config options #####
[topology_cacher.debug]
caching_interval = '{{ topology_cacher.debug.caching_interval }}'
##### circulating supply cacher config options #####
[circulating_supply_cacher]
# Specifies whether circulating supply caching service is enabled in this process.
enabled = {{ circulating_supply_cacher.enabled }}
[circulating_supply_cacher.debug]
caching_interval = '{{ circulating_supply_cacher.debug.caching_interval }}'
##### rewarding config options #####
[rewarding]

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