Compare commits

...

102 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu f4bd48263d Apply cargo fmt 2026-06-08 15:32:57 +03:00
rachyandco 5c40052d39 Bugfix: set connection fd callback on LP registration socket
The LP registration client opened its TCP connection to the gateway
  control port with a plain TcpStream::connect, without invoking the
  connection_fd_callback used by the websocket registration path. On
  Linux the callback sets SO_MARK (fwmark) so the daemon's own firewall
  allows the connection during the connecting state; without it the
  unmarked SYN matched no allow rule and was rejected locally with
  ECONNREFUSED. The daemon blamed the gateway, blacklisted it, and
  cycled through every gateway the same way, making it impossible to
  connect when LP registration is enabled.

  Plumb the existing connection_fd_callback from the builder config
  through RegistrationClientConfig into the LP client via an optional
  dialer, which creates the socket, applies the callback before
  connect(), and only then dials. The fd callback must run before
  connect so the SYN itself carries the mark.

  The exit gateway needs no dialer: its registration is forwarded
  through the entry gateway's nested session and never opens its own
  TCP connection.
2026-06-05 23:36:52 +02:00
Jędrzej Stuczyński 7b858dfd69 chore: LP registration adjustments (#6845)
* remove mixnet fallback for LP registration

* change LP registration timeouts and introduce exchange timeout

* remove fallback client construction and disable mixnet via LP registration
2026-06-02 16:28:57 +01:00
Jack Wampler a4bd547023 Handle Rate Limit Challenge Response (#6825)
rotate urls on HTTP response error indicating API rate limiting
2026-05-26 14:58:45 -06:00
Andy Duplain db03ec31b1 Merge pull request #6812 from nymtech/cherry-pick/nym-583-corrupt-db-windows
NYM-583: Avoid corrupted database on Windows.
2026-05-21 14:44:39 +01:00
Andy Duplain 9b285735b8 NYM-583: Avoid corrupted database on Windows.
NYM-583: Avoid corrupted database on Windows.
2026-05-21 14:23:20 +01:00
Simon Wicky 691280797a back to v8 on non sdk client (#6771) 2026-05-13 18:24:50 +02:00
benedettadavico f84de25302 update changelog 2026-05-06 07:16:42 +02:00
benedettadavico e554f1e0ad bump versions 2026-04-28 15:02:40 +02:00
benedetta davico 62a4a2ed70 Merge pull request #6710 from nymtech/bdq/versioning-fix 2026-04-28 10:01:52 +02:00
benedetta davico caad74c73d Merge pull request #6713 from nymtech/bdq/nym-binaries-ci
update CI runners
2026-04-27 15:39:44 +02:00
benedettadavico 917993d8fb clean 2026-04-27 12:17:31 +02:00
benedettadavico 1451db39e6 warn 2026-04-27 11:27:41 +02:00
benedettadavico f13a2a6c06 change to warn level 2026-04-27 10:45:42 +02:00
benedetta davico ce39fb6675 Update publish-nym-binaries.yml 2026-04-27 10:20:10 +02:00
benedettadavico 02a926b74a addressing comments 2026-04-27 10:10:08 +02:00
benedetta davico 54ba710ea0 Change CI platform from ubuntu-22.04 to arc-ubuntu-22.04 2026-04-27 09:33:57 +02:00
benedettadavico 2653d12e55 fix ipr msg, and unit tests 2026-04-24 16:07:49 +02:00
benedettadavico f94d6d51cf adding debugging traces 2026-04-24 14:11:19 +02:00
Andrej Mihajlov a0116f9aec Merge pull request #6708 from nymtech/am/lazy-init-dns
Only init SHARED_CLIENT if requested
2026-04-23 18:36:57 +02:00
Tommy Verrall aaa8ee9d53 Revert "Merge remote-tracking branch 'origin/develop' into chore/eslint-9-flat-config-migration"
This reverts commit ab0f6af4b9, reversing
changes made to cca19f36c2.
2026-04-23 15:42:27 +01:00
Tommy Verrall ab0f6af4b9 Merge remote-tracking branch 'origin/develop' into chore/eslint-9-flat-config-migration 2026-04-23 15:40:01 +01:00
Tommy Verrall 7669d0933f Migrate ESLint config to flat config for ESLint 9
Dependabot bumped eslint to ^9 across the lint-scoped TS packages but did
not migrate the legacy .eslintrc.* configs, breaking CI lint on develop.

Behavior preserved: yarn lint passes locally with the same effective rule
coverage as the pre-bump setup. Pre-existing warnings in nym-wallet and
mui-theme are unchanged. Orphan .eslintrc files in sdk/typescript outside
the lerna lint scope are left untouched.
2026-04-23 15:39:11 +01:00
Andrej Mihajlov 50433fe265 Only init SHARED_CLIENT if requested 2026-04-23 16:29:02 +02:00
benedettadavico 42aade29eb more v9 fixes 2026-04-23 13:28:17 +02:00
benedettadavico 9f26759b8d v9 bugfix 2026-04-23 13:28:17 +02:00
benedettadavico 9e642c6354 v9 bugfix 2026-04-23 13:28:16 +02:00
mfahampshire cca19f36c2 Remove unused header (#6699) 2026-04-22 09:35:59 +00:00
Merve 17894880e0 Changelog urda (#6698)
* test identity

* changelog update
2026-04-22 08:41:12 +00:00
benedetta davico ef6fc82c39 Merge pull request #6696 from nymtech/release/2026.8-urda
final merge todevelop
2026-04-21 13:14:23 +02:00
benedettadavico 0c83ae2408 duplicate description 2026-04-21 12:06:23 +02:00
benedetta davico 92490731e7 Merge pull request #6691 from nymtech/merge/release/2026.8-urda
Merge release/2026.8 urda
2026-04-20 14:28:34 +02:00
benedettadavico 0ce93e366e Merge branch 'release/2026.8-urda' into develop 2026-04-20 14:10:23 +02:00
benedettadavico 0d031875f6 merge conflicts 2026-04-20 14:07:38 +02:00
benedettadavico e6103e4c43 update changelog 2026-04-20 13:58:11 +02:00
dynco-nym 7140ba4ea9 Fix invalid ticket spend (#6683)
* Fix

* PR feedback

* Bump version

* Update sqlx files
2026-04-17 16:49:17 +02:00
benedetta davico 62962509eb Merge pull request #6686 from nymtech/workflow/fix
Fixes to crates and CI
2026-04-17 16:21:12 +02:00
mfahampshire d285b70030 Specify Rust v1.85 for all contract crates (test) 2026-04-17 13:04:14 +01:00
dynco-nym 534a5068d3 Return ipv6 addresses as well (#6684)
* Return ipv6 addresses as well

* Fix clippy

* Bump NS API version
2026-04-17 13:42:48 +02:00
benedettadavico e32c042c8d version bump 2026-04-17 11:03:03 +02:00
mfahampshire dd6a45f251 Make publication explicit 2026-04-17 09:23:55 +01:00
mfahampshire 6ee1f16ce8 Canonical ordering lefthook checker 2026-04-17 08:17:18 +01:00
mfahampshire 924d7d1ccc Enforce ordering of [package] fields in cargo.toml files 2026-04-17 07:49:50 +01:00
mfahampshire 395c134186 Cargo.toml package field fixes for preflight check 2026-04-16 14:02:57 +01:00
benedetta davico 55e485ebce Update Cargo.toml 2026-04-16 13:16:37 +02:00
benedetta davico cfce6dedff Update Cargo.toml 2026-04-16 13:13:35 +02:00
benedetta davico 5a08a4cdd2 Enable publishing for nym-lp Cargo package 2026-04-16 13:11:07 +02:00
benedetta davico 42ffb7d36e Enable publishing for nym-kkt-ciphersuite 2026-04-16 13:10:51 +02:00
benedettadavico e024d68fac mebbe 2026-04-16 12:28:16 +02:00
benedettadavico e66a069d5f add file 2026-04-16 12:28:15 +02:00
benedettadavico 3531901a17 ? 2026-04-16 12:28:15 +02:00
benedettadavico a399a75b03 test.. 2026-04-16 12:28:14 +02:00
benedettadavico 0de14718cb attempt at fix 2026-04-16 12:28:14 +02:00
mfahampshire cd476ef6a2 Update libcrux crates to use versions published on crates.io instead of
git import
2026-04-15 17:30:57 +01:00
dynco-nym ad56645fc5 Block non-public IPR/NR checks (#6670)
* Block non-public IPR/NR checks

* Add CLI override flag
2026-04-15 15:59:38 +02:00
mfahampshire 7ceaf9a40e Max/mixtcp (#6321)
* Add mixtcp crate 

Components:
- NymIprDevice: smoltcp::phy::Device impl using channel-based I/O
- NymIprBridge: async task bridging the device to IpMixStream
- create_device(): helper to set up the complete stack

* - Cleanup
- Add graceful shutdown
- Declutter logging - move a lot of bridge info! -> trace!
- Move rustls, nym-bin-common, bytes to dev-dependencies
- Extract TlsOverTcp to mod.rs
- Make timing more granular
- Update readme

* Add UDP example

* Add UDP example to readme

* rename mixtcp -> smolmix

* Add Tunnel API with TcpStream and UdpSocket over tokio-smoltcp

* Re-export Tunnel API and add init_logging convenience function

* Remove raw smoltcp path, flatten tunnel module

* Clean up bridge, device, and tunnel code

* Consolidate architecture docs, tidy examples and README

- Add src/ARCHITECTURE.md as single source of truth for architecture
- Include in docs.rs via doc = include_str!
- Strip duplicated diagrams from tunnel.rs, device.rs, README
- Extract tls_connector() helper in HTTPS example to match websocket example
- Use consistent 'smolmix' casing in README

* Update smolmix imports for ipr_wrapper API

- stream_wrapper::{IpMixStream, NetworkEnvironment} → ipr_wrapper::
- connect_tunnel() → check_connected()
- disconnect_stream() → disconnect()
- allocated_ips() returns &IpPair directly (no Option)

* Add Tunnel::new_with_ipr, re-export IpPair/Recipient, tidy examples

- Add Tunnel::new_with_ipr() for targeting a specific exit node
- Re-export IpPair and Recipient so users don't need direct deps
- Add DNS leak warning to WebSocket example
- Await hyper connection task in HTTPS example

* Restructure smolmix into multi-crate workspace

- Move core tunnel code to smolmix/core/- Rewrite examples for each crate with clearnet/mixnet comparisons

* Add workspace README with architecture overview

* Update nym-sdk README module descriptions

- Replace stale stream_wrapper description with ipr_wrapper + mixnet::stream
- Remove TODO comment

* Remove companion crates, scope to smolmix-core

* Comment out additional components on -core branch README.md

* Cargo.lock fix for compilation issue

* Downgrade accidentally bumped dependencies in Cargo lock + change
smolmix dependencies to import from workspace

* Fix workspace deps + move nym-bin-common to dev-deps

* PR review changes + fix Sink delegation

* Fix borked merge + update README.md

* Fix up stale docs + rewrite examples to use proper imports and timing
logs

* Update readmes + architecture file

* Impl Drop for BridgeShutdownHandle + update comment
2026-04-14 20:13:12 +00:00
dynco-nym 2209e8ac04 Include all gateways in the returned list (#6649)
* Include all gateways in the returned list

* Fix clippy

* Bump API version
2026-04-10 16:55:42 +02:00
import this 6f2a3d9033 [DOCs/operators]: Add -t flag to ansible guides 2026-04-10 14:34:11 +02:00
mfahampshire 0530967807 Fix broken redirects & links. (#6660) 2026-04-10 11:01:20 +00:00
mfahampshire 9db748e8dd Max/sdk docrs (#6566)
* Improve SDK rustdoc and add ARCHITECTURE.md files

- Rewrite lib.rs module docs with quick-start example and module overview
- Add stream example and include_str! ARCHITECTURE.md to mixnet module
- Add ARCHITECTURE.md for mixnet, client_pool, and stream modules
- Add rustdoc to MixnetClientBuilder, MixnetClientSender, MixnetMessageSender
- Add cancel safety and drop behavior annotations to async methods
- Add TcpProxy deprecation notice pointing to stream module

* Fix rustdoc errors and add stepwise comments to remaining examples

Rustdoc fixes:
- Add missing .unwrap() on connect_new example
- Replace broken turbofish intra-doc link in MixnetClientBuilder
- Fix NymProxyServer::new args in tcp_proxy example
- Wrap BandwidthImporter example in scoped block to fix borrow-then-move
- Change misleading "5-hop routing" to "multi-hop routing"
- Fix copy-paste "forget me" in send_remember_me error message
- Fix wrong cargo run command in stream_simple_read_write
- Fix DecayWrapper description

* Cut down doc comment length

* Trimmed down SDK ARCHITECTURE files

* Slim Rust SDK docs and rename opener to dialer

- Merge tour page into SDK landing page, delete tour.mdx
- Trim all three tutorials: cut boilerplate, duplicated code, and misplaced content
- Make FFI page evergreen with Go and C++ snippets, link to repo examples
- Rename "opener" to "dialer" in stream docs, source ARCHITECTURE.md, and rustdoc
- Add reply-to-open arrow in stream mermaid diagram
- Replace remaining Unicode dashes in mermaid flowchart

* - elevate streams in rustdoc: examples on lib.rs, MixnetClient, open_stream, listener
- add stream quick reference to mixnet ARCHITECTURE.md
- add stream types to key types list in ARCHITECTURE.md
- add docs.rs links for AsyncRead/AsyncWrite and stream submodule
- tcp_proxy: replace bold deprecation with warning box

* - replace individual example doc pages with GitHub-linked tables
- add step-by-step inline comments to all SDK example source files
- add doc comments to examples missing them (simple, surb_reply, builder, etc.)
- expand mixnet tutorial with persistent identity and split_sender sections
- add tcpproxy tutorial
- rename "API Reference" to "TypeDoc Reference" in TS SDK sidebar
- rename "Misc" to "Extras" in developer sidebar, move VPN CLI up
- remove echo server from tools
- update message-queue callout to reference actual modules
- fix mixnet/examples redirect collision

* Add missing mut to example code

* Update ARCHITECTURE.md with LP Framing + stream examples with sequencing

* Update doc comment in utils.rs

* Standardise commenting style across Rust SDK examples

* Fix inline doc examples and trim re-export boilerplate

* Update sdk/rust/nym-sdk/examples/bandwidth.rs

Co-authored-by: Simon Wicky <simon@nymtech.net>

* Fix review comments

---------

Co-authored-by: Simon Wicky <simon@nymtech.net>
2026-04-10 10:51:38 +00:00
mfahampshire 82ed88e26e Update revs for all tutorials to current release & tweak tutorial (#6659)
* Update revs for all tutorials to current release.

* Update missed rev

* Bump sizes of sent echo messages
2026-04-10 08:01:42 +00:00
mfahampshire 594174827d Minor grammar tweaks (#6658)
* Minor grammar tweaks

* Minor final tweaks to grammar.
2026-04-09 16:55:12 +00:00
import this f6f364c551 Operators: Ansible version syntax and comment update (#6657) 2026-04-09 17:39:41 +02:00
mfahampshire f648349e82 Max/docs-diataxis-ify (#6494)
* Diatixisify!

* First pass at Typedoc generation for TS SDK

* Remove overview pages

* Fix typos and remove codebase references from docs

Fix typos across network and developer docs: Quorum, available,
cryptosystem, transaction, proportional, Standalone. Remove TODO
placeholder from dVPN protocol page. Strip GitHub source links
from network docs to decouple documentation from repo structure.

* Expand thin landing pages across network and developer docs

- Add intro content to network overview, infrastructure, and reference landing pages
- Expand developer index with "where to start" guide
- Add usage instructions and explanations to all five TS playground pages
- Expand WebSocket client page with setup and message format examples

* Restructure Rust SDK developer docs

- Delete redundant mixnet example, message-helpers, and message-types subpages
- Delete client-pool architecture and example subpages (content folded into landing)
- Delete tcpproxy troubleshooting (folded into landing page)
- Add deprecation notices to TcpProxy pages, pointing to Stream module
- Add stream module docs: landing page, architecture, tutorial, and 4 example pages
- Add mixnet and client-pool tutorials
- Add SDK tour page
- Update navigation and landing pages with docs.rs links

* Restructure TS SDK developer docs

- Merge overview, installation, and getting started into TS SDK landing page
- Fold FAQ content into bundling/troubleshooting section
- Delete redundant overview, installation, start, and FAQ pages
- Update internal links in browsers.mdx and native.mdx
- Update navigation and example page imports

* Flatten and expand APIs section

- Collapse nested API subpages into single pages with inline Redoc embeds
- Rewrite introduction as landing page with decision table
- Add endpoint categories, quick curl examples to each API page
- Mark Explorer API as deprecated
- Move NS API deployment guide to operators/performance-and-testing
- Fix dangling /apis/nym-api/mainnet link in network-components
- Remove sandbox endpoints from all API pages

* Add redirects for moved and deleted pages

- Add 25 redirects covering TS SDK, Rust SDK, APIs, and network sections
- Fix dangling /developers/typescript/start link in operators changelog

* Replace individual example doc pages with GitHub-linked tables, expand tutorials

- replace individual example doc pages with GitHub-linked tables
- expand mixnet tutorial with persistent identity and split_sender sections
- add tcpproxy tutorial
- rename "API Reference" to "TypeDoc Reference" in TS SDK sidebar
- rename "Misc" to "Extras" in developer sidebar, move VPN CLI up
- remove echo server from tools
- update message-queue callout to reference actual modules
- fix mixnet/examples redirect collision

* Add SEO frontmatter, validate encryption standards, clean up URLs

- add title/description/schemaType/section/lastUpdated frontmatter to 48
  pages across developers, network, and APIs sections
- remove network/.archive/ directory (compare against develop instead)
- update nymtech.net → nym.com for website/blog links (keep infra URLs)
- add native proxy "in progress" callout for Rust/C/Go

* API-scraper update (#6598)

* read nodes and locations

* update python-prebuild.sh

* Address PR #6494 review feedback
- Use "mode" consistently instead of "role" on nym-nodes page
- Replace "staking" with "bonding" for NYM token collateral
- Wire up auto-scraped node counts via TimeNow + nodes-count.json
- Fix broken licensing images: download CC icons locally, replace inline HTML
- Fix 9 stale redirects pointing through deleted /network/architecture path

* Fix linkcheck errors
- Fix stale cross-links: /network/concepts/ → /network/mixnet-mode/
- Replace README.md references with globals.md in TypeDoc output
- Add entryFileName: globals to typedoc.json configs to prevent recurrence

* Fix remaining stale /network/architecture links
- zk-nym-overview: architecture/nyx#nym-api → /network/infrastructure/nyx#nym-api
- setup: network/architecture → /network/overview

* Remove accidentally re-included architecture.md file from rebase

* Standardize tutorials, document examples, add llms.txt, apply tone fixes

- Expand Rust SDK tutorials with step-by-step structure; document all SDK examples across mixnet, client-pool, and tcpproxy pages
- Add llms.txt generation script, wire into build and CI workflows
- Apply tone/style fixes: deduplicate callouts, vary sentence structure, standardize voice consistency across changed pages

* Consolidate redundant network overview docs

* Trim dev docs: git-first imports, stream notice, collapse TcpProxy

* Update tutorial

* Refresh auto-generated API and command outputs

* Update network section docs

* Update developer and API docs: reusable components, stream protocol, conventions, tutorial fixes

* Fix Rust SDK tutorial bugs: setup_env, port conflicts, logging,
open_stream race condition

* Update stream.mdx

* Remove docs.rs link from Stream overview for the moment

* add llms.txt and llms-full.txt note to readme

---------

Co-authored-by: import this <97586125+serinko@users.noreply.github.com>
2026-04-09 15:25:31 +00:00
import this 4fb78c3737 FIX: add tags to Ansible NTM role (#6656) 2026-04-09 17:15:57 +02:00
Merve f208855bc8 [DOCs/operators]: Release notes for tola release (#6645)
* changelog for tola release

* Add new updates to changelog

* add docs rework to operatos news

* add ansible fix

* bump stats

---------

Co-authored-by: merve <e@E-MacBook-Air.local>
Co-authored-by: serinko <97586125+serinko@users.noreply.github.com>
2026-04-09 15:06:52 +02:00
import this 60426b8c45 NTM: NIP-10 exit policy upgrade (#6648) 2026-04-09 14:41:02 +02:00
benedetta davico 9792a8829b Merge pull request #6646 from nymtech/release/2026.7-tola
merge release/2026.7-tola
2026-04-09 14:18:16 +02:00
import this 5b23429415 Bugfix: Ansible dwl fresh NTM on each run (#6654) 2026-04-09 13:53:08 +02:00
dynco-nym 89de989ad1 Optimize GW probe in NS agent (#6636)
* WIP

* NS agent calls probe as lib

* Clippy: ns agent

* Fix submit_v2 on API

* Adjust dockerfile, deployment details for the new flow

* Bump package versions

* PR feedback

* Fix CI

* Final version
2026-04-07 16:35:50 +02:00
benedettadavico 97068b2aac update changelog 2026-04-07 15:51:44 +02:00
benedetta davico 0e3e5c27f3 Merge pull request #6634 from nymtech/simon/ecash-contract-serde-fix
Simon/ecash contract serde fix
2026-04-01 10:56:27 +02:00
Simon Wicky 01e3c8206b alias not working, adding separate method 2026-03-31 17:32:57 +02:00
Simon Wicky ef20b8c7d1 serde magic on ecash contract 2026-03-31 14:39:34 +02:00
benedetta davico 61af16784b Merge pull request #6632 from nymtech/bdq/ecash-contract-test
small fix to allow ecash migrate
2026-03-30 13:33:34 +02:00
benedettadavico caf21076c9 .. 2026-03-30 10:37:45 +02:00
benedettadavico 1672135308 bump versions 2026-03-30 07:11:55 +02:00
mfahampshire c07ef0253d Max/sdk stream wrapper (#6320)
* Replace MixnetStream with LP framing
- Replace custom header with LpFrameHeader
- Added sequence number for message ordering

* IPR: support LP Stream-framed client connections
- Detect and route LP Stream frames in mixnet_listener
- Wrap inline responses in LP Stream frames
- Thread stream_id to ConnectedClientHandler for TUN responses

* sdk: add ipr_wrapper module with IpMixStream
- IpMixStream wraps MixnetStream for IPR tunnel over mixnet
- LP Stream framing handled automatically by MixnetStream
- Gateway discovery, connect handshake, IP packet send/receive

* sdk: remove superseded stream_wrapper module

* Trim obvious comments, add architecture.md stub

* sdk: add missing deps and fix warnings

* Cut down architecture diagram until finished with rest of the code, leaving stubs

* sdk: refactor IpMixStream, extract shared helpers

- Extract gateway discovery and connect response parsing
- Add recv() to MixnetStream, remove 64KB read buffer
- Simplify IpMixStream constructor

* Fix SphinxStream renames missed during rebase

* Add IpPacketResponse::from_bytes() for stream-based deserialization

* Clean up ip_packet_client: delete stale connect.rs, take raw bytes not ReconstructedMessage

* Clippy

* Delete unused ip_packet_client modules

- Remove helpers.rs (ICMP utilities moved to example)
- Remove error.rs (errors consolidated into sdk/error.rs)
- Remove README.md
- Update module root to only export discovery + listener

* Simplify listener, IpMixStream, and network_env

- Collapse IprListener struct into standalone handle_ipr_response()
- Move check_ipr_message_version() into listener.rs
- Remove IpMixStream test module (moved to example)
- Remove parse_network() and commented-out Sandbox arms
- Return Result from find_workspace_root() instead of panicking
- Add IprTunnelDisconnected and WorkspaceRootNotFound error variants

* Refactor IPR stream handling and document seq conventions
- Inline stream_id tracking (remove current_stream_id field)
- Re-export encode_stream_frame from clients module
- Document seq=0 reservation for inline control responses
- Document data-path counter starting at 1 with skip-on-wrap

* Add ipr_tunnel example for integration testing
- ICMP ping through IPR with --gateway flag for targeting specific exits
- Move pnet_packet from dependencies to dev-dependencies

* Add message reordering to stream router
- Buffer out-of-order messages per-stream using BTreeMap
- Drain contiguous sequences individually to preserve message boundaries
- Drop duplicate/old sequence numbers with a warning
- Remove dead_code allow on StreamFrame::sequence_num

* Clean up comments and fill architecture.md
- Remove separator line comments
- Update stale comments about ordering not being implemented
- Remove collapsible_if allows, use let-else instead
- Fill in architecture.md data flow and connection lifecycle

* Simplify ipr_tunnel example to minimal smoke test
- Single ping instead of multi-ping loop
- Remove identifier and PING_COUNT
- Collapse ICMP helpers into single build_icmp_ping function

* Add dual-stack IPv6 ping and rename gateway → ipr
- Rename --gateway flag to --ipr and new_with_gateway() to new_with_ipr()
- Add ICMPv6 ping to ipr_tunnel example for dual-stack smoke test
- Tighten echo reply validation (protocol field check, diagnostic output)
- Document IP allocation (subnets, static vs dynamic, client keying) in architecture.md
- Promote LP Stream Open handshake log to INFO

* Tweak subnet comment in docs

* Don't stop IPR listener on decode failure
- Change break to continue so garbage packets can't kill the listener
- Remaining valid packets in the bundle are still processed

* Fix license headers and use workspace dep for pnet_packet
- Switch GPL-3.0 to Apache-2.0 on all SDK library files
- Add missing license headers to 7 files
- Use workspace version for pnet_packet dependency

* Document IP pool isolation from WG/LP dVPN pool
- IPR uses 10.0.0.0/16 on nymtun, WG uses 10.1.0.0/16 on nymwg
- Reference constants.rs as source of truth

* Remove network_env.rs and simplify IpMixStream API
  - Default to mainnet via setup_env(None) instead of requiring env param
  - Remove NetworkEnvironment enum and workspace root detection
  - Remove WorkspaceRootNotFound error variant
  - Update ipr_tunnel example to match new signatures

* Use weighted random selection for IPR gateway discovery
  - Replace max_by_key with choose_weighted biased by performance score
  - Prevents all clients converging on a single highest-performing IPR

* Cap stream reorder buffer to prevent unbounded memory growth
- Add MAX_REORDER_BUFFER (256) to limit per-stream pending messages:
	- buffer overflows = skip ahead to lowest buffered seq and drain
	- protects against malicious senders that deliberately skip sequence numbers

* Extract shared IPR response helpers into nym-ip-packet-requests
  - Add response_helpers module with version check, connect response
    parsing, and control response dispatch
  - SDK ip_packet_client now delegates to shared module
  - Monorepo nym-ip-packet-client uses shared version check and
    connect response parsing
  - Fix doc comment attributing fork to nym-vpn-client

* Extract ICMP test helpers into nym-ip-packet-requests
  - Add icmp_utils module behind test-utils feature flag
  - Move build_icmp_ping, build_icmpv6_ping, is_echo_reply_v4/v6 from
    example
  - Update ipr_tunnel example to use shared helpers

* Add protocol v9 LP-framed transport marker

- Add v9 module (re-exports v8, VERSION=9)
- Accept v9 requests and responses in IPR
- Switch SDK IpMixStream to send v9

* Log protocol version in dynamic connect requests

* Remove KCP from IPR and fix unwrap_or_default in SDK
- Remove all KCP session management from ip-packet-router (replaced by
  LP Stream framing)
- Drop nym-kcp dependency and KcpError variant from IPR
- Replace unwrap_or_default with ok_or(Error::NoNymAPIUrl) in
  IpMixStream::new()

* Add v9 protocol wrapper constructors and enforce version/transport
consistency
- Add v9::new_connect_request(), new_data_request(),
  new_ip_packet_response() to centralise version stamping
- Replace manual protocol.version overrides in SDK and IPR with v9
  wrapper calls
- Bump nym-ip-packet-client current re-export from v8 to v9
- Enforce LP Stream frames must carry v9+ payloads, non-stream must be
  v8 or lower

* Filter IPR exit nodes by minimum v9-compatible release version
- Define MIN_RELEASE_VERSION (1.30.0) in ip-packet-requests/v9 alongside protocol constants
- Add semver-based filtering in SDK gateway discovery to skip nodes below v9 threshold
- Add semver dependency to ip-packet-requests and nym-sdk

* Use numeric version comparison for transport/version enforcement
- Compare version as u8 instead of enum equality so future v10+ is handled correctly
- Remove unused `use super::*` import left over from KCP test removal
2026-03-27 20:35:26 +00:00
benedetta davico cc799b69d3 Merge pull request #6622 from nymtech/jmwample/fallback-nym-ip
Update Fallback IP for Nym API
2026-03-27 10:06:13 +01:00
jmwample dd4bbc0708 nym-api moved default 2026-03-26 11:36:04 -06:00
Jack Wampler 7b77091fb1 Nym Node spam logging (#6621)
prevent spam logs when downstream node is slow
2026-03-26 11:27:14 -06:00
Jędrzej Stuczyński 6581ebf235 feat: multiple deposit prices (#6608)
* added reduced pricing handling logic

* admin methods for setting the whitelist of reduced price accounts

* updated client traits

* query to get all whitelisted accounts

* query for getting detailed deposit statistics

* fixes

* set initial whitelisted accounts in the migration

* stop transferring tokens to the holding account after redemption

* stop gateways from creating redemption multisig proposals

* make sure credential-proxy uses reduced deposits when available

* cargo fmt

* update deposit handler to allow EITHER default price or reduced price

this will allow non-breaking upgrades of NS and credential proxy

* removed use of unstable rust features

* rebuilt contract schema

* correct license timestamp
2026-03-26 16:02:19 +00:00
benedetta davico 82ace6d27b Merge pull request #6611 from nymtech/master
Keep master and develop in sync
2026-03-26 16:07:36 +01:00
import this e362207583 [DOCs/operators]: Fix - disable ufw to clean machine conf state (#6620) 2026-03-26 12:27:57 +01:00
import this 68caecff35 [DOCs]: Release notes v2026.6 stilton (#6606)
* operators updates

* add headers

* Update changelog.mdx

* bump up node version

* udpate time

* edit typos

---------

Co-authored-by: Merve <111695676+merve64@users.noreply.github.com>
2026-03-26 11:02:10 +01:00
import this 2fae4414d2 NTM Update: single port managment tool (#6607)
* update ntm

* update docs

* add table for ports

* cherry on the cake

* polish ntm

* quic cherry - add 4443
2026-03-26 10:18:32 +01:00
benedetta davico 6eca09b904 Merge pull request #6610 from nymtech/release/2026.6-stilton
Merge stilton to master
2026-03-25 17:09:28 +01:00
benedetta davico 7ab821cb11 Merge pull request #6609 from nymtech/release/2026.6-stilton
Merge stilton to develop
2026-03-25 17:09:16 +01:00
mfahampshire 9904f6b17c Make mobile friendly (#6605)
- Add overflow:hidden on grid
- Shrink `pre` font on mobile
- Stack grid on narrow pages
2026-03-24 21:56:15 +00:00
mfahampshire 5e0eeeddd6 hotfix (#6603) 2026-03-24 15:32:30 +00:00
mfahampshire b6df383584 Max/docs theme rework (#6593)
* Rawer landing page
- Angular, clean docs styling inspired by Oxide
- zero all border-radius globally (kill rounded corners)
- sharp code blocks with subtle border
- callouts: left-border accent instead of rounded pill
- clean table grid lines, sharp search box and MUI buttons
- tighter heading letter-spacing (-0.02em)
- flat left-border sidebar active item instead of background blob

* Add JetBrains Mono for headings/sidebar, push Oxide styling further
- import JetBrains Mono via Google Fonts
- apply mono font to headings, sidebar, nav bar, search, table headers
- darken background (#181C1E), muted body text, h2 bottom border
- subtle background tint on active sidebar item
- inline code: background-only (no border), monospace table headers
- fix active sidebar item font size (scope separator label rule)

* Rework docs landing page: hero, ASCII cards, SDKs, get started
- add hero section with subtitle covering all doc areas
- replace PNG vector illustrations with ASCII art in primary green
- add SDKs section with Rust and TypeScript links
- add get started section: What is the Mixnet, Send a message, Run a node
- add footer links to GitHub and Matrix
- fix nav dropdown font (button + ul selectors)
- add landing card hover style

* Self-host JetBrains Mono, refine landing page
- replace Google Fonts import with local @font-face (woff2)
- add font files + OFL license to public/fonts/
- remove redundant "Nym Docs" hero heading (already in nav)
- drop quick-links pills section
- fix SDK box borders (negative margin collapse)
- rewrite footer as simple link row (GitHub, Matrix, nym.com)

* Light mode styling, dark-mode diagram invert, click-to-expand images
- add full light mode CSS: pale grey bg, darker green links, mono fonts
- invert diagram images in dark mode with mix-blend-mode: lighten
- add click-to-expand overlay for content images
- revert mermaid diagrams back to original PNGs

* Fix Lychee config for fonts

* Make light mode green darker

* Animate landing page ASCII art, remove architecture diagram

- Network: animated packet traversal through gw_e → M1/M2/M3 → gw_ex
  with diagonal cross-connections showing mixing paths
- Developers: typewriter effect with blinking cursor
- Operators: looping progress bar with continuously incrementing packet count
- APIs: staged line-by-line response reveal
- Remove architecture overview PNG from network/architecture.mdx

* Small copy change to SDK headers

* Fix links
2026-03-24 15:08:07 +00:00
benedetta davico 3f00e2c317 Merge pull request #6592 from nymtech/bdq/bump-ns-version
bump NS versions
2026-03-20 15:37:18 +01:00
benedettadavico 3cdda8fdfd bump NS version 2026-03-20 15:33:16 +01:00
benedetta davico 33f47ef36e Merge pull request #6591 from nymtech/release/2026.6-stilton
merge stilton to develop
2026-03-20 15:30:48 +01:00
dynco-nym 180802feb8 Fix socks5 GW probe regression (#6576)
* Restore tested gateway into topology

* Bump agent version

* Update .sqlx files

* Clean up code in probe test

* Probe error & logging improvements

* Fix clippy, improve log line

* Improve logging in ns agent

* Better tooling in NS API

* Bump agent

* Bump NS agent version
2026-03-20 10:36:32 +01:00
mfahampshire 4077717d3a Max/lp stream framing (#6573)
* Add LpFrameKind::Stream variant with StreamFrameAttributes
- Define LP wire format for stream multiplexing
- Handle new variant in entry gateway match arm

* Replace MixnetStream with LP framing
- Replace custom header with LpFrameHeader
- Added sequence number for message ordering

* Revert accidental vergen bump

* Revert accidental bumps

* Rename Stream to SphinxStream and split match arms in client_handler

* Add LpFrameAttributes type alias for [u8; 14]
2026-03-19 15:30:59 +00:00
Simon Wicky bc3df31518 move format_debug_bytes in common crate (#6580)
* move format_debug_bytes in common crate

* license change
2026-03-19 15:09:20 +01:00
Jack Wampler 61d6acace8 HTTP domain rotation conditions (#6570)
Add more explicit handling for df enable and domain rotations
2026-03-19 07:38:48 -06:00
Jędrzej Stuczyński abb4e3f988 bugfix: make sure client keys are generated before requesting credentials (#6579) 2026-03-19 08:55:00 +00:00
mfahampshire c5488337da Max/mixfetch docs tweak (#6523)
* update mixfetch concurrency info

* Update MixFetch version + update note

* Update python3 install method on docs runners
2026-03-18 14:23:51 +00:00
mfahampshire f06eefe184 Only publish mixfetch in script (#6560) 2026-03-18 14:01:24 +00:00
benedetta davico e86fa8fc7f Merge pull request #6537 from nymtech/release/2026.5-raclette
Raclette to master
2026-03-10 12:07:12 +01:00
696 changed files with 42252 additions and 7848 deletions
+3 -3
View File
@@ -15,10 +15,8 @@ jobs:
- uses: actions/checkout@v6
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
- name: Install pip3
run: sudo apt install -y python3-pip
- name: Install Python3 modules
run: sudo pip3 install pandas tabulate
run: sudo apt install -y python3-pandas python3-tabulate
- name: Install rsync
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
@@ -41,6 +39,8 @@ jobs:
- name: Install project dependencies
run: pnpm i
- name: Generate llms-full.txt
run: pnpm run generate:llms
- name: Build project
run: pnpm run build
- name: Generate sitemap
@@ -36,7 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-22.04]
platform: [arc-ubuntu-22.04]
runs-on: ${{ matrix.platform }}
env:
+5 -4
View File
@@ -90,7 +90,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets --exclude nym-gateway-probe --exclude nym-node-status-api -- -D warnings
args: --workspace --all-targets --exclude nym-gateway-probe --exclude nym-node-status-api --exclude nym-node-status-agent --exclude nym-node-status-client -- -D warnings
- name: Clippy (non-macos)
if: contains(matrix.os, 'linux') || contains(matrix.os, 'windows')
@@ -104,14 +104,15 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --exclude nym-gateway-probe --exclude nym-node-status-api --exclude nym-node-status-agent --exclude nym-node-status-client
# only build on linux because of wg FFI bindings of its dependency (network probe)
- name: Build nym-node-status-api (linux only)
# Build Go FFI-dependent crates separately (requires Go, only available on Linux CI)
- name: Build nym-node-status-api and nym-node-status-agent (linux only)
if: runner.os == 'Linux'
uses: actions-rs/cargo@v1
with:
command: build
args: -p nym-node-status-api
args: -p nym-node-status-api -p nym-node-status-agent
- name: Build all examples
if: contains(matrix.os, 'linux')
@@ -15,6 +15,9 @@ env:
jobs:
publish-dry-run:
runs-on: arc-linux-latest
timeout-minutes: 35
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
@@ -59,20 +62,60 @@ jobs:
- name: Bump versions (local only)
run: |
cargo workspaces version custom ${{ inputs.version }} \
--allow-branch ${{ github.ref_name }} \
--no-git-commit \
--yes
- name: Preflight publish checks
run: |
python3 tools/internal/check_publish_preflight.py
# Dry run may show cascading dependency errors because packages aren't
# actually uploaded - these are expected and ignored. We check for real
# errors like packaging failures, missing metadata, or invalid Cargo.toml.
- name: Publish (dry run)
run: |
output=$(cargo workspaces publish --dry-run --allow-dirty 2>&1) || true
echo "$output"
set +e
publish_status=1
max_attempts=2
attempt=1
rm -f /tmp/publish-dry-run.log
# Check for real errors (not cascading dependency errors)
# Cascading errors mention "crates.io index", real errors mention "Cargo.toml"
echo "$output" | grep -i "Cargo.toml" && exit 1 || true
while [ "$attempt" -le "$max_attempts" ]; do
echo "Dry-run publish attempt ${attempt}/${max_attempts}"
cargo workspaces publish --dry-run --allow-dirty 2>&1 | tee /tmp/publish-dry-run.log
publish_status=${PIPESTATUS[0]}
if [ "$publish_status" -eq 0 ]; then
break
fi
# Retry once for interruption/runner issues.
if [ "$attempt" -lt "$max_attempts" ] && \
{ [ "$publish_status" -eq 130 ] || [ "$publish_status" -eq 137 ]; }; then
echo "Publish dry-run interrupted (exit ${publish_status}), retrying in 10s..."
sleep 10
attempt=$((attempt + 1))
continue
fi
break
done
set -e
if grep -Eiq \
"failed to verify manifest|failed to parse manifest|invalid Cargo.toml|error: package .* has no (description|license|repository)" \
/tmp/publish-dry-run.log; then
echo "Detected real packaging/manifest errors"
exit 1
fi
# In dry-run mode, non-zero publish status is expected due to
# dependency-cascade failures against crates.io index.
if [ "$publish_status" -ne 0 ]; then
echo "Dry-run publish returned non-zero (${publish_status}) but no real manifest blockers were detected."
fi
echo "Only expected dry-run dependency cascade errors detected (if any)."
# Show the list of packages published
- name: Show package versions
@@ -17,6 +17,8 @@ on:
jobs:
publish:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
+2
View File
@@ -17,6 +17,8 @@ on:
jobs:
publish:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
@@ -15,6 +15,8 @@ env:
jobs:
version-bump:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
permissions:
contents: write
steps:
+22 -3
View File
@@ -6,6 +6,8 @@ on:
branches-ignore: [master]
paths:
- "documentation/docs/**"
- "sdk/typescript/packages/sdk/src/**"
- "sdk/typescript/packages/mix-fetch/src/**"
- ".github/workflows/ci-docs.yml"
jobs:
@@ -20,10 +22,8 @@ jobs:
- uses: actions/checkout@v6
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler git python3 && sudo apt-get update --fix-missing
- name: Install pip3
run: sudo apt install -y python3-pip
- name: Install Python3 modules
run: sudo pip3 install pandas tabulate
run: sudo apt install -y python3-pandas python3-tabulate
- name: Install rsync
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
@@ -44,8 +44,27 @@ jobs:
command: build
args: --workspace --release
- name: Check if TypeScript SDK source changed
id: check-ts-sdk
run: |
if git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep -qE '^sdk/typescript/packages/(sdk|mix-fetch)/src/'; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
working-directory: ${{ github.workspace }}
- name: Regenerate TypeDoc API reference
if: steps.check-ts-sdk.outputs.changed == 'true'
run: |
npm install -g typedoc@0.25.13 typedoc-plugin-markdown@4.0.3
cd ${{ github.workspace }}/sdk/typescript/packages/sdk && typedoc --skipErrorChecking
cd ${{ github.workspace }}/sdk/typescript/packages/mix-fetch && typedoc --skipErrorChecking
- name: Install project dependencies
run: pnpm i
- name: Generate llms-full.txt
run: pnpm run generate:llms
- name: Build project
run: pnpm run build
- name: Generate sitemap
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: arc-ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
@@ -25,6 +25,10 @@ jobs:
- name: Install cargo-workspaces
run: cargo install cargo-workspaces
- name: Preflight publish checks
run: |
python3 tools/internal/check_publish_preflight.py
- name: Publish remaining crates
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+50
View File
@@ -4,6 +4,56 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2026.9-venaco] (2026-05-06)
- Fix for v9 IPR ([#6710])
- Only init SHARED_CLIENT if requested ([#6708])
- Fixes to crates and CI ([#6686])
- Return ipv6 addresses as well ([#6684])
- Fix invalid ticket spend ([#6683])
- Block non-public IPR/NR checks ([#6670])
[#6710]: https://github.com/nymtech/nym/pull/6710
[#6708]: https://github.com/nymtech/nym/pull/6708
[#6686]: https://github.com/nymtech/nym/pull/6686
[#6684]: https://github.com/nymtech/nym/pull/6684
[#6683]: https://github.com/nymtech/nym/pull/6683
[#6670]: https://github.com/nymtech/nym/pull/6670
## [2026.8-urda] (2026-04-20)
- Include all gateways in the returned list ([#6649])
- Optimize GW probe in NS agent ([#6636])
- Max/sdk docrs ([#6566])
- Max/sdk stream wrapper ([#6320])
[#6649]: https://github.com/nymtech/nym/pull/6649
[#6636]: https://github.com/nymtech/nym/pull/6636
[#6566]: https://github.com/nymtech/nym/pull/6566
[#6320]: https://github.com/nymtech/nym/pull/6320
## [2026.7-tola] (2026-04-07)
- Simon/ecash contract serde fix ([#6634])
- Update Fallback IP for Nym API ([#6622])
- Nym Node spam logging ([#6621])
- feat: multiple deposit prices ([#6608])
- move format_debug_bytes in common crate ([#6580])
- bugfix: make sure client keys are generated before requesting credentials ([#6579])
- Fix socks5 GW probe regression ([#6576])
- Max/lp stream framing ([#6573])
- HTTP domain rotation conditions ([#6570])
[#6634]: https://github.com/nymtech/nym/pull/6634
[#6622]: https://github.com/nymtech/nym/pull/6622
[#6621]: https://github.com/nymtech/nym/pull/6621
[#6608]: https://github.com/nymtech/nym/pull/6608
[#6580]: https://github.com/nymtech/nym/pull/6580
[#6579]: https://github.com/nymtech/nym/pull/6579
[#6576]: https://github.com/nymtech/nym/pull/6576
[#6573]: https://github.com/nymtech/nym/pull/6573
[#6570]: https://github.com/nymtech/nym/pull/6570
## [2026.6-stilton] (2026-03-25)
- lp fixes ([#6601])
Generated
+219 -64
View File
@@ -1691,7 +1691,8 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core-models"
version = "0.0.5"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "657f625ff361906f779745d08375ae3cc9fef87a35fba5f22874cf773010daf4"
dependencies = [
"hax-lib",
"pastey 0.2.1",
@@ -2350,6 +2351,47 @@ dependencies = [
"x25519-dalek",
]
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [
"defmt 1.0.1",
]
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "delegate-display"
version = "3.0.0"
@@ -2534,7 +2576,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -3398,6 +3440,15 @@ dependencies = [
"serde_json",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -3523,6 +3574,16 @@ dependencies = [
"http 1.3.1",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.4.1"
@@ -3589,7 +3650,7 @@ dependencies = [
"once_cell",
"rand 0.9.2",
"ring",
"rustls 0.23.29",
"rustls 0.23.37",
"thiserror 2.0.12",
"tinyvec",
"tokio",
@@ -3614,7 +3675,7 @@ dependencies = [
"parking_lot",
"rand 0.9.2",
"resolv-conf",
"rustls 0.23.29",
"rustls 0.23.37",
"smallvec",
"thiserror 2.0.12",
"tokio",
@@ -3871,7 +3932,7 @@ dependencies = [
"http 1.3.1",
"hyper 1.6.0",
"hyper-util",
"rustls 0.23.29",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustls-pki-types",
"tokio",
@@ -4611,7 +4672,8 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libcrux-aesgcm"
version = "0.0.7"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99f2a019dab4097585a7d4f5b9deebe46cd1e628b16a5bc4cb0ce35e1da334e6"
dependencies = [
"libcrux-intrinsics",
"libcrux-platform",
@@ -4621,8 +4683,9 @@ dependencies = [
[[package]]
name = "libcrux-chacha20poly1305"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc08d044676af21343b32b988411fa98dbb5cf65a03c9df478ced221bbdfdb1b"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4634,7 +4697,8 @@ dependencies = [
[[package]]
name = "libcrux-curve25519"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1e5fd8476a6ed609d24ef42aee5ab6f99f7c65d054f92412da9f499e423299"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4645,7 +4709,8 @@ dependencies = [
[[package]]
name = "libcrux-ecdh"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65f73ce79337c762eb38bbac91e4c9b9e60cf318e8501b812750c640814d45e"
dependencies = [
"libcrux-curve25519",
"libcrux-p256",
@@ -4655,8 +4720,9 @@ dependencies = [
[[package]]
name = "libcrux-ed25519"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835919315b7042fe9e03b6458efe0db94bf2aa7b873934dbee5b5463a8124b43"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4668,7 +4734,8 @@ dependencies = [
[[package]]
name = "libcrux-hacl-rs"
version = "0.0.4"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2637dc87d158e1f1b550fd9b226443e84153fded4de69028d897b534d16d22e6"
dependencies = [
"libcrux-macros",
]
@@ -4676,7 +4743,8 @@ dependencies = [
[[package]]
name = "libcrux-hkdf"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1a89ca0c89be3a268a921e47105fb7873badf7267f5e3ebf4ea46baedd73ef"
dependencies = [
"libcrux-hacl-rs",
"libcrux-hmac",
@@ -4686,7 +4754,8 @@ dependencies = [
[[package]]
name = "libcrux-hmac"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7a242707d65960770bd7e14e4f18a92bdf0b967777dd404887db8d087a643b"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4696,7 +4765,8 @@ dependencies = [
[[package]]
name = "libcrux-intrinsics"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1b5db005ff8001e026b73a6842ee81bbef8ec5ff0e1915a67ae65fd2a9fafa5"
dependencies = [
"core-models",
"hax-lib",
@@ -4704,8 +4774,9 @@ dependencies = [
[[package]]
name = "libcrux-kem"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12631592f491d22fd1a176d32b2c6edfb673998fd3987e9d95f8fa79ad2a737b"
dependencies = [
"libcrux-curve25519",
"libcrux-ecdh",
@@ -4720,7 +4791,8 @@ dependencies = [
[[package]]
name = "libcrux-macros"
version = "0.0.3"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338"
dependencies = [
"quote",
"syn 2.0.106",
@@ -4728,8 +4800,9 @@ dependencies = [
[[package]]
name = "libcrux-ml-dsa"
version = "0.0.7"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a72929ed421cc3bf16a946b3e7d2a58d215b0b5c2a12be26b53629f081bf49b2"
dependencies = [
"core-models",
"hax-lib",
@@ -4742,8 +4815,9 @@ dependencies = [
[[package]]
name = "libcrux-ml-kem"
version = "0.0.7"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a14ab3e477de9df6ee1273a114018ff62c4996ca9220070c4e5cb1743f94a67d"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
@@ -4758,7 +4832,8 @@ dependencies = [
[[package]]
name = "libcrux-p256"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4778ba25cb08bb8a96bd100e19ed9aecf78337198fd176036e21042b2dd99bc"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4770,15 +4845,17 @@ dependencies = [
[[package]]
name = "libcrux-platform"
version = "0.0.3"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9e21d7ed31a92ac539bd69a8c970b183ee883872d2d19ce27036e24cb8ecc4"
dependencies = [
"libc",
]
[[package]]
name = "libcrux-poly1305"
version = "0.0.4"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02491808ee5b9db8cb65fad64ae0be812db64beef179d945c00c7787dc7dfcf9"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4786,8 +4863,9 @@ dependencies = [
[[package]]
name = "libcrux-psq"
version = "0.0.7"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779ade7aa5e1b4b400c716b313cbf69070988dd005f92e961c2da4c3c42fbea4"
dependencies = [
"classic-mceliece-rust",
"libcrux-aesgcm",
@@ -4809,7 +4887,8 @@ dependencies = [
[[package]]
name = "libcrux-secrets"
version = "0.0.5"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce650f3041b44ba40d4263852347d007cd2cd9d1cc856a6f6c8b2e10c3fd40b"
dependencies = [
"hax-lib",
]
@@ -4817,7 +4896,8 @@ dependencies = [
[[package]]
name = "libcrux-sha2"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9d253473f259fc74a280c43f29c464f7e374abdf28b4942234dc707f529d4b7"
dependencies = [
"libcrux-hacl-rs",
"libcrux-macros",
@@ -4826,8 +4906,9 @@ dependencies = [
[[package]]
name = "libcrux-sha3"
version = "0.0.7"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
version = "0.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1ae0b7d0e1cc4793a609fd0ff2ca3b3a3fabae523770c619a3d4bc86417b0d7"
dependencies = [
"hax-lib",
"libcrux-intrinsics",
@@ -4838,7 +4919,8 @@ dependencies = [
[[package]]
name = "libcrux-traits"
version = "0.0.6"
source = "git+https://github.com/cryspen/libcrux?rev=b17f8687b67cdcfc10b55aeecc998bbbca28f775#b17f8687b67cdcfc10b55aeecc998bbbca28f775"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e4fa89f3f5e34b47f928b22b1b78395a0d4ec23b1f583db635f128159d65f"
dependencies = [
"libcrux-secrets",
"rand 0.9.2",
@@ -5047,6 +5129,12 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "managed"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
[[package]]
name = "maplit"
version = "1.0.2"
@@ -5448,7 +5536,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -5547,7 +5635,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.76"
version = "1.1.79"
dependencies = [
"anyhow",
"async-trait",
@@ -5792,7 +5880,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.73"
version = "1.1.76"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5875,7 +5963,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.73"
version = "1.1.76"
dependencies = [
"bs58",
"clap",
@@ -6687,6 +6775,7 @@ version = "1.20.4"
dependencies = [
"anyhow",
"base64 0.22.1",
"bs58",
"bytes",
"clap",
"futures",
@@ -6711,7 +6800,6 @@ dependencies = [
"nym-lp",
"nym-network-defaults",
"nym-node-requests",
"nym-node-status-client",
"nym-registration-client",
"nym-registration-common",
"nym-sdk",
@@ -6849,6 +6937,7 @@ dependencies = [
"nym-network-defaults",
"once_cell",
"reqwest 0.13.1",
"rustls 0.23.37",
"serde",
"serde_json",
"serde_plain",
@@ -6939,6 +7028,7 @@ dependencies = [
"bytes",
"futures",
"nym-ip-packet-requests",
"nym-lp",
"nym-sdk",
"thiserror 2.0.12",
"tokio",
@@ -6956,12 +7046,15 @@ dependencies = [
"nym-crypto",
"nym-service-provider-requests-common",
"nym-sphinx",
"pnet_packet",
"rand 0.8.5",
"semver 1.0.27",
"serde",
"thiserror 2.0.12",
"time",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
@@ -6984,7 +7077,7 @@ dependencies = [
"nym-exit-policy",
"nym-id",
"nym-ip-packet-requests",
"nym-kcp",
"nym-lp",
"nym-network-defaults",
"nym-network-requester",
"nym-sdk",
@@ -7086,6 +7179,7 @@ dependencies = [
"criterion",
"libcrux-psq",
"num_enum",
"nym-common",
"nym-crypto",
"nym-kkt",
"nym-kkt-ciphersuite",
@@ -7279,7 +7373,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.74"
version = "1.1.77"
dependencies = [
"addr",
"anyhow",
@@ -7329,7 +7423,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.28.0"
version = "1.31.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -7466,14 +7560,16 @@ dependencies = [
[[package]]
name = "nym-node-status-agent"
version = "1.1.3"
version = "2.0.0"
dependencies = [
"anyhow",
"clap",
"futures",
"nym-bin-common",
"nym-crypto",
"nym-gateway-probe",
"nym-node-status-client",
"nym-sdk",
"rand 0.8.5",
"regex",
"serde_json",
@@ -7485,7 +7581,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "4.3.0"
version = "4.6.1"
dependencies = [
"ammonia",
"anyhow",
@@ -7550,9 +7646,9 @@ version = "0.3.0"
dependencies = [
"anyhow",
"bincode",
"bs58",
"nym-credentials",
"nym-crypto",
"nym-gateway-probe",
"reqwest 0.13.1",
"serde",
"serde_json",
@@ -7790,6 +7886,8 @@ dependencies = [
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-ip-packet-requests",
"nym-lp",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-service-providers-common",
@@ -7802,8 +7900,10 @@ dependencies = [
"nym-topology",
"nym-validator-client",
"parking_lot",
"pnet_packet",
"rand 0.8.5",
"reqwest 0.13.1",
"semver 1.0.27",
"serde",
"tap",
"tempfile",
@@ -7875,7 +7975,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.73"
version = "1.1.76"
dependencies = [
"bs58",
"clap",
@@ -8143,7 +8243,7 @@ dependencies = [
"tempfile",
"tokio",
"tracing",
"tracing-subscriber",
"tracing-test",
"windows 0.61.3",
]
@@ -8673,7 +8773,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.38"
version = "0.1.41"
dependencies = [
"anyhow",
"bytes",
@@ -9652,7 +9752,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.29",
"rustls 0.23.37",
"socket2 0.5.10",
"thiserror 2.0.12",
"tokio",
@@ -9673,7 +9773,7 @@ dependencies = [
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls 0.23.29",
"rustls 0.23.37",
"rustls-pki-types",
"slab",
"thiserror 2.0.12",
@@ -9940,7 +10040,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.29",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustls-pki-types",
"serde",
@@ -9981,7 +10081,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.29",
"rustls 0.23.37",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
@@ -10241,16 +10341,16 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.29"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki 0.103.4",
"rustls-webpki 0.103.9",
"subtle 2.6.1",
"zeroize",
]
@@ -10331,14 +10431,14 @@ dependencies = [
"jni",
"log",
"once_cell",
"rustls 0.23.29",
"rustls 0.23.37",
"rustls-native-certs 0.8.3",
"rustls-platform-verifier-android",
"rustls-webpki 0.103.4",
"rustls-webpki 0.103.9",
"security-framework 3.6.0",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -10370,9 +10470,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.103.4"
version = "0.103.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
dependencies = [
"aws-lc-rs",
"ring",
@@ -11063,6 +11163,47 @@ dependencies = [
"serde_core",
]
[[package]]
name = "smolmix"
version = "0.0.1"
dependencies = [
"futures",
"hickory-proto",
"hickory-resolver",
"http-body-util",
"hyper 1.6.0",
"hyper-util",
"nym-bin-common",
"nym-ip-packet-requests",
"nym-sdk",
"reqwest 0.13.1",
"rustls 0.23.37",
"smoltcp",
"thiserror 2.0.12",
"tokio",
"tokio-rustls 0.26.2",
"tokio-smoltcp",
"tokio-tungstenite",
"tracing",
"webpki-roots 0.26.11",
]
[[package]]
name = "smoltcp"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"cfg-if",
"defmt 0.3.100",
"heapless",
"libc",
"log",
"managed",
]
[[package]]
name = "snafu"
version = "0.7.5"
@@ -11203,7 +11344,7 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
"rustls 0.23.29",
"rustls 0.23.37",
"serde",
"serde_json",
"sha2 0.10.9",
@@ -12025,10 +12166,24 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls 0.23.29",
"rustls 0.23.37",
"tokio",
]
[[package]]
name = "tokio-smoltcp"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f5d53da1c3095663a8900d86c2abb0ffe02d3f6aa86527b066148fcb33e65e"
dependencies = [
"futures",
"parking_lot",
"pin-project-lite",
"smoltcp",
"tokio",
"tokio-util",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
@@ -12843,7 +12998,7 @@ dependencies = [
"serde",
"tempfile",
"textwrap",
"toml 0.8.23",
"toml 0.9.12+spec-1.1.0",
"uniffi_internal_macros 0.31.0",
"uniffi_meta 0.31.0",
"uniffi_pipeline 0.31.0",
@@ -12952,7 +13107,7 @@ dependencies = [
"quote",
"serde",
"syn 2.0.106",
"toml 0.8.23",
"toml 0.9.12+spec-1.1.0",
"uniffi_meta 0.31.0",
]
+16 -9
View File
@@ -147,6 +147,7 @@ members = [
"sdk/ffi/go",
"sdk/ffi/shared",
"sdk/rust/nym-sdk",
"smolmix/core",
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
@@ -184,7 +185,6 @@ default-members = [
"nym-api",
"nym-credential-proxy/nym-credential-proxy",
"nym-node",
"nym-node-status-api/nym-node-status-agent",
"nym-statistics-api",
"nym-validator-rewarder",
"nyx-chain-watcher",
@@ -280,6 +280,7 @@ getrandom03 = { package = "getrandom", version = "=0.3.3" }
glob = "0.3"
handlebars = "3.5.5"
hex = "0.4.3"
hickory-proto = "0.25.2"
hickory-resolver = "0.25.2"
hkdf = "0.12.3"
hmac = "0.12.1"
@@ -334,6 +335,7 @@ rayon = "1.5.1"
regex = "1.10.6"
reqwest = { version = "0.13.1", default-features = false }
rs_merkle = "1.5.0"
rustls = { version = "0.23.37", default-features = false }
schemars = "0.8.22"
semver = "1.0.26"
serde = "1.0.219"
@@ -347,6 +349,8 @@ serde_yaml = "0.9.25"
serde_plain = "1.0.2"
sha2 = "0.10.3"
si-scale = "0.2.3"
smolmix = { version = "0.0.1", path = "smolmix/core" }
smoltcp = "0.12"
snow = "0.9.6"
sphinx-packet = "=0.6.0"
sqlx = "0.8.6"
@@ -367,6 +371,8 @@ tokio-postgres = "0.7"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-rustls = "0.26"
tokio-smoltcp = "0.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.15"
toml = "0.8.22"
@@ -398,14 +404,14 @@ prometheus = { version = "0.14.0" }
# libcrux
libcrux-kem = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-ecdh = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-curve25519 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-chacha20poly1305 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-psq = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-ml-kem = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-sha3 = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-traits = { git = "https://github.com/cryspen/libcrux", rev = "b17f8687b67cdcfc10b55aeecc998bbbca28f775" }
libcrux-kem = "0.0.7"
libcrux-ecdh = "0.0.6"
libcrux-curve25519 = "0.0.6"
libcrux-chacha20poly1305 = "0.0.7"
libcrux-psq = "0.0.8"
libcrux-ml-kem = "0.0.8"
libcrux-sha3 = "0.0.8"
libcrux-traits = "0.0.8"
# Workspace dep definitions required by crates.io publication - we need a workspace version since `cargo workspaces` doesn't work with path imports from crate manifests
nym-api-requests = { version = "1.20.4", path = "nym-api/nym-api-requests" }
@@ -559,6 +565,7 @@ wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4.49"
wasm-bindgen-test = "0.3.49"
wasmtimer = "0.4.1"
webpki-roots = "0.26"
web-sys = "0.3.76"
# for local development:
@@ -60,7 +60,8 @@ packages:
## SYSTEM MAINTENANCE PLAYBOOK KNOBS
###############################################################################
# nym_version: "v2025.21-mozzarella"
# To use particular version instead of Latest, provide in such form:
# nym_version: "nym-binaries-v2026.7-tola"
## NOTE:
## if you want to pin Nym to a specific version instead of using the
@@ -117,4 +118,4 @@ packages:
# enable_writeback_tuning: true
# writeback_dirty_writeback_centisecs: 1500
# writeback_dirty_expire_centisecs: 6000
# writeback_dirty_expire_centisecs: 6000
+25 -6
View File
@@ -1,11 +1,30 @@
---
- name: Configure tunnel manager
- name: Ensure nym binaries directory exists
file:
path: /root/nym-binaries
state: directory
mode: "0755"
tags:
- tunnel
- network_tunnel_manager
become: true
command:
cmd: "/root/nym-binaries/network-tunnel-manager.sh {{ item }}"
- ntm
- name: Download network tunnel manager
get_url:
url: "{{ tunnel_manager_url }}"
dest: /root/nym-binaries/network-tunnel-manager.sh
mode: "0755"
force: yes
tags:
- tunnel
- network_tunnel_manager
- ntm
- name: Run network tunnel manager
command: "/root/nym-binaries/network-tunnel-manager.sh {{ item }}"
loop:
- complete_networking_configuration
register: tunnel_mgr
failed_when: false
tags:
- tunnel
- network_tunnel_manager
- ntm
+3 -3
View File
@@ -1,11 +1,11 @@
[package]
name = "nym-client"
version = "1.1.73"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
version = "1.1.76"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.85"
license.workspace = true
rust-version = "1.85"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-client-websocket-requests"
description = "Request and response definitions for Nym client websocket connections"
version.workspace = true
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "Request and response definitions for Nym client websocket connections"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+3 -3
View File
@@ -1,11 +1,11 @@
[package]
name = "nym-socks5-client"
version = "1.1.73"
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"
version = "1.1.76"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.85"
license.workspace = true
rust-version = "1.85"
publish = false
[dependencies]
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-async-file-watcher"
description = "Simple file watcher that sends a notification whenever there was any change in the watched file"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
description = "Simple file watcher that sends a notification whenever there was any change in the watched file"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+6 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-authenticator-requests"
description = "Crate defining requests and responses for the Nym authenticator client"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Crate defining requests and responses for the Nym authenticator client"
rust-version.workspace = true
readme.workspace = true
publish = true
[dependencies]
base64 = { workspace = true }
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-bandwidth-controller"
description = "Crate for controlling the use of zknym credentials to ensure constant bandwidth availability for NymVPN app"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Crate for controlling the use of zknym credentials to ensure constant bandwidth availability for NymVPN app"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -34,7 +34,7 @@ where
let signing_key = ed25519::PrivateKey::new(&mut rng);
let expiration = expiration.unwrap_or_else(ecash_default_expiration_date);
let deposit_amount = client.get_required_deposit_amount().await?;
let deposit_amount = client.get_default_deposit_amount().await?;
info!("we'll need to deposit {deposit_amount} to obtain the ticketbook");
let result = client
.make_ticketbook_deposit(
+10
View File
@@ -25,6 +25,8 @@ pub trait BandwidthTicketProvider: Send + Sync {
) -> Result<PreparedCredential, BandwidthControllerError>;
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError>;
async fn close(&self) {}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -56,6 +58,10 @@ where
.map_err(|_| BandwidthControllerError::MalformedUpgradeModeToken)?;
Ok(Some(token))
}
async fn close(&self) {
self.storage.close().await;
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -75,4 +81,8 @@ impl<T: BandwidthTicketProvider + ?Sized + Send> BandwidthTicketProvider for Box
async fn get_upgrade_mode_token(&self) -> Result<Option<String>, BandwidthControllerError> {
(**self).get_upgrade_mode_token().await
}
async fn close(&self) {
(**self).close().await;
}
}
+8 -2
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-bin-common"
version.workspace = true
description = "Common code for nym binaries"
edition = { workspace = true }
version.workspace = true
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
[dependencies]
clap = { workspace = true, features = ["derive"], optional = true }
@@ -38,6 +43,7 @@ default = []
openapi = ["utoipa"]
output_format = ["serde_json", "dep:clap"]
bin_info_schema = ["schemars"]
ip_check = []
basic_tracing = ["dep:tracing", "dep:tracing-subscriber"]
otel-otlp = [
"basic_tracing",
@@ -1,29 +1,12 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
// use `ip` feature without nightly
// issue: https://github.com/rust-lang/rust/issues/27709
pub(crate) const fn is_global_ip(ip: &IpAddr) -> bool {
pub const fn is_global_ip(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(addr) => is_global_ipv4(addr),
IpAddr::V6(addr) => is_global_ipv6(addr),
}
}
const fn is_shared_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)
}
const fn is_benchmarking_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18
}
const fn is_reserved_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] & 240 == 240 && !ip.is_broadcast()
}
const fn is_global_ipv4(ip: &Ipv4Addr) -> bool {
!(ip.octets()[0] == 0 // "This network"
|| ip.is_private()
@@ -42,6 +25,18 @@ const fn is_global_ipv4(ip: &Ipv4Addr) -> bool {
|| ip.is_broadcast())
}
const fn is_shared_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000)
}
const fn is_benchmarking_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18
}
const fn is_reserved_ipv4(ip: &Ipv4Addr) -> bool {
ip.octets()[0] & 240 == 240 && !ip.is_broadcast()
}
const fn is_documentation_ipv6(ip: &Ipv6Addr) -> bool {
(ip.segments()[0] == 0x2001) && (ip.segments()[1] == 0xdb8)
}
+3
View File
@@ -9,3 +9,6 @@ pub mod completions;
#[cfg(feature = "output_format")]
pub mod output_format;
#[cfg(feature = "ip_check")]
pub mod ip_check;
+4 -2
View File
@@ -1,14 +1,16 @@
[package]
name = "nym-client-core"
description = "Crate containing core client functionality and configs, used by all other Nym client implentations"
version.workspace = true
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2024"
rust-version = "1.85"
license.workspace = true
description = "Crate containing core client functionality and configs, used by all other Nym client implentations"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-client-core-config-types"
description = "Low level configs and constants used by Nym clients and nodes"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Low level configs and constants used by Nym clients and nodes"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,13 +1,16 @@
[package]
name = "nym-client-core-gateways-storage"
description = "Functionality for Nym clients to store and retrive Gateway connections"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
rust-version.workspace = true
description = "Functionality for Nym clients to store and retrive Gateway connections"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1023,6 +1023,16 @@ where
let encryption_keys = init_res.client_keys.encryption_keypair();
let identity_keys = init_res.client_keys.identity_keypair();
let credential_store_for_close = credential_store.clone();
let close_credential_token = shutdown_tracker.clone_shutdown_token();
shutdown_tracker.try_spawn_named(
async move {
close_credential_token.cancelled().await;
credential_store_for_close.close().await;
},
"CredentialStorage::close_on_shutdown",
);
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
let bandwidth_controller = self
@@ -11,11 +11,17 @@ use nym_bandwidth_controller::BandwidthController;
use nym_client_core_gateways_storage::OnDiskGatewaysDetails;
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_validator_client::{QueryHttpRpcNyxdClient, nyxd};
use std::{io, path::Path};
use std::{io, path::Path, time::Duration};
use time::OffsetDateTime;
use tracing::{error, info, trace};
use url::Url;
/// Maximum rename retry attempts when the database file is temporarily locked.
const ARCHIVE_MAX_RETRY_ATTEMPTS: u8 = 15;
/// Delay between archive rename retry attempts.
const ARCHIVE_RETRY_DELAY: Duration = Duration::from_millis(200);
async fn setup_fresh_backend<P: AsRef<Path>>(
db_path: P,
surb_config: &config::ReplySurbs,
@@ -74,13 +80,58 @@ async fn archive_corrupted_database<P: AsRef<Path>>(db_path: P) -> io::Result<()
};
let renamed = db_path.with_extension(new_extension);
tokio::fs::rename(db_path, &renamed).await.inspect_err(|_| {
error!(
"Failed to rename corrupt database file: {} to {}",
db_path.display(),
renamed.display()
);
})
// On Windows, sqlx may release its OS file handles asynchronously after
// pool.close() returns, briefly keeping the file locked
// (ERROR_SHARING_VIOLATION, os error 32). Retry with a short delay to
// give the OS time to flush the remaining handles.
for attempt in 0..ARCHIVE_MAX_RETRY_ATTEMPTS {
match tokio::fs::rename(db_path, &renamed).await {
Ok(()) => return Ok(()),
Err(e) if is_file_locked_error(&e) && (attempt + 1) < ARCHIVE_MAX_RETRY_ATTEMPTS => {
trace!(
"Database file is temporarily locked, retrying archive \
(attempt {}/{}): {e}",
attempt + 1,
ARCHIVE_MAX_RETRY_ATTEMPTS
);
tokio::time::sleep(ARCHIVE_RETRY_DELAY).await;
}
Err(e) => {
error!(
"Failed to rename corrupt database file: {} to {}",
db_path.display(),
renamed.display()
);
return Err(e);
}
}
}
// Reached only when every attempt was blocked by a file lock.
error!(
"Failed to rename corrupt database file after {} attempts: {} to {}",
ARCHIVE_MAX_RETRY_ATTEMPTS,
db_path.display(),
renamed.display()
);
Err(io::Error::other(
"corrupt database archive blocked by persistent file lock",
))
}
/// Returns `true` when the IO error indicates a temporary file lock held by another handle
/// within the same process. Only meaningful on Windows; always `false` elsewhere.
fn is_file_locked_error(e: &io::Error) -> bool {
#[cfg(windows)]
{
// ERROR_SHARING_VIOLATION = 32, ERROR_LOCK_VIOLATION = 33
matches!(e.raw_os_error(), Some(32) | Some(33))
}
#[cfg(not(windows))]
{
let _ = e;
false
}
}
pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-client-core-surb-storage"
description = "Functionality for Nym clients to generate and use Single Use Reply Blocks (SURBs)"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Functionality for Nym clients to generate and use Single Use Reply Blocks (SURBs)"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -337,6 +337,8 @@ impl ReplyStorageBackend for Backend {
}
async fn stop_storage_session(self) -> Result<(), Self::StorageError> {
self.stop_client_use().await
let result = self.stop_client_use().await;
self.shutdown().await;
result
}
}
+5 -3
View File
@@ -48,6 +48,7 @@ where
debug!("Started PersistentReplyStorage");
if let Err(err) = self.backend.start_storage_session().await {
error!("failed to start the storage session - {err}");
self.backend.stop_storage_session().await.ok();
return;
}
@@ -55,10 +56,11 @@ where
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
error!("failed to flush our reply-related data to the persistent storage: {err}")
} else {
info!("Data flush is complete")
error!("failed to flush our reply-related data to the persistent storage: {err}");
self.backend.stop_storage_session().await.ok();
return;
}
info!("Data flush is complete");
if let Err(err) = self.backend.stop_storage_session().await {
error!("failed to properly stop the storage session - {err}. We might not be able to smoothly restore it")
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-gateway-client"
description = "Functions and types for Nym client <> Gateway connections"
version.workspace = true
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "Functions and types for Nym client <> Gateway connections"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-mixnet-client"
description = "Client for Mix Node <> Mix Node & Mix Node <> Gateway communication"
version.workspace = true
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "Client for Mix Node <> Mix Node & Mix Node <> Gateway communication"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -342,7 +342,7 @@ impl SendWithoutResponse for Client {
sending_res.map_err(|err| {
match err {
TrySendError::Full(_) => {
warn!(
trace!(
event = "mixclient.try_send",
peer = %address,
result = "full_dropped",
@@ -1,14 +1,16 @@
[package]
name = "nym-validator-client"
description = "Client for interacting with Nyx Cosmos SDK blockchain"
version.workspace = true
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.85"
license.workspace = true
description = "Client for interacting with Nyx Cosmos SDK blockchain"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -8,6 +8,7 @@ use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmwasm_std::Coin;
use nym_ecash_contract_common::deposit::LatestDepositResponse;
use nym_ecash_contract_common::deposit_statistics::DepositsStatistics;
use nym_ecash_contract_common::msg::QueryMsg as EcashQueryMsg;
use serde::Deserialize;
@@ -17,6 +18,9 @@ pub use nym_ecash_contract_common::blacklist::{
pub use nym_ecash_contract_common::deposit::{
Deposit, DepositData, DepositId, DepositResponse, PagedDepositsResponse,
};
pub use nym_ecash_contract_common::reduced_deposit::{
WhitelistedAccount, WhitelistedAccountsResponse,
};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -42,8 +46,18 @@ pub trait EcashQueryClient {
.await
}
async fn get_required_deposit_amount(&self) -> Result<Coin, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetRequiredDepositAmount {})
async fn get_default_deposit_amount(&self) -> Result<Coin, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetDefaultDepositAmount {})
.await
}
async fn get_reduced_deposit_amount(&self, address: String) -> Result<Option<Coin>, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetReducedDepositAmount { address })
.await
}
async fn get_all_whitelisted_accounts(&self) -> Result<WhitelistedAccountsResponse, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetAllWhitelistedAccounts {})
.await
}
@@ -65,6 +79,11 @@ pub trait EcashQueryClient {
self.query_ecash_contract(EcashQueryMsg::GetDepositsPaged { start_after, limit })
.await
}
async fn get_deposits_statistics(&self) -> Result<DepositsStatistics, NyxdError> {
self.query_ecash_contract(EcashQueryMsg::GetDepositsStatistics {})
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -122,10 +141,17 @@ mod tests {
EcashQueryMsg::GetDepositsPaged { limit, start_after } => {
client.get_deposits_paged(start_after, limit).ignore()
}
EcashQueryMsg::GetRequiredDepositAmount {} => {
client.get_required_deposit_amount().ignore()
EcashQueryMsg::GetDefaultDepositAmount {} => {
client.get_default_deposit_amount().ignore()
}
EcashQueryMsg::GetReducedDepositAmount { address } => {
client.get_reduced_deposit_amount(address).ignore()
}
EcashQueryMsg::GetAllWhitelistedAccounts {} => {
client.get_all_whitelisted_accounts().ignore()
}
EcashQueryMsg::GetLatestDeposit {} => client.get_latest_deposit().ignore(),
EcashQueryMsg::GetDepositsStatistics {} => client.get_deposits_statistics().ignore(),
};
}
}
@@ -62,13 +62,47 @@ pub trait EcashSigningClient {
new_deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = EcashExecuteMsg::UpdateDepositValue {
let req = EcashExecuteMsg::UpdateDefaultDepositValue {
new_deposit: new_deposit.into(),
};
self.execute_ecash_contract(fee, req, "Ecash::UpdateDepositValue".to_string(), vec![])
.await
}
async fn set_reduced_deposit_price(
&self,
address: String,
deposit: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = EcashExecuteMsg::SetReducedDepositPrice {
address,
deposit: deposit.into(),
};
self.execute_ecash_contract(
fee,
req,
"Ecash::SetReducedDepositPrice".to_string(),
vec![],
)
.await
}
async fn remove_reduced_deposit_price(
&self,
address: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = EcashExecuteMsg::RemoveReducedDepositPrice { address };
self.execute_ecash_contract(
fee,
req,
"Ecash::RemoveReducedDepositPrice".to_string(),
vec![],
)
.await
}
async fn propose_for_blacklist(
&self,
public_key: String,
@@ -141,9 +175,15 @@ mod tests {
.ignore(),
ExecuteMsg::RedeemTickets { .. } => unimplemented!(), // no redeem tickets method for the client
ExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(),
ExecuteMsg::UpdateDepositValue { new_deposit } => client
ExecuteMsg::UpdateDefaultDepositValue { new_deposit } => client
.update_deposit_value(new_deposit.into(), None)
.ignore(),
ExecuteMsg::SetReducedDepositPrice { address, deposit } => client
.set_reduced_deposit_price(address, deposit.into(), None)
.ignore(),
ExecuteMsg::RemoveReducedDepositPrice { address } => {
client.remove_reduced_deposit_price(address, None).ignore()
}
};
}
}
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-cli-commands"
description = "Common commands crate used by the nym-cli tool for interacting with the Nyx Cosmos SDK blockchain and Mixnet endpoints"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Common commands crate used by the nym-cli tool for interacting with the Nyx Cosmos SDK blockchain and Mixnet endpoints"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
[dependencies]
anyhow = { workspace = true }
+6 -1
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-config"
description = "Config related helpers and functions"
version.workspace = true
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "Config related helpers and functions"
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,12 +1,16 @@
[package]
name = "nym-coconut-dkg-common"
description = "Common crate for Nym's DKG cosmwasm contract"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Common crate for Nym's DKG cosmwasm contract"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,15 +1,16 @@
[package]
name = "nym-contracts-common-testing"
description = "Common crate for cosmwasm contract tests"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
rust-version = "1.85"
readme.workspace = true
description = "Common crate for cosmwasm contract tests"
publish = true
[dependencies]
anyhow = { workspace = true }
@@ -1,11 +1,16 @@
[package]
name = "nym-contracts-common"
version.workspace = true
description = "Common library for Nym cosmwasm contracts"
edition = { workspace = true }
version.workspace = true
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
bs58 = { workspace = true }
@@ -2,8 +2,8 @@
name = "easy-addr"
version.workspace = true
edition = "2021"
publish = false
license.workspace = true
publish = false
[lib]
proc-macro = true
@@ -1,12 +1,16 @@
[package]
name = "nym-ecash-contract-common"
description = "Common crate for Nym's ecash/zknym cosmwasm contract"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Common crate for Nym's ecash/zknym cosmwasm contract"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -6,6 +6,14 @@ use cosmwasm_std::Coin;
#[cw_serde]
pub struct PoolCounters {
/// Represents the total amount of funds deposited into the contract.
pub total_deposited: Coin,
/// Represents the total amount of funds redeemed from the contract that got transferred into the holding account.
pub total_redeemed: Coin,
/// Represents the total amount of tickets requested to be redeemed from the contract and get moved into the holding account,
/// after that functionality got disabled.
#[serde(default)]
pub tickets_requested_and_not_redeemed: u64,
}
@@ -0,0 +1,38 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Coin;
use std::collections::HashMap;
/// Aggregate statistics about all deposits made through the ecash contract.
#[cw_serde]
pub struct DepositsStatistics {
/// Total number of deposits ever made (at any price tier),
/// derived from the deposit id counter.
pub total_deposits_made: u32,
/// Total value of all deposits ever made (at any price tier),
/// sourced from `PoolCounters::total_deposited`.
pub total_deposited: Coin,
/// Number of deposits made at the default (non-reduced) price.
pub total_deposits_made_with_default_price: u32,
/// Total value deposited at the default price.
pub total_deposited_with_default_price: Coin,
/// Number of deposits made at any custom (reduced) price, summed across all whitelisted accounts.
pub total_deposits_made_with_custom_price: u32,
/// Total value deposited at custom prices, summed across all whitelisted accounts.
pub total_deposited_with_custom_price: Coin,
/// Per-account breakdown of deposit counts for whitelisted addresses.
// note: we use String for addressing due to serialisation incompatibility
pub deposits_made_with_custom_price: HashMap<String, u32>,
/// Per-account breakdown of deposited amounts for whitelisted addresses.
// note: we use String for addressing due to serialisation incompatibility
pub deposited_with_custom_price: HashMap<String, Coin>,
}
@@ -65,4 +65,26 @@ pub enum EcashContractError {
#[error("the account blacklisting hasn't been fully implemented yet")]
UnimplementedBlacklisting,
#[error("reduced deposit must use the same denom as the default deposit (expected '{expected}', got '{got}')")]
InvalidReducedDepositDenom { expected: String, got: String },
#[error(
"reduced deposit amount ({reduced}) must be strictly less than the default ({default})"
)]
ReducedDepositNotReduced {
reduced: cosmwasm_std::Uint128,
default: cosmwasm_std::Uint128,
},
#[error("address '{address}' does not have a custom reduced deposit price set")]
NoReducedDepositPrice { address: String },
#[error(
"deposit amount ({amount}) must be at least the ticket book size ({ticket_book_size})"
)]
DepositBelowTicketBookSize {
amount: cosmwasm_std::Uint128,
ticket_book_size: u64,
},
}
@@ -4,10 +4,12 @@
pub mod blacklist;
pub mod counters;
pub mod deposit;
pub mod deposit_statistics;
pub mod error;
pub mod event_attributes;
pub mod events;
pub mod msg;
pub mod redeem_credential;
pub mod reduced_deposit;
pub use error::EcashContractError;
@@ -9,6 +9,10 @@ use crate::blacklist::{BlacklistedAccountResponse, PagedBlacklistedAccountRespon
#[cfg(feature = "schema")]
use crate::deposit::{DepositResponse, LatestDepositResponse, PagedDepositsResponse};
#[cfg(feature = "schema")]
use crate::deposit_statistics::DepositsStatistics;
#[cfg(feature = "schema")]
use crate::reduced_deposit::WhitelistedAccountsResponse;
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
#[cw_serde]
@@ -42,10 +46,25 @@ pub enum ExecuteMsg {
admin: String,
},
UpdateDepositValue {
#[serde(alias = "update_deposit_value")]
UpdateDefaultDepositValue {
new_deposit: Coin,
},
/// Set (or overwrite) a reduced deposit price for a specific address.
/// Only callable by the contract admin.
SetReducedDepositPrice {
address: String,
deposit: Coin,
},
/// Remove the reduced deposit price for a specific address, reverting them to
/// the default price. Returns an error if the address has no custom price set.
/// Only callable by the contract admin.
RemoveReducedDepositPrice {
address: String,
},
// TODO: properly implement
ProposeToBlacklist {
public_key: String,
@@ -68,7 +87,15 @@ pub enum QueryMsg {
},
#[cfg_attr(feature = "schema", returns(Coin))]
GetRequiredDepositAmount {},
#[serde(alias = "get_required_deposit_amount")]
#[serde(alias = "GetRequiredDepositAmount")]
GetDefaultDepositAmount {},
#[cfg_attr(feature = "schema", returns(Option<Coin>))]
GetReducedDepositAmount { address: String },
#[cfg_attr(feature = "schema", returns(WhitelistedAccountsResponse))]
GetAllWhitelistedAccounts {},
#[cfg_attr(feature = "schema", returns(DepositResponse))]
GetDeposit { deposit_id: u32 },
@@ -81,7 +108,22 @@ pub enum QueryMsg {
limit: Option<u32>,
start_after: Option<u32>,
},
#[cfg_attr(feature = "schema", returns(DepositsStatistics))]
GetDepositsStatistics {},
}
#[cw_serde]
pub struct MigrateMsg {}
pub struct MigrateMsg {
/// Initial set of whitelisted accounts with their reduced deposit prices.
/// Each entry is validated and stored during migration.
pub initial_whitelist: Vec<WhitelistedDeposit>,
}
/// An address and its reduced deposit price, used when seeding the whitelist
/// via migration.
#[cw_serde]
pub struct WhitelistedDeposit {
pub address: String,
pub deposit: Coin,
}
@@ -0,0 +1,16 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin};
#[cw_serde]
pub struct WhitelistedAccount {
pub address: Addr,
pub deposit: Coin,
}
#[cw_serde]
pub struct WhitelistedAccountsResponse {
pub whitelisted_accounts: Vec<WhitelistedAccount>,
}
@@ -1,12 +1,16 @@
[package]
name = "nym-group-contract-common"
description = "Common crate for Nym's group cosmwasm contract"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Common crate for Nym's group cosmwasm contract"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
cosmwasm-schema = { workspace = true }
@@ -1,12 +1,16 @@
[package]
name = "nym-mixnet-contract-common"
version.workspace = true
description = "Common library for the Nym mixnet contract"
rust-version = "1.85"
edition = { workspace = true }
version.workspace = true
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
bs58 = { workspace = true }
@@ -1,10 +1,16 @@
[package]
name = "nym-multisig-contract-common"
description = "Common code for the Nym multisig CosmWasm smart contract"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Common code for the Nym multisig CosmWasm smart contract"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
cosmwasm-schema = { workspace = true }
@@ -1,15 +1,16 @@
[package]
name = "nym-performance-contract-common"
description = "Common crate for Nym's group performance contract"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
rust-version = "1.85"
readme.workspace = true
description = "Common crate for Nym's group performance contract"
publish = true
[dependencies]
thiserror = { workspace = true }
@@ -1,15 +1,16 @@
[package]
name = "nym-pool-contract-common"
version.workspace = true
description = "Common library for the Nym Pool contract"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
thiserror = { workspace = true }
@@ -1,11 +1,16 @@
[package]
name = "nym-vesting-contract-common"
version.workspace = true
description = "Common library for the Nym vesting contract"
edition = { workspace = true }
version.workspace = true
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage.workspace = true
documentation.workspace = true
rust-version = "1.85"
readme.workspace = true
publish = true
[dependencies]
cosmwasm-std = { workspace = true }
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-credential-proxy-lib"
description = "Build script and core functionality of the Nym Credential Proxy"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Build script and core functionality of the Nym Credential Proxy"
publish = true
[dependencies]
anyhow = { workspace = true }
@@ -6,7 +6,7 @@ use crate::helpers::LockTimer;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_validator_client::nyxd::contract_traits::NymContractsProvider;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{Coin, Config, CosmWasmClient, NyxdClient};
use nym_validator_client::nyxd::{AccountId, Coin, Config, CosmWasmClient, NyxdClient};
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, nyxd};
use std::ops::Deref;
use std::sync::Arc;
@@ -50,6 +50,10 @@ impl ChainClient {
Ok(ChainClient(Arc::new(RwLock::new(client))))
}
pub async fn address(&self) -> AccountId {
self.0.read().await.address()
}
pub async fn query_chain(&self) -> ChainReadPermit<'_> {
let _acquire_timer = LockTimer::new("acquire chain query permit");
self.0.read().await
@@ -8,6 +8,7 @@ use nym_validator_client::nyxd::contract_traits::EcashQueryClient;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::RwLock;
use tracing::{info, warn};
pub struct CachedDeposit {
valid_until: OffsetDateTime,
@@ -56,13 +57,29 @@ impl RequiredDepositCache {
// update cache
drop(read_guard);
let address = chain_client.address().await;
info!("checking deposit required by {address}");
let mut write_guard = self.inner.write().await;
let deposit_amount = chain_client
.query_chain()
.await
.get_required_deposit_amount()
let read_permit = chain_client.query_chain().await;
let reduced = read_permit
.get_reduced_deposit_amount(address.to_string())
.await?;
let deposit_amount = match reduced {
Some(reduced) => {
info!("we're permitted to use reduced price");
reduced
}
None => {
warn!(
"using default deposit value {address} is not whitelisted for price reduction"
);
read_permit.get_default_deposit_amount().await?
}
};
let nym_coin: Coin = deposit_amount.into();
write_guard.update(nym_coin.clone());
+5 -2
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-credential-storage"
description = "Crate for handling and storing spent and unspent zknym ticketbooks"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
rust-version.workspace = true
description = "Crate for handling and storing spent and unspent zknym ticketbooks"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-credential-utils"
description = "Utils crate for dealing with zknym credentials"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Utils crate for dealing with zknym credentials"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-credential-verification"
description = "Store and verify zknym credentials"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Store and verify zknym credentials"
publish = true
[dependencies]
async-trait = { workspace = true }
@@ -3,25 +3,19 @@
use crate::Error;
use crate::ecash::error::EcashTicketError;
use crate::ecash::helpers::for_each_api_concurrent;
use crate::ecash::state::SharedState;
use cosmwasm_std::Fraction;
use cw_utils::ThresholdResponse;
use futures::channel::mpsc::UnboundedReceiver;
use futures::{Stream, StreamExt};
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketBody};
use nym_api_requests::ecash::models::VerifyEcashTicketBody;
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_validator_client::EcashApiClient;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
};
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
use nym_validator_client::nyxd::cw3::Status;
use nym_validator_client::nyxd::contract_traits::MultisigQueryClient;
use si_scale::helpers::bibytes2;
use std::collections::{HashMap, HashSet};
use std::ops::Deref;
@@ -31,22 +25,6 @@ use tokio::sync::{Mutex, RwLockReadGuard};
use tokio::time::{Duration, Instant, interval_at};
use tracing::{debug, error, info, instrument, trace, warn};
enum ProposalResult {
Executed,
Rejected,
Pending,
}
impl ProposalResult {
fn is_pending(&self) -> bool {
matches!(self, ProposalResult::Pending)
}
fn is_rejected(&self) -> bool {
matches!(self, ProposalResult::Rejected)
}
}
struct PendingVerification {
ticket: ClientTicket,
@@ -68,43 +46,6 @@ impl PendingVerification {
}
}
struct PendingRedemptionVote {
proposal_id: u64,
digest: Vec<u8>,
included_serial_numbers: Vec<Vec<u8>>,
epoch_id: EpochId,
// vec of node ids of apis that haven't sent a valid response
pending: Vec<u64>,
}
impl PendingRedemptionVote {
fn new(
proposal_id: u64,
digest: Vec<u8>,
included_serial_numbers: Vec<Vec<u8>>,
epoch_id: EpochId,
pending: Vec<u64>,
) -> Self {
PendingRedemptionVote {
proposal_id,
digest,
included_serial_numbers,
epoch_id,
pending,
}
}
fn to_request_body(&self, gateway_cosmos_addr: AccountId) -> BatchRedeemTicketsBody {
BatchRedeemTicketsBody::new(
self.digest.clone(),
self.proposal_id,
self.included_serial_numbers.clone(),
gateway_cosmos_addr,
)
}
}
pub struct CredentialHandlerConfig {
/// Specifies the multiplier for revoking a malformed/double-spent ticket
/// (if it has to go all the way to the nym-api for verification)
@@ -132,7 +73,6 @@ pub struct CredentialHandler {
ticket_receiver: UnboundedReceiver<ClientTicket>,
shared_state: SharedState,
pending_tickets: Vec<PendingVerification>,
pending_redemptions: Vec<PendingRedemptionVote>,
}
impl CredentialHandler {
@@ -184,75 +124,6 @@ impl CredentialHandler {
Ok(pending)
}
async fn rebuild_pending_votes(
shared_state: &SharedState,
) -> Result<Vec<PendingRedemptionVote>, EcashTicketError> {
// 1. get all tickets that were not fully verified
let unverified = shared_state.storage.get_all_unresolved_proposals().await?;
let mut pending = Vec::with_capacity(unverified.len());
let epoch_id = shared_state.current_epoch_id().await?;
let apis = shared_state
.api_clients(epoch_id)
.await?
.iter()
.map(|s| (s.cosmos_address.to_string(), s.node_id))
.collect::<Vec<_>>();
for proposal_id in unverified {
// get all of the votes
let votes = shared_state
.start_query()
.await
.get_all_votes(proposal_id as u64)
.await
.map_err(EcashTicketError::chain_query_failure)?
.into_iter()
.map(|v| v.voter)
.collect::<HashSet<_>>();
let mut missing_votes = Vec::new();
// see who hasn't voted
for (api_address, api_id) in &apis {
// for each signer, check if they have actually voted; if not, that's the missing guy
if !votes.contains(api_address) {
missing_votes.push(*api_id)
}
}
// attempt to rebuild SN and digest from the proposal info + storage data
let proposal_info = shared_state
.start_query()
.await
.query_proposal(proposal_id as u64)
.await
.map_err(EcashTicketError::chain_query_failure)?;
let tickets = shared_state
.storage
.get_all_proposed_tickets_with_sn(proposal_id as u32)
.await?;
let digest =
BatchRedeemTicketsBody::make_digest(tickets.iter().map(|t| &t.serial_number));
let encoded_digest = bs58::encode(&digest).into_string();
if encoded_digest != proposal_info.description {
error!("the lost proposal {proposal_id} does not have a matching digest!");
continue;
}
pending.push(PendingRedemptionVote {
proposal_id: proposal_id as u64,
digest,
included_serial_numbers: tickets.into_iter().map(|t| t.serial_number).collect(),
epoch_id,
pending: missing_votes,
})
}
Ok(pending)
}
pub(crate) async fn new(
config: CredentialHandlerConfig,
ticket_receiver: UnboundedReceiver<ClientTicket>,
@@ -276,51 +147,15 @@ impl CredentialHandler {
// on startup read pending credentials and api responses from the storage
let pending_tickets = Self::rebuild_pending_tickets(&shared_state).await?;
// on startup read pending proposals from the storage
// then reconstruct the votes by querying the multisig contract for votes on those proposals
// digest from the description and count from the message
let pending_redemptions = Self::rebuild_pending_votes(&shared_state).await?;
Ok(CredentialHandler {
config,
multisig_threshold,
ticket_receiver,
shared_state,
pending_tickets,
pending_redemptions,
})
}
// the argument is temporary as we'll be reading from the storage
async fn create_redemption_proposal(
&self,
commitment: &[u8],
number_of_tickets: u16,
) -> Result<u64, EcashTicketError> {
let res = self
.shared_state
.start_tx()
.await
.request_ticket_redemption(
bs58::encode(commitment).into_string(),
number_of_tickets,
None,
)
.await
.map_err(|source| EcashTicketError::RedemptionProposalCreationFailure { source })?;
// that one is quite tricky because proposal exists on chain, but we didn't get the id...
// but it should be quite impossible to ever reach this unless we make breaking changes
let proposal_id = res
.parse_singleton_u64_contract_data()
.inspect_err(|err| error!("reached seemingly impossible error! could not recover the redemption proposal id: {err}"))
.map_err(|source| EcashTicketError::ProposalIdParsingFailure { source })?;
info!("created redemption proposal {proposal_id} to redeem {number_of_tickets} tickets");
Ok(proposal_id)
}
/// Attempt to send ticket verification request to the provided ecash verifier.
async fn verify_ticket(
&self,
@@ -522,42 +357,7 @@ impl CredentialHandler {
async fn resolve_pending(&mut self) -> Result<(), EcashTicketError> {
let mut still_failing = Vec::new();
// 1. attempt to resolve all pending proposals
while let Some(mut pending) = self.pending_redemptions.pop() {
match self.try_resolve_pending_proposal(&mut pending, None).await {
Ok(resolution) => {
if resolution.is_pending() {
warn!(
"still failed to reach quorum for proposal {}. apis: {:?} haven't responded. we'll retry later",
pending.proposal_id, pending.pending
);
still_failing.push(pending);
} else {
self.shared_state
.storage
.clear_post_proposal_data(
pending.proposal_id as u32,
OffsetDateTime::now_utc(),
resolution.is_rejected(),
)
.await?;
}
}
Err(err) => {
error!(
"experienced internal error when attempting to resolve pending proposal: {err}"
);
// make sure to update internal state to not lose any data
self.pending_redemptions.push(pending);
self.pending_redemptions.append(&mut still_failing);
return Err(err);
}
}
}
let mut still_failing = Vec::new();
// 2. attempt to verify the remaining tickets
// 1. attempt to verify the remaining tickets
while let Some(mut pending) = self.pending_tickets.pop() {
// possible optimisation: if there's a lot of pending tickets, pre-emptively grab locks for api_clients
match self
@@ -595,362 +395,14 @@ impl CredentialHandler {
Ok(())
}
/// Attempt to send batch redemption request to the provided ecash verifier.
async fn redeem_tickets(
&self,
proposal_id: u64,
request: &BatchRedeemTicketsBody,
client: &EcashApiClient,
) -> Result<bool, EcashTicketError> {
match client.api_client.batch_redeem_ecash_tickets(request).await {
Ok(res) => {
let accepted = if res.proposal_accepted {
trace!("{client} has accepted proposal {proposal_id}");
true
} else {
warn!("{client} has rejected proposal {proposal_id}");
false
};
Ok(accepted)
}
Err(err) => {
error!(
"failed to send proposal {proposal_id} for redemption vote to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later"
);
Ok(false)
}
}
}
async fn try_execute_proposal(&self, proposal_id: u64) -> Result<(), EcashTicketError> {
self.shared_state
.start_tx()
.await
.execute_proposal(proposal_id, None)
.await
.map_err(
|source| EcashTicketError::RedemptionProposalExecutionFailure {
proposal_id,
source,
},
)?;
Ok(())
}
async fn get_proposal_status(&self, proposal_id: u64) -> Result<Status, EcashTicketError> {
Ok(self
.shared_state
.start_query()
.await
.query_proposal(proposal_id)
.await
.map_err(EcashTicketError::chain_query_failure)?
.status)
}
async fn try_finalize_proposal(
&self,
proposal_id: u64,
) -> Result<ProposalResult, EcashTicketError> {
match self.get_proposal_status(proposal_id).await? {
Status::Pending => {
// the voting hasn't even begun!
error!("impossible case! the proposal {proposal_id} is still pending");
Ok(ProposalResult::Pending)
}
Status::Open => {
debug!("proposal {proposal_id} is still open and needs more votes");
Ok(ProposalResult::Pending)
}
Status::Rejected => {
warn!("proposal {proposal_id} has been rejected");
Ok(ProposalResult::Rejected)
}
Status::Passed => {
info!(
"proposal {proposal_id} has already been passed - we just need to execute it"
);
self.try_execute_proposal(proposal_id).await?;
info!("executed proposal {proposal_id}");
Ok(ProposalResult::Executed)
}
Status::Executed => {
info!("proposal {proposal_id} has already been executed - nothing to do!");
Ok(ProposalResult::Executed)
}
}
}
async fn try_resolve_pending_proposal(
&self,
pending: &mut PendingRedemptionVote,
api_clients: Option<RwLockReadGuard<'_, Vec<EcashApiClient>>>,
) -> Result<ProposalResult, EcashTicketError> {
let proposal_id = pending.proposal_id;
info!(
"attempting to resolve pending redemption proposal {proposal_id} to redeem {} tickets",
pending.included_serial_numbers.len()
);
// check if the proposal still needs more votes from the apis
let result = self.try_finalize_proposal(proposal_id).await?;
if !result.is_pending() {
return Ok(result);
}
let api_clients = match api_clients {
Some(clients) => clients,
None => self.shared_state.api_clients(pending.epoch_id).await?,
};
let redemption_request = pending.to_request_body(self.shared_state.address.clone());
// TODO: optimisation: tell other apis they can purge our tickets even if they haven't voted
let total = api_clients.len();
let api_failures = Mutex::new(Vec::new());
let rejected = AtomicUsize::new(0);
for_each_api_concurrent(&api_clients, &pending.pending, |ecash_client| async {
// errors are only returned on hard, storage, failures
match self
.redeem_tickets(pending.proposal_id, &redemption_request, ecash_client)
.await
{
Err(err) => {
error!("internal failure. could not proceed with ticket redemption: {err}");
api_failures.lock().await.push(ecash_client.node_id);
}
Ok(false) => {
rejected.fetch_add(1, Ordering::SeqCst);
}
_ => {}
}
})
.await;
let api_failures = api_failures.into_inner();
let num_failures = api_failures.len();
pending.pending = api_failures;
let rejected = rejected.into_inner();
let rejected_ratio = rejected as f32 / total as f32;
let rejected_perc = rejected_ratio * 100.;
if rejected_ratio >= (1. - self.multisig_threshold) {
error!(
"{rejected_perc:.2}% of signers rejected proposal {proposal_id}. we won't be able to execute it"
);
// no need to query the chain as with so many rejections it's impossible it has passed.
return Ok(ProposalResult::Rejected);
}
let accepted_ratio = (total - rejected - num_failures) as f32 / total as f32;
let accepted_perc = accepted_ratio * 100.;
match accepted_ratio {
n if n < self.multisig_threshold => {
error!(
"less than 2/3 of signers ({accepted_perc:.2}%) accepted proposal {proposal_id}. we're not yet be able to execute it to get funds out"
);
return Ok(ProposalResult::Pending);
}
n if n < self.config.minimum_api_quorum => {
warn!(
"the system seems to be a bit unstable: less than 80%, but more than 67% of signers ({accepted_perc:.2}%) accepted proposal {proposal_id}"
);
}
_ => {
trace!("{accepted_perc:.2}% of signers accepted proposal {proposal_id}");
}
}
// attempt to execute the proposal if it reached the required threshold
self.try_finalize_proposal(proposal_id).await
}
async fn maybe_redeem_tickets(&mut self) -> Result<(), EcashTicketError> {
if !self.pending_tickets.is_empty() {
return Err(EcashTicketError::PendingTickets);
}
let latest_stored = self.shared_state.storage.latest_proposal().await?;
// check if we have already created the proposal but crashed before persisting it in the db
//
// if we have some persisted proposals in storage, try to see if there's anything more recent on chain
// (i.e. the missing proposal)
// if not (i.e. this would have been our first) check the latest page of proposals.
// while this is not ideal, realistically speaking we probably crashed few minutes ago
// and worst case scenario we'll just recreate the proposal instead
//
// LIMITATION: if MULTIPLE proposals got created in between, well. though luck.
let latest_on_chain = if let Some(latest_stored) = &latest_stored {
// those are sorted in ASCENDING way
self.shared_state
.proposals_since(latest_stored.proposal_id as u64)
.await?
.pop()
} else {
// but those are DESCENDING
self.shared_state
.last_proposal_page()
.await?
.first()
.cloned()
};
let now = OffsetDateTime::now_utc();
let prior_proposal = match (&latest_stored, latest_on_chain) {
(None, None) => {
// we haven't created any proposals before
trace!("this could be our first redemption proposal");
None
}
(Some(stored), None) => {
if stored.created_at + MIN_BATCH_REDEMPTION_DELAY > now {
trace!("too soon to create new redemption proposal");
return Ok(());
}
None
}
(_, Some(on_chain)) => {
warn!(
"we seem to have crashed after creating proposal, but before persisting it onto disk!"
);
Some(on_chain)
}
};
// technically we could have been just caching all of those serial numbers as we verify tickets,
// but given how infrequently we call this, there's no point in wasting this memory
let verified_tickets = self
.shared_state
.storage
.get_all_verified_tickets_with_sn()
.await?;
// TODO: somehow simplify that nasty nested if
if verified_tickets.len() < self.config.minimum_redemption_tickets {
// bypass the number of tickets check if we're about to lose our rewards due to expiration
if let Some(latest_stored) = latest_stored {
if latest_stored.created_at + self.config.maximum_time_between_redemption < now {
{}
} else {
debug!(
"we only have {} verified tickets. there's no point in creating a redemption request yet. (we need at least {} (configurable))",
verified_tickets.len(),
self.config.minimum_redemption_tickets
);
return Ok(());
}
} else {
// first proposal
debug!(
"we only have {} verified tickets. there's no point in creating a redemption request yet. (we need at least {} (configurable))",
verified_tickets.len(),
self.config.minimum_redemption_tickets
);
return Ok(());
}
}
// this should have been ensured when querying
assert!(verified_tickets.len() <= u16::MAX as usize);
let digest =
BatchRedeemTicketsBody::make_digest(verified_tickets.iter().map(|t| &t.serial_number));
let encoded_digest = bs58::encode(&digest).into_string();
let prior_proposal_id = if let Some(prior_proposal) = prior_proposal {
if prior_proposal.description == encoded_digest {
info!("we have already created proposal for those tickets");
Some(prior_proposal.id)
} else {
warn!(
"our missed proposal seem to have been for different tickets - abandoning it"
);
None
}
} else {
None
};
// if the proposal has already existed on chain, do use it. otherwise create a new one
let proposal_id = if let Some(prior) = prior_proposal_id {
prior
} else {
self.create_redemption_proposal(&digest, verified_tickets.len() as u16)
.await?
};
if proposal_id > u32::MAX as u64 {
// realistically will we ever reach it? no.
panic!(
"we have created more than {} proposals. we can't handle that.",
u32::MAX
)
}
self.shared_state
.storage
.insert_redemption_proposal(
&verified_tickets,
proposal_id as u32,
OffsetDateTime::now_utc(),
)
.await?;
let current_epoch = self.shared_state.current_epoch_id().await?;
let api_clients = self.shared_state.api_clients(current_epoch).await?;
let ids = api_clients.iter().map(|c| c.node_id).collect();
let mut pending = PendingRedemptionVote::new(
proposal_id,
digest,
verified_tickets
.into_iter()
.map(|t| t.serial_number)
.collect(),
current_epoch,
ids,
);
let resolution = self
.try_resolve_pending_proposal(&mut pending, Some(api_clients))
.await?;
if resolution.is_pending() {
warn!(
"failed to reach quorum for proposal {proposal_id}. apis: {:?} haven't responded. we'll retry later",
pending.pending
);
self.pending_redemptions.push(pending);
} else {
self.shared_state
.storage
.clear_post_proposal_data(
proposal_id as u32,
OffsetDateTime::now_utc(),
resolution.is_rejected(),
)
.await?;
}
Ok(())
}
async fn periodic_operations(&mut self) -> Result<(), EcashTicketError> {
trace!(
"attempting to resolve all pending operations -> tickets that are waiting for verification and possibly redemption"
"attempting to resolve all pending operations -> tickets that are waiting for verification"
);
// 1. retry all operations that have failed in the past: verification requests and pending redemption
// retry the pending verification requests that have failed before
self.resolve_pending().await?;
// 2. if applicable, attempt to redeem all newly verified tickets
self.maybe_redeem_tickets().await?;
Ok(())
}
@@ -7,7 +7,7 @@ use std::future::Future;
use std::ops::Deref;
use tokio::sync::RwLockReadGuard;
pub(crate) fn apis_stream<'a>(
pub fn apis_stream<'a>(
// if needed we could make this argument more generic to accept either locks or iterators, etc.
all_clients: &'a RwLockReadGuard<'a, Vec<EcashApiClient>>,
filter_by_id: &'a [u64],
@@ -22,7 +22,7 @@ pub(crate) fn apis_stream<'a>(
)
}
pub(crate) async fn for_each_api_concurrent<'a, F, Fut>(
pub async fn for_each_api_concurrent<'a, F, Fut>(
all_clients: &'a RwLockReadGuard<'a, Vec<EcashApiClient>>,
filter_by_id: &'a [u64],
f: F,
@@ -20,7 +20,7 @@ use tracing::error;
pub mod credential_sender;
pub mod error;
mod helpers;
pub mod helpers;
mod state;
pub mod traits;
@@ -3,17 +3,12 @@
use crate::Error;
use crate::ecash::error::EcashTicketError;
use cosmwasm_std::{CosmosMsg, WasmMsg, from_json};
use nym_credentials_interface::VerificationKeyAuth;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_gateway_storage::traits::BandwidthGatewayStorage;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::nyxd::contract_traits::{
DkgQueryClient, MultisigQueryClient, NymContractsProvider,
};
use nym_validator_client::nyxd::cw3::ProposalResponse;
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, NymContractsProvider};
use nym_validator_client::{DirectSigningHttpRpcNyxdClient, EcashApiClient};
use std::collections::BTreeMap;
use std::ops::Deref;
@@ -77,53 +72,6 @@ impl SharedState {
Ok(this)
}
fn created_redemption_proposal(&self, proposal: &ProposalResponse) -> bool {
let Some(msg) = proposal.msgs.first() else {
return false;
};
let CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) = msg else {
return false;
};
let Ok(ExecuteMsg::RedeemTickets { gw, .. }) = from_json(msg) else {
return false;
};
gw == self.address.as_ref()
}
/// retrieve all redemption proposals made by this gateway since, but excluding, the provided id
pub(crate) async fn proposals_since(
&self,
proposal_id: u64,
) -> Result<Vec<ProposalResponse>, EcashTicketError> {
Ok(self
.start_query()
.await
.list_proposals(Some(proposal_id), None)
.await
.map_err(EcashTicketError::chain_query_failure)?
.proposals
.into_iter()
.filter(|p| self.created_redemption_proposal(p))
.collect())
}
/// retrieve all redemption proposals made by this gateway that are available on the last page of the query
pub(crate) async fn last_proposal_page(
&self,
) -> Result<Vec<ProposalResponse>, EcashTicketError> {
Ok(self
.start_query()
.await
.reverse_proposals(None, None)
.await
.map_err(EcashTicketError::chain_query_failure)?
.proposals
.into_iter()
.filter(|p| self.created_redemption_proposal(p))
.collect())
}
async fn set_epoch_data(
&self,
epoch_id: EpochId,
@@ -240,24 +188,6 @@ impl SharedState {
data.get(&epoch_id).map(|d| &d.master_key).unwrap()
}))
}
pub(crate) async fn start_tx(&self) -> RwLockWriteGuard<'_, DirectSigningHttpRpcNyxdClient> {
self.nyxd_client.write().await
}
pub(crate) async fn start_query(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
self.nyxd_client.read().await
}
pub(crate) async fn current_epoch_id(&self) -> Result<EpochId, EcashTicketError> {
Ok(self
.start_query()
.await
.get_current_epoch()
.await
.map_err(EcashTicketError::chain_query_failure)?
.epoch_id)
}
}
pub(crate) struct EpochState {
+6 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-credentials-interface"
description = "Interface for Nym's compact eacash / zknym credential scheme"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Interface for Nym's compact eacash / zknym credential scheme"
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-credentials"
description = "Crate for using Nym's zknym credentials"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Crate for using Nym's zknym credentials"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+7 -2
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-crypto"
version.workspace = true
description = "Crypto library for the nym mixnet"
edition = { workspace = true }
version.workspace = true
authors = { workspace = true }
edition = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
[dependencies]
aes-gcm-siv = { workspace = true, optional = true }
+8 -4
View File
@@ -1,13 +1,17 @@
[package]
name = "nym-dkg"
version.workspace = true
edition = "2021"
resolver = "2"
license.workspace = true
description = "Nym's Distributed Key Generation functionality"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-ecash-signer-check-types"
description = "Crate containing types for the `ecash-signer-check` crate used to check if zknym signers are up and running properly"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Crate containing types for the `ecash-signer-check` crate used to check if zknym signers are up and running properly"
publish = true
[dependencies]
semver = { workspace = true }
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-ecash-signer-check"
description = "Functions to interact with zknym signers, checking their status and health"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Functions to interact with zknym signers, checking their status and health"
publish = true
[dependencies]
futures = { workspace = true }
+6 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-ecash-time"
description = "Time-related helper functions for Nym's zknym scheme"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Time-related helper functions for Nym's zknym scheme"
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+6 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-exit-policy"
description = "Get and set the Nym Exit Policy, used by Exit Gateways"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Get and set the Nym Exit Policy, used by Exit Gateways"
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -1
View File
@@ -3,14 +3,17 @@
[package]
name = "nym-gateway-requests"
description = "Request and response definitions for Nym Gateway <> client communication"
version.workspace = true
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "Request and response definitions for Nym Gateway <> client communication"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+5 -3
View File
@@ -1,14 +1,16 @@
[package]
name = "nym-gateway-stats-storage"
description = "Functionality Nym Gateway statistics storage"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "Functionality Nym Gateway statistics storage"
readme.workspace = true
publish = true
[dependencies]
sqlx = { workspace = true, features = [
+5 -3
View File
@@ -1,14 +1,16 @@
[package]
name = "nym-gateway-storage"
description = "Crate handling db setup and use for Nym Gateways, used for credentials, packets, connections"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
description = "Crate handling db setup and use for Nym Gateways, used for credentials, packets, connections"
readme.workspace = true
publish = true
[dependencies]
async-trait = { workspace = true }
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-http-api-client-macro"
description = "Proc-macros for configuring HTTP clients globally via the `inventory` crate"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Proc-macros for configuring HTTP clients globally via the `inventory` crate"
publish = true
[lib]
proc-macro = true
+7 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-http-api-client"
description = "Nym's HTTP API client, examples, and tests"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Nym's HTTP API client, examples, and tests"
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -34,6 +37,7 @@ tracing = { workspace = true }
itertools = { workspace = true }
inventory = { workspace = true }
tokio = { workspace = true, features = ["rt", "macros", "time"] }
rustls = { workspace=true }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
encoding_rs = { workspace = true }
+1 -1
View File
@@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub const NYM_API_DOMAIN: &str = "validator.nymtech.net";
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(212, 71, 233, 232))];
pub const NYM_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(92, 39, 63, 14))];
pub const NYM_VPN_API_DOMAIN: &str = "nymvpn.com";
pub const NYM_VPN_API_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(76, 76, 21, 21))];
+112 -8
View File
@@ -161,6 +161,8 @@ use reqwest::{RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[cfg(not(target_arch = "wasm32"))]
use std::io::ErrorKind;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use thiserror::Error;
@@ -1150,7 +1152,10 @@ impl ApiClientCore for Client {
#[cfg(target_arch = "wasm32")]
let response: Result<Response, HttpClientError> = {
let client = self.reqwest_client.as_ref().unwrap_or(&*SHARED_CLIENT);
let client = self
.reqwest_client
.as_ref()
.unwrap_or_else(|| &*SHARED_CLIENT);
Ok(
wasmtimer::tokio::timeout(self.request_timeout, client.execute(req))
.await
@@ -1160,21 +1165,29 @@ impl ApiClientCore for Client {
#[cfg(not(target_arch = "wasm32"))]
let response = {
let client = self.reqwest_client.as_ref().unwrap_or(&*SHARED_CLIENT);
let client = self
.reqwest_client
.as_ref()
.unwrap_or_else(|| &*SHARED_CLIENT);
client.execute(req).await
};
match response {
Ok(resp) => return Ok(resp),
Ok(resp) => {
// Check if the response includes a rate limit error from the vercel API
if is_http_rate_limit_err(&resp) {
warn!("encountered vercel rate limit error for {}", url.as_str());
// if we have multiple urls, update to the next
self.maybe_rotate_hosts(Some(url.clone()));
}
return Ok(resp);
}
Err(err) => {
// only if there was a network issue should we consider updating the host info
//
// note: for now this includes DNS resolution failure, I am not sure how I would go about
// segregating that based on the interface provided by request for errors.
#[cfg(target_arch = "wasm32")]
let is_network_err = err.is_timeout();
#[cfg(not(target_arch = "wasm32"))]
let is_network_err = err.is_timeout() || err.is_connect();
let is_network_err = might_be_network_interference(&err);
if is_network_err {
// if we have multiple urls, update to the next
@@ -1222,6 +1235,90 @@ impl ApiClientCore for Client {
}
}
const VERCEL_CHALLENGE_HEADER: &str = "x-vercel-mitigated";
const VERCEL_CHALLENGE_VALUE: &[u8] = b"challenge";
/// Check for Rate Limit challenge response from the vercel API
pub(crate) fn is_http_rate_limit_err(resp: &Response) -> bool {
let status = resp.status() == StatusCode::FORBIDDEN;
let header = resp
.headers()
.get(VERCEL_CHALLENGE_HEADER)
.is_some_and(|v| v.as_bytes() == VERCEL_CHALLENGE_VALUE);
let content_type = resp
.headers()
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok())
.is_some_and(|mime_type| {
mime_type.type_() == mime::TEXT && mime_type.subtype() == mime::HTML
});
status && header && content_type
}
#[cfg(not(target_arch = "wasm32"))]
const MAX_ERR_SOURCE_ITERATIONS: usize = 4;
/// This functions attempts to check the error returned by reqwest to see if rotating host
/// information (for clients with multiple hosts defined) could be helpful. This looks for
/// situations where the error could plausibly be caused by a network adversary, or where rotating
/// to an equivalent hostname might help.
///
/// For example --> NetworkUnreachable will not be helped by rotating domains, but ConnectionReset
/// might be caused by a network adversary blocking by SNI which could possibly benefit from
/// rotating domains.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn might_be_network_interference(err: &reqwest::Error) -> bool {
if err.is_timeout() {
return true;
}
if !(err.is_connect() || err.is_request()) {
return false;
}
// The io::Error source is several layers deep, for clarity this is done as a loop
// * reqwest::Error -> hyper_util::Error
// * hyper_util::Error -> hyper_util::ClientError
// * hyper_util::ClientError -> io::Error
let mut inner = err.source();
for _ in 0..MAX_ERR_SOURCE_ITERATIONS {
if let Some(e) = inner {
if let Some(io_err) = e.downcast_ref::<std::io::Error>() {
// try downcast to io::Error from <dyn std::error:Error>
match io_err.kind() {
// device not connected to the internet
ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown => return false,
// connection errors can indicate connection interference
ErrorKind::ConnectionReset
| ErrorKind::HostUnreachable
| ErrorKind::ConnectionRefused => return true,
// TLS errors get wrapped in custom io::Errors
ErrorKind::Other | ErrorKind::InvalidData => {
// io::Error get_ref works while source doesn't here -_-
// if you don't like it take it up with the rust devs https://users.rust-lang.org/t/question-about-implementation-of-std-source/121117
inner = io_err.get_ref().map(|e| e as &dyn std::error::Error);
}
_ => return false,
}
} else if let Some(_tls_err) = e.downcast_ref::<rustls::Error>() {
// try downcast to TLS error
return true;
} else if let Some(resolve_err) = e.downcast_ref::<hickory_resolver::ResolveError>() {
// try downcast to DNS error
return resolve_err.is_nx_domain();
} else {
inner = e.source();
}
} else {
break;
}
}
false
}
/// Common usage functionality for the http client.
///
/// These functions allow for cleaner downstream usage free of type parameters and unneeded imports.
@@ -1631,6 +1728,13 @@ where
decode_raw_response(&headers, full)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound { url: Box::new(url) })
} else if is_http_rate_limit_err(&res) {
Err(HttpClientError::EndpointFailure {
url: Box::new(url),
status,
headers: Box::new(headers),
error: String::from("received vercel rate limit challenge response"),
})
} else {
let Ok(plaintext) = res.text().await else {
return Err(HttpClientError::RequestFailure {
+6 -3
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-http-api-common"
description = "Common crate for Nym-related HTTP API interaction"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Common crate for Nym-related HTTP API interaction"
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+7 -2
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-inclusion-probability"
version.workspace = true
description = "Nym active set probability simulator"
edition.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
[dependencies]
log = { workspace = true }
+12 -3
View File
@@ -1,16 +1,23 @@
[package]
name = "nym-ip-packet-requests"
description = "Codec, signing functionality, and different version definitions for IP packet request and responses"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
description = "Codec, signing functionality, and different version definitions for IP packet request and responses"
rust-version.workspace = true
readme.workspace = true
publish = true
[features]
test-utils = ["pnet_packet"]
[dependencies]
pnet_packet = { workspace = true, optional = true }
bincode = { workspace = true }
bytes = { workspace = true }
nym-bin-common = { workspace = true }
@@ -18,8 +25,10 @@ nym-crypto = { workspace = true }
nym-service-provider-requests-common = { workspace = true }
nym-sphinx = { workspace = true }
rand = { workspace = true }
semver = { workspace = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
time = { workspace = true }
tokio = { workspace = true, features = ["time"] }
tokio-util = { workspace = true, features = ["codec"] }
tracing = { workspace = true }
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// Extracted from sdk/rust/nym-sdk/examples/ipr_tunnel.rs
//! ICMP/ICMPv6 packet construction and reply detection helpers for testing
//! IPR connectivity. Gated behind the `test-utils` feature.
use std::net::{Ipv4Addr, Ipv6Addr};
use pnet_packet::Packet;
use pnet_packet::icmp::echo_reply::EchoReplyPacket;
use pnet_packet::icmp::echo_request::MutableEchoRequestPacket;
use pnet_packet::icmp::{IcmpPacket, IcmpTypes};
use pnet_packet::icmpv6::Icmpv6Types;
use pnet_packet::ipv4::{Ipv4Flags, MutableIpv4Packet};
use pnet_packet::ipv6::MutableIpv6Packet;
/// Build a complete IPv4 ICMP echo request packet.
pub fn build_icmp_ping(src: Ipv4Addr, dst: Ipv4Addr, seq: u16) -> Option<Vec<u8>> {
let mut echo = MutableEchoRequestPacket::owned(vec![0u8; 64])?;
echo.set_icmp_type(IcmpTypes::EchoRequest);
echo.set_icmp_code(pnet_packet::icmp::IcmpCode::new(0));
echo.set_sequence_number(seq);
let cksum = pnet_packet::icmp::checksum(&IcmpPacket::new(echo.packet())?);
echo.set_checksum(cksum);
let total_len = 20 + echo.packet().len();
let mut ip = MutableIpv4Packet::owned(vec![0u8; total_len])?;
ip.set_version(4);
ip.set_header_length(5);
ip.set_total_length(total_len as u16);
ip.set_ttl(64);
ip.set_next_level_protocol(pnet_packet::ip::IpNextHeaderProtocols::Icmp);
ip.set_source(src);
ip.set_destination(dst);
ip.set_flags(Ipv4Flags::DontFragment);
ip.set_payload(echo.packet());
let mut buf = ip.consume_to_immutable().packet().to_vec();
let cksum = ipv4_checksum(&buf);
buf[10] = (cksum >> 8) as u8;
buf[11] = cksum as u8;
Some(buf)
}
/// Build a complete IPv6 ICMPv6 echo request packet.
pub fn build_icmpv6_ping(src: Ipv6Addr, dst: Ipv6Addr, seq: u16) -> Option<Vec<u8>> {
let mut echo =
pnet_packet::icmpv6::echo_request::MutableEchoRequestPacket::owned(vec![0u8; 64])?;
echo.set_icmpv6_type(Icmpv6Types::EchoRequest);
echo.set_icmpv6_code(pnet_packet::icmpv6::Icmpv6Code::new(0));
echo.set_sequence_number(seq);
let cksum = pnet_packet::icmpv6::checksum(
&pnet_packet::icmpv6::Icmpv6Packet::new(echo.packet())?,
&src,
&dst,
);
echo.set_checksum(cksum);
let payload_len = echo.packet().len();
let mut ip = MutableIpv6Packet::owned(vec![0u8; 40 + payload_len])?;
ip.set_version(6);
ip.set_payload_length(payload_len as u16);
ip.set_next_header(pnet_packet::ip::IpNextHeaderProtocols::Icmpv6);
ip.set_hop_limit(64);
ip.set_source(src);
ip.set_destination(dst);
ip.set_payload(echo.packet());
Some(ip.consume_to_immutable().packet().to_vec())
}
/// Check if a raw packet is an IPv4 ICMP echo reply destined to `expected_dst`.
pub fn is_echo_reply_v4(data: &[u8], expected_dst: Ipv4Addr) -> bool {
let Some(ip) = pnet_packet::ipv4::Ipv4Packet::new(data) else {
return false;
};
if ip.get_destination() != expected_dst {
return false;
}
if ip.get_next_level_protocol() != pnet_packet::ip::IpNextHeaderProtocols::Icmp {
return false;
}
let Some(reply) = EchoReplyPacket::new(ip.payload()) else {
return false;
};
reply.get_icmp_type() == IcmpTypes::EchoReply
}
/// Check if a raw packet is an IPv6 ICMPv6 echo reply destined to `expected_dst`.
pub fn is_echo_reply_v6(data: &[u8], expected_dst: Ipv6Addr) -> bool {
let Some(ip) = pnet_packet::ipv6::Ipv6Packet::new(data) else {
return false;
};
if ip.get_destination() != expected_dst {
return false;
}
if ip.get_next_header() != pnet_packet::ip::IpNextHeaderProtocols::Icmpv6 {
return false;
}
let Some(reply) = pnet_packet::icmpv6::echo_reply::EchoReplyPacket::new(ip.payload()) else {
return false;
};
reply.get_icmpv6_type() == Icmpv6Types::EchoReply
}
fn ipv4_checksum(header: &[u8]) -> u16 {
let mut sum = 0u32;
for i in (0..20).step_by(2) {
sum += ((header[i] as u32) << 8) | header[i + 1] as u32;
}
while (sum >> 16) > 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
+14
View File
@@ -3,10 +3,22 @@ use std::fmt::{Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
pub mod codec;
#[cfg(feature = "test-utils")]
pub mod icmp_utils;
pub mod response_helpers;
pub mod sign;
pub mod v6;
pub mod v7;
pub mod v8;
pub mod v9;
/// Highest IPR protocol version that is allowed to be sent as a **non-stream** mixnet payload
/// (i.e. not wrapped in `LpFrameKind::SphinxStream`).
pub const MAX_NON_STREAM_VERSION: u8 = v8::VERSION;
/// First IPR protocol version that **requires** the SphinxStream (LP) transport for non-stream
/// mixnet sends, matching the node-side enforcement in `ip-packet-router`.
pub const SPHINX_STREAM_VERSION_THRESHOLD: u8 = v9::VERSION;
// version 3: initial version
// version 4: IPv6 support
@@ -14,6 +26,8 @@ pub mod v8;
// version 6: Increase the available IPs
// version 7: Add signature support (for the future)
// version 8: Anonymous sends
// version 9: LP-framed transport (SphinxStream)
// response_helpers: shared IPR response parsing (nym-ip-packet-client + nym-sdk)
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IpPair {
@@ -0,0 +1,134 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bytes::{Bytes, BytesMut};
use tokio_util::codec::Decoder;
use tracing::{error, info, warn};
use crate::{
IpPair,
codec::MultiIpPacketCodec,
v8::response::{
ConnectResponseReply, ControlResponse, InfoLevel, IpPacketResponse, IpPacketResponseData,
},
};
#[derive(Debug, thiserror::Error)]
pub enum IprResponseError {
#[error("no version byte in message")]
NoVersionByte,
#[error("version mismatch: received v{received}, expected v{expected}")]
VersionMismatch { expected: u8, received: u8 },
#[error("expected control response, got {0:?}")]
UnexpectedResponse(IpPacketResponseData),
#[error("connect denied: {0:?}")]
ConnectDenied(crate::v8::response::ConnectFailureReason),
}
pub enum MixnetMessageOutcome {
IpPackets(Vec<Bytes>),
Disconnect,
}
// Extracted from:
// nym-ip-packet-client/src/helpers.rs — check_ipr_message_version()
// sdk/rust/nym-sdk/src/ip_packet_client/listener.rs — check_ipr_message_version()
/// Check that the first byte of an IPR message matches the expected protocol version.
pub fn check_ipr_message_version(data: &[u8], expected: u8) -> Result<(), IprResponseError> {
let version = data.first().ok_or(IprResponseError::NoVersionByte)?;
if *version != expected {
return Err(IprResponseError::VersionMismatch {
expected,
received: *version,
});
}
Ok(())
}
// Extracted from:
// nym-ip-packet-client/src/connect.rs — handle_connect_response() + handle_ip_packet_router_response()
// sdk/rust/nym-sdk/src/ip_packet_client/discovery.rs — parse_connect_response()
/// Parse an IPR connect response, returning allocated IPs on success.
pub fn parse_connect_response(response: IpPacketResponse) -> Result<IpPair, IprResponseError> {
let control_response = match response.data {
IpPacketResponseData::Control(c) => c,
other => return Err(IprResponseError::UnexpectedResponse(other)),
};
match *control_response {
ControlResponse::Connect(connect_resp) => match connect_resp.reply {
ConnectResponseReply::Success(success) => Ok(success.ips),
ConnectResponseReply::Failure(reason) => Err(IprResponseError::ConnectDenied(reason)),
},
_ => Err(IprResponseError::UnexpectedResponse(
IpPacketResponseData::Control(control_response),
)),
}
}
// Extracted from:
// nym-ip-packet-client/src/listener.rs — IprListener::handle_reconstructed_message()
// sdk/rust/nym-sdk/src/ip_packet_client/listener.rs — handle_ipr_response()
/// Parse raw IPR response bytes into an outcome.
///
/// Logs non-fatal conditions (unknown control messages, deserialization
/// failures) and returns `None` for them.
pub fn handle_ipr_response(data: &[u8]) -> Option<MixnetMessageOutcome> {
match IpPacketResponse::from_bytes(data) {
Ok(response) => match response.data {
IpPacketResponseData::Data(data_response) => {
let mut codec = MultiIpPacketCodec::new();
let mut buf = BytesMut::from(data_response.ip_packet.as_ref());
let mut packets = Vec::new();
loop {
match codec.decode(&mut buf) {
Ok(Some(packet)) => packets.push(packet.into_bytes()),
Ok(None) => break,
Err(e) => {
warn!("Failed to decode bundled IP packet: {e}");
break;
}
}
}
Some(MixnetMessageOutcome::IpPackets(packets))
}
IpPacketResponseData::Control(control_response) => match *control_response {
ControlResponse::Connect(_) => {
info!("Received connect response when already connected - ignoring");
None
}
ControlResponse::Disconnect(_) | ControlResponse::UnrequestedDisconnect(_) => {
info!("Received disconnect from IPR");
Some(MixnetMessageOutcome::Disconnect)
}
ControlResponse::Pong(_) => {
info!("Received pong response");
None
}
ControlResponse::Health(_) => {
info!("Received health response");
None
}
ControlResponse::Info(info_resp) => {
let msg = format!(
"Received info response from the mixnet: {}",
info_resp.reply
);
match info_resp.level {
InfoLevel::Info => info!("{msg}"),
InfoLevel::Warn => warn!("{msg}"),
InfoLevel::Error => error!("{msg}"),
}
None
}
},
},
Err(err) => {
warn!("Failed to deserialize IPR response: {err}");
None
}
}
}
+6 -2
View File
@@ -179,11 +179,15 @@ impl IpPacketResponse {
make_bincode_serializer().serialize(self)
}
pub fn from_bytes(data: &[u8]) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(data)
}
pub fn from_reconstructed_message(
message: &nym_sphinx::receiver::ReconstructedMessage,
) -> Result<Self, bincode::Error> {
use bincode::Options;
make_bincode_serializer().deserialize(&message.message)
Self::from_bytes(&message.message)
}
}
+34
View File
@@ -0,0 +1,34 @@
pub const VERSION: u8 = 9;
/// Minimum nym-node release version that supports v9 (LP Stream framing).
/// Nodes running older versions will not understand LP-wrapped packets.
pub const MIN_RELEASE_VERSION: semver::Version = semver::Version::new(1, 30, 0);
// v9 uses the same wire format as v8. The version bump indicates
// the message was sent with LP framing (SphinxStream).
//
// Types are re-exported for deserialization/matching. Use the wrapper
// constructors below to create correctly-versioned packets — never
// manually set `protocol.version` or `response.version`.
pub use super::v8::{request, response};
/// Create a v9 connect request (version byte set to 9).
pub fn new_connect_request(buffer_timeout: Option<u64>) -> (request::IpPacketRequest, u64) {
let (mut req, id) = request::IpPacketRequest::new_connect_request(buffer_timeout);
req.protocol.version = VERSION;
(req, id)
}
/// Create a v9 data request (version byte set to 9).
pub fn new_data_request(data: bytes::Bytes) -> request::IpPacketRequest {
let mut req = request::IpPacketRequest::new_data_request(data);
req.protocol.version = VERSION;
req
}
/// Create a v9 IP packet response (version byte set to 9).
pub fn new_ip_packet_response(ip_packet: bytes::Bytes) -> response::IpPacketResponse {
let mut resp = response::IpPacketResponse::new_ip_packet(ip_packet);
resp.version = VERSION;
resp
}
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-mixnode-common"
description = "Common crate for Nym Mix Nodes"
version.workspace = true
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "Common crate for Nym Mix Nodes"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+7 -2
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-network-defaults"
version.workspace = true
description = "Nym network defaults"
edition.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# Exclude build.rs from published crate - it's only used for dev-time sync
# of env files and requires workspace context
exclude = ["build.rs"]
+5 -1
View File
@@ -1,12 +1,16 @@
[package]
name = "nym-node-tester-utils"
description = "Utils for the Nym Node Tester"
version.workspace = true
authors.workspace = true
edition = "2021"
license.workspace = true
description = "Utils for the Nym Node Tester"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -1
View File
@@ -1,13 +1,16 @@
[package]
name = "nym-nonexhaustive-delayqueue"
description = "A copy of tokio-util delay_queue with `Sleep` and `Instant` being replaced with`wasm_timer` equivalents"
version.workspace = true
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
license.workspace = true
description = "A copy of tokio-util delay_queue with `Sleep` and `Instant` being replaced with`wasm_timer` equivalents"
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+4 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "nym-cache"
description = "Helper functions around a RwLock for writing to local cache of items"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
description = "Helper functions around a RwLock for writing to local cache of items"
publish = true
[dependencies]
tokio = { workspace = true, features = ["sync"] }
+8 -3
View File
@@ -1,11 +1,16 @@
[package]
name = "nym-common"
description = "Runtime diagnostics for high frequency logging, debugging and error handling utilities"
version.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
description = "Runtime diagnostics for high frequency logging, debugging and error handling utilities"
license.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
rust-version.workspace = true
readme.workspace = true
publish = true
[lints]
workspace = true
@@ -1,3 +1,6 @@
// Copyright 2026 Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::fmt::{self, Write};
pub fn format_debug_bytes(bytes: &[u8]) -> Result<String, fmt::Error> {
+1
View File
@@ -2,6 +2,7 @@
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod debug;
mod error;
pub mod flood;

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