Compare commits

...

132 Commits

Author SHA1 Message Date
durch 1364316f95 FMT, fix typo 2025-06-25 00:43:53 +02:00
durch 55f0471481 fix(nym-api): refactor simulation API to remove redundant data structures
- Remove old_method/new_method nested objects from NodeMethodComparison
- Use direct fields for production_performance and simulated_performance
- Update build_node_comparisons_from_single_dataset to create cleaner structure
- Fix calculate_summary_statistics to work with new data model
- Update all tests to match new structure

The API now returns a single dataset with production performance values
included directly, rather than separate old/new method objects.

- Deduplicate routes, to control for deterministic routing
2025-06-25 00:40:52 +02:00
durch a127a303f1 refactor(nym-api): simplify simulation to only run new method
Since old method data is already available in production database,
simulation now only calculates new method performance. The API combines
production data with simulated data at query time.

Key changes:
- Remove old_method_simulation and run_both_methods config
- Add production_performance field to NodePerformanceData model
- Update API endpoints to fetch production data from node annotations cache
- Refactor compare_methods to use single performance dataset
- Fix route analysis comparison to handle missing old method data
- Update all tests to use new single-dataset approach

This simplifies the simulation code and ensures consistency with
production calculations.
2025-06-24 23:43:24 +02:00
durch 730c88b0d2 feat(nym-api): add reliability distribution categories to simulation comparison
- Added ReliabilityDistribution struct with 6 categories: excellent (>95%), very_good (90-95%), good (75-90%), moderate (50-75%), poor (25-50%), very_poor (<25%)
- Updated ComparisonSummaryStats to include distribution breakdowns for both old and new methods
- Provides better insight into performance distribution beyond averages
- Added tests for distribution calculation
2025-06-24 21:53:29 +02:00
durch 9cb18cd163 Bump nnm 2025-06-24 21:35:06 +02:00
durch 6993ef0dc8 Bump nym-api 2025-06-24 21:31:50 +02:00
durch 0e53562ce2 Fmt 2025-06-24 21:31:15 +02:00
Drazen Urch c1acef9bc8 Check gateway supported versions (#5860)
* Check gateway supported versions

* Fix boxed errors

* Fmt

* feat(client-core): integrate gateway protocol validation into SDK

Enable protocol version checking for all SDK-based clients by using
gateways_for_init_with_protocol_validation in MixnetClientBuilder.
This ensures clients only connect to gateways with compatible protocol
versions, preventing potential communication issues.

- Replace gateways_for_init with gateways_for_init_with_protocol_validation
- Add import for the new validation function
- Protocol validation now active for network monitor and all SDK users

* refactor(client-core): allow gateways with newer protocol versions

Change protocol validation to be more permissive - instead of rejecting
gateways with newer protocol versions, now logs a warning and continues.
This enables graceful degradation when gateways upgrade, relying on their
backward compatibility while signaling users to update their clients.

Changes:
- Accept gateways with protocol version > client version
- Log warning about version mismatch suggesting client update
- Update log messages from "validation" to "check" for clarity
- Add trace logging showing both gateway and client versions

This prevents connectivity issues when gateways upgrade before clients,
improving overall network resilience.

* feat(nym-network-monitor): add diagnostic logging for ConnectionRefused debugging

- Add detailed logging for client rotation lifecycle with [CLIENT_ROTATION_*] tags
- Add request tracking with unique IDs using the random message as identifier
- Add HTTP connection acceptance logging with [HTTP_REQUEST] tags
- Improve locust script with connection error handling and backoff strategy
- Add TCP backlog configuration support (default 1024)
- Add socket configuration with SO_REUSEADDR and configurable backlog
- Add HTTP timeout and tracing layers for better observability

These changes help diagnose when and why ConnectionRefused errors occur during load testing,
particularly around client rotation periods.

* fix(nym-api): calculate route average reliability as node mean instead of route-based

- Changed route average reliability calculation to use mean of all node reliabilities
- Added median calculation alongside mean for better statistical representation
- Fixed null average reliability for old method which doesn't analyze routes
- Rounded all float values to 2 decimal places in API responses for cleaner display
- Store median values in analysis_parameters JSON field
2025-06-24 21:28:07 +02:00
durch d67a968e76 fix(nym-api): correct reliability value conversions in performance simulation
The old method simulation was incorrectly treating reliability values (0-1 fractions)
as percentages, causing all performance scores to be truncated to 0 when cast to u64.

Changes:
- Old method: multiply reliability by 100 before passing to Performance::from_percentage_value()
- New method: remove division by 100 since Performance::naive_try_from_f64() expects fractions
- Route analysis: multiply average reliability by 100 for percentage display

This ensures consistent handling where internal calculations use fractions (0-1)
while storage and API responses use percentages (0-100).
2025-06-24 19:05:07 +02:00
durch 8ad641f8f8 fix(nym-api): allow simulation mode to run when another API is advancing epoch
In simulation mode, the nym-api should be able to run performance
simulations regardless of which instance is handling epoch advancement,
since simulations don't perform any blockchain transactions.

This fix:
- Allows simulation mode to proceed when another API is advancing the epoch
- Skips epoch transition attempts in simulation mode
- Skips actual reward distribution blockchain operations in simulation mode
2025-06-24 17:44:25 +02:00
durch b45bb9e7e9 Add socket-level websocket connection health checking
Implements proper socket-level health checking for gateway websocket connections:

- Add is_connection_alive() method to GatewayTransceiver trait
- Implement socket-level health check using TcpStream::peek() on Unix systems
- Add is_gateway_connection_alive() method to MixnetClient for easy access
- Update network monitor to use connection health checks before sending
- Fix axum router state configuration in network monitor

The health check performs actual OS-level socket validation rather than just
checking file descriptor existence, providing reliable connection status.
2025-06-06 12:15:58 +02:00
durch cab072f2d0 Fix client drop loop hanging in network monitor
Replace infinite busy-wait loop with timeout-based client cleanup to prevent potential system hangs during client lifecycle management.
2025-06-06 11:02:06 +02:00
durch c389f43dd0 Put client factory back 2025-06-06 10:51:32 +02:00
durch 71c24d8c81 Respond with 504 to timeouts 2025-06-06 10:49:03 +02:00
durch e336e02df2 Dont use hickory dns for gateway client 2025-06-06 10:42:43 +02:00
durch 74db9be819 Fix config cleanup 2025-06-06 10:22:55 +02:00
durch 77c4acf602 Add RESET_CONFIG option to entrypoint 2025-06-06 09:38:28 +02:00
durch f4d0ac855c Add periodic route data cleanup to epoch operations
Implements automatic cleanup of route monitoring results to prevent
unbounded storage growth while maintaining data for performance analysis.

- Add purge_old_routes() method to StorageManager following existing patterns
- Integrate route cleanup into purge_old_statuses() wrapper function
- Route data now purged every epoch with 48-hour retention, to facilitate comparisons with legacy data
- Update logging to reflect cleanup of both node statuses and routes
2025-06-06 09:12:21 +02:00
durch eb1c7d649e Client per request 2025-06-05 12:06:15 +02:00
durch 75f34ef51b Add timeout to locust 2025-06-05 11:43:04 +02:00
durch 4f7fa557d5 Optimize database queries by eliminating N+1 patterns in simulation system
This commit addresses critical N+1 query performance issues identified in
the reward simulation system. The optimizations significantly reduce database
round trips and improve performance when processing large datasets.

**Key Optimizations:**

1. **Batch Identity Key Lookups**
   - Added `get_mixnode_identity_keys_batch()` and `get_gateway_identity_keys_batch()`
   - Updated simulation performance conversion to use batch operations
   - Reduced from N individual queries to 2 batch queries

2. **Batch Node Classification**
   - Added `classify_nodes_batch()` method for mixnode/gateway determination
   - Updated reliability calculation methods to use batch classification
   - Reduced from N individual lookups to 2 batch queries

3. **Batch Epoch Metadata Enhancement**
   - Added `count_simulated_node_performance_for_epochs_batch()`
   - Added `get_available_calculation_methods_for_epochs_batch()`
   - Updated API handlers to use batch operations for metadata enhancement
   - Reduced from 2N queries to 2 batch queries for epoch data

4. **Bulk Insert Optimizations**
   - Converted individual INSERT operations to use `sqlx::QueryBuilder::push_values()`
   - Optimized simulation data insertion methods
   - Eliminated transaction overhead from individual inserts

**Performance Impact:**
- Before: N+2N database queries for N nodes/epochs
- After: 2+2 batch queries regardless of dataset size
- Significant performance improvement for large simulation datasets

All changes maintain backward compatibility while providing substantial
performance benefits for the reward simulation system.
2025-06-05 10:45:08 +02:00
durch a96fb098c2 Locust sleep if no clients are available 2025-06-05 09:54:39 +02:00
durch ad5c6ab829 Enhance simulation system with performance comparison framework
Refactors the simulation system to focus on performance methodology comparison
rather than reward amounts, enabling robust analysis of old vs new calculation
methods. Key improvements:

- Replace simulated_rewards table with performance_comparisons for better metrics
- Add performance_rankings table for ranking analysis across methodologies
- Enhance database schema with additional performance tracking fields
- Update simulation coordinator to use performance-focused data structures
- Add comprehensive performance ranking calculations
- Improve API models and handlers for performance comparison workflows
- Update SQLx query cache with new database schema changes

This provides a foundation for data-driven performance methodology evaluation
while maintaining separation from actual reward calculations.
2025-06-04 16:59:42 +02:00
durch b3d07e8832 Tests 2025-06-04 11:28:47 +02:00
durch e761255174 Add complete simulation API layer for reward method comparison
This completes Phase 3 of the simulation system implementation:

- Add comprehensive REST API endpoints for simulation data access
- Implement /v1/simulation/* routes with full CRUD operations
- Support JSON/CSV export for external analysis
- Add statistical comparison between old vs new methods
- Provide node performance history tracking
- Include proper error handling and response formatting
- Simplify simulation coordinator to remove unused complex return types
- Clean up dead code while maintaining all functionality
- Pass clippy with no warnings

The simulation API provides complete access to:
- Simulation epoch listing and details
- Method comparison analytics (old 24h vs new 1h)
- Node performance analysis across epochs
- Route reliability statistics
- Export capabilities for further analysis

All simulation data is persisted and accessible via REST endpoints.
2025-06-03 15:38:58 +02:00
durch e4a20f9cf5 Implement core simulation logic for dual reward calculations
Add complete simulation engine that compares old (24h cache-based) vs new (1h route-based)
reward calculation methodologies with full integration into epoch operations.

Core Simulation Engine:
- Add SimulationCoordinator with configurable time windows and comparison settings
- Implement dual calculation methods with proper Performance type conversions
- Add comprehensive error handling with DatabaseError variant in RewardingError
- Store simulation results in database with proper relationship constraints

Old Method Implementation (24h Cache-Based):
- Wrap existing reliability calculation using get_all_avg_mix_reliability_in_last_24hr()
- Convert reliability percentages to Performance types using from_percentage_value()
- Maintain exact same logic as production for accurate baseline comparison
- Generate simulation data structures with proper metadata

New Method Implementation (1h Route-Based):
- Leverage calculate_corrected_node_reliabilities_for_interval() for route analysis
- Support configurable time windows (default 1 hour vs 24 hours)
- Provide detailed route statistics including success rates and failure analysis
- Convert route reliability data to Performance types with naive_try_from_f64()

Epoch Operations Integration:
- Extend EpochAdvancer struct with optional SimulationConfig field
- Update constructor and start method to accept simulation configuration
- Add simulation trigger in perform_epoch_operations() before real rewarding
- Ensure simulation failures don't break epoch advancement process

CLI Integration:
- Update run.rs to handle both --enable-rewarding and --simulate-rewarding modes
- Create SimulationConfig from rewarding.debug configuration settings
- Implement mutual exclusivity between real rewarding and simulation mode
- Skip permission checks for simulation-only mode (no blockchain transactions)

The simulation system runs in parallel with epoch operations, storing comparative
data for analysis without affecting production reward distribution.
2025-06-03 14:52:13 +02:00
durch 1eefe8a579 Add simulated rewarding system foundation
Implement foundation for simulated reward calculations to compare old (24h cache-based)
vs new (1h route-based) methodologies without blockchain transactions.

Database Changes:
- Add migration with 4 new tables for simulation system
- simulated_reward_epochs: tracks each simulation run
- simulated_node_performance: stores performance calculations
- simulated_rewards: stores reward calculation results
- simulated_route_analysis: metadata for route analysis
- Add comprehensive indexes for efficient querying

Configuration Changes:
- Add simulation_mode flag to rewarding configuration
- Add CLI flag --simulate-rewarding with proper dependencies/conflicts
- Add validation for simulation-specific settings
- Add time window configuration for new method (default: 1 hour)

Storage Layer:
- Add model structs with SQLx FromRow derives for all simulation tables
- Add comprehensive CRUD methods for simulation data management
- Add proper type annotations to fix SQLx compile issues
- Maintain separation between simulation and real rewarding logic

The simulation mode is mutually exclusive with real rewarding and does not
require mnemonic since no blockchain transactions are performed.
2025-06-03 12:39:44 +02:00
durch e9dc848950 Bump nym-api 2025-06-02 12:49:13 +02:00
durch 81162fba7e Allow nym-api init fail 2025-06-02 12:45:21 +02:00
durch be36da68b1 Clap value_delimiter 2025-06-02 11:58:12 +02:00
durch 21a56e307f Bump NNM 2025-06-02 09:31:09 +02:00
durch bd966383be Towards untangling nym-api client 2025-06-02 09:30:36 +02:00
durch 7626785ce4 Bump NM version 2025-05-27 15:40:59 +02:00
durch 6f79d39d48 Filter out non mixnodes 2025-05-27 15:40:33 +02:00
durch 014b5f767a Log APIs used 2025-05-27 15:23:18 +02:00
durch e0966565e6 Mnemonic to run, bump 2025-05-27 13:38:13 +02:00
durch c6aec663b7 Bump nym-api version 2025-05-27 13:18:31 +02:00
durch 7d041ddd44 Explicit mnemonic to entrypoint 2025-05-27 13:02:38 +02:00
durch 5d8bdc6570 Bunch of new query files 2025-05-27 10:28:00 +02:00
durch 06c412b3ba Remove debug logging 2025-05-27 10:25:27 +02:00
durch 356cf00106 Put the monitoring back properly 2025-05-27 10:25:27 +02:00
durch 58493a69aa Fix submission URLs 2025-05-27 10:25:27 +02:00
durch e881da834b More NM logging 2025-05-27 10:25:27 +02:00
durch eee9d8ab0c DEBUG: disable epoch operations, less noisy logging 2025-05-27 10:25:27 +02:00
durch 09026307f4 Debug logging for nym-api 2025-05-27 10:25:27 +02:00
durch 507ddf246c Stagger out route sending 2025-05-27 10:25:26 +02:00
durch 8d8ce29113 Update NM readme, fmt 2025-05-27 10:25:26 +02:00
durch 3be9e06bef sqlx prepare, bunch of nits 2025-05-27 10:25:26 +02:00
durch 770078a9ed Delete test script 2025-05-27 10:25:26 +02:00
durch fcffebfe45 Raw route handling and reliability corrections 2025-05-27 10:25:19 +02:00
durch 9c7d79683b Force routing through all nodes 2025-05-27 10:21:02 +02:00
durch c7f34d04c0 Support submitting to multiple APIs 2025-05-27 10:21:02 +02:00
Jon Häggblad 9a62581272 Update codeowners 2025-05-23 08:54:25 +02:00
Drazen Urch ebb8e4ef19 Build and push nym-api action (#5793) 2025-05-22 19:12:29 +02:00
mfahampshire a0057eb223 add notice re sdks (#5792)
* add notice re sdks

* fix borked notice

* fix another borked notice
2025-05-22 10:25:25 +00:00
import this 39195d79f5 [DOCs/operators]: Hotfix - Round decimalds to common convention (#5791) 2025-05-21 16:02:09 +00:00
import this ede5ffaffc [DOCs/operaotrs]: Automate Rewards calculator default state value (#5790) 2025-05-21 09:47:04 +00:00
Bogdan-Ștefan Neacşu ed16505137 Fix contains ticketbook function that always returned true (#5787) 2025-05-20 17:18:06 +03:00
Simon Wicky 03bec90b83 swap a decode into a fromrow to please future postgres feature (#5785)
* swap a decode into a fromrow to please future postgres feature

* add missing feature and missing crate in log filter
2025-05-20 15:48:09 +02:00
import this add57b2c14 [DOCs/operators]: Rewards calculator quick tweak (#5786) 2025-05-20 13:26:55 +00:00
import this 927ca8970c [DOCs/operators]: Tokenomics cleanup (#5782)
* correcting APY to ROI

* cleanup and small edits

* add tooltip on ROI
2025-05-19 11:12:36 +00:00
Jack Wampler 47d222b13d more relaxed usage of reqwest accept-encoding (#5779) 2025-05-16 13:03:24 -06:00
benedettadavico 3b2481e5a5 merge appenzeller to develop 2025-05-16 12:59:02 +02:00
import this de47982585 [DOCs/operators]: Updated tokenomics, reward calculator & release notes v2025.9-appenzeller (#5769)
* correct expression about node stake

* typo fix

* sharpen overview

* detail rewards formula

* make calculator into standalone jsx component and import it

* finish pr for review

* fix alpha example with correct formula

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

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

* Add upgrade for config value

* Fix ip packet router too

* Fix clippy

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* helper method for including expiration headers

* add expires header for /active nodes responses

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

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

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

* add RememberMe to clients config

* change stats collection logic to handle remember me

* set up sdk client to send remember me message

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

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

* client parsing support

* cargo fmt

* missing changes to nym-api tests

* fixed node status api build + adjusted NymApiClient construction

* NMv2 fixes + further api changes

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

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

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

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

* ready for review

* move info block lower down

* edit phrasing and add formulas

* delete extra syntax

* update syntax

* add release notes
2025-04-30 12:13:41 +00:00
dynco-nym fbcf44eeb9 Add /account/{address} (#5673)
* Add /account/{address}

* Don't query vesting info

* Don't query rewards

* Remove unused code

* Fix clippy

* Fix build.rs build on Windows

* Addressing PR feedback
- not cloning nym nodes from cache
- reduced number of nym nodes kept in memory
- reduced number of iterations to read all data
- removed some fields

* Fix total_delegations

* Optimize nym_nodes hashmap

* Split flow into functions

* Remove vesting info

* Add caching for endpoint

* Cache optimizations

* Return early if balance is 0

* Refactor state cloning shenanigans
2025-04-29 13:23:14 +02:00
benedettadavico e594630314 update changelog 2025-04-29 12:19:54 +02:00
dynco-nym f4785099c2 Add nodes/delegations endpoint (#5733)
* WIP

* Add /delegations endpoint

* Bump package version

* Remove node_id field
2025-04-28 23:59:40 +02:00
benedettadavico 9c2595d9ef bump versions 2025-04-25 15:47:20 +02:00
Jędrzej Stuczyński b04d3ba376 add reserved byte to reply surb serialisation (#5731) 2025-04-25 10:05:38 +01:00
Jędrzej Stuczyński 5ad1f0b61a add reserved byte to reply surb serialisation (#5731) 2025-04-25 10:02:32 +01:00
Jędrzej Stuczyński b2dfdda210 NET-271: bugfix: use node saturation instead of its stake for selection weight (#5717) 2025-04-24 15:39:14 +01:00
dependabot[bot] 41ef3a26f5 build(deps-dev): bump http-proxy-middleware in /wasm/client/internal-dev (#5719)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 10:27:12 +01:00
dependabot[bot] bae1b488de build(deps): bump golang.org/x/net in /wasm/mix-fetch/go-mix-conn (#5720)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.36.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.36.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 10:26:52 +01:00
dependabot[bot] 40cf2c441a build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group (#5722)
Bumps the patch-updates group with 1 update: [clap](https://github.com/clap-rs/clap).


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

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 10:26:32 +01:00
windy-ux 34871b14b3 / change landing page tab title to "Nym docs" (#5729) 2025-04-23 15:32:34 +00:00
dynco-nym c14b010f9e Eliminate duplicate node_ids from endpoint (#5728)
* Improve swagger definitions

* Sort data in DB

* Improve logging

* Store gw description to nym nodes table

* Move explorer related path to /explorer

* Bump package version
2025-04-23 15:19:15 +02:00
benedetta davico 04f75e7e48 Merge pull request #5726 from nymtech/release/2025.7-tex
Merge tex to develop
2025-04-22 10:50:40 +02:00
Bogdan-Ștefan Neacşu 866dcd1e39 Peer handle should die more gracefully (#5704)
* Don't exit handle without having peer removed

* Kernel going back to 0 is not an error

* Fix build

* Add stronger message for failure on last resort remove
2025-04-22 10:34:46 +03:00
Bogdan-Ștefan Neacşu a8526d698e Remove inactive peers (#5721) 2025-04-17 12:49:43 +03:00
import this 3f5e0cdb1f [DOCs/operators]: Release notes for v2025.7 tex (#5718)
* bump version in setup pafe

* testing menu changes propagation

* add release notes to changelog

* add NSL announcement

* add announcement for debian versions

* sync up styling
2025-04-16 12:56:05 +00:00
Jack Wampler 96239a7812 allow client to specify whether to include mix hops or not in MixnetClient Debug Config (#5696) 2025-04-15 10:49:11 -06:00
windy-ux 762cfb8709 Merge branch 'locale/add_docs_favicon' (#5716) 2025-04-15 16:16:28 +00:00
dependabot[bot] 9835ad3396 build(deps): bump next from 14.2.21 to 14.2.25 (#5655)
Bumps [next](https://github.com/vercel/next.js) from 14.2.21 to 14.2.25.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.21...v14.2.25)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:49:05 +01:00
dependabot[bot] f73a3ac932 build(deps): bump openssl from 0.10.70 to 0.10.72 in /nym-wallet (#5688)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.70 to 0.10.72.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.70...openssl-v0.10.72)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.72
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:48:56 +01:00
dependabot[bot] 5af4d8d862 build(deps): bump actions/checkout from 3 to 4 (#5700)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:48:45 +01:00
Jack Wampler 2c81195e79 Update Hickory DNS "0.24.4" to "0.25" (#5709)
update the dependency on hickory dns to the latest minor version
2025-04-15 09:30:23 -06:00
dependabot[bot] 4a9066fb6b build(deps): bump pnpm/action-setup from 4.0.0 to 4.1.0 (#5436)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:24:17 +01:00
dependabot[bot] 86cc600ea3 build(deps): bump crossbeam-channel from 0.5.14 to 0.5.15 in /nym-wallet (#5703)
Bumps [crossbeam-channel](https://github.com/crossbeam-rs/crossbeam) from 0.5.14 to 0.5.15.
- [Release notes](https://github.com/crossbeam-rs/crossbeam/releases)
- [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-channel-0.5.14...crossbeam-channel-0.5.15)

---
updated-dependencies:
- dependency-name: crossbeam-channel
  dependency-version: 0.5.15
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:22:39 +01:00
dependabot[bot] 459b109b5c build(deps): bump the patch-updates group across 1 directory with 7 updates (#5708)
Bumps the patch-updates group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.97` | `1.0.98` |
| [clap](https://github.com/clap-rs/clap) | `4.5.34` | `4.5.36` |
| [env_logger](https://github.com/rust-cli/env_logger) | `0.11.7` | `0.11.8` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.1.0` | `1.1.1` |
| [hyper-util](https://github.com/hyperium/hyper-util) | `0.1.10` | `0.1.11` |
| [tendermint](https://github.com/informalsystems/tendermint-rs) | `0.40.1` | `0.40.3` |
| [tendermint-rpc](https://github.com/informalsystems/tendermint-rs) | `0.40.1` | `0.40.3` |



Updates `anyhow` from 1.0.97 to 1.0.98
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.97...1.0.98)

Updates `clap` from 4.5.34 to 4.5.36
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.34...clap_complete-v4.5.36)

Updates `env_logger` from 0.11.7 to 0.11.8
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.7...v0.11.8)

Updates `flate2` from 1.1.0 to 1.1.1
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.1.0...1.1.1)

Updates `hyper-util` from 0.1.10 to 0.1.11
- [Release notes](https://github.com/hyperium/hyper-util/releases)
- [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.10...v0.1.11)

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

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

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.98
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: clap
  dependency-version: 4.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: env_logger
  dependency-version: 0.11.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: flate2
  dependency-version: 1.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: hyper-util
  dependency-version: 0.1.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tendermint
  dependency-version: 0.40.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
- dependency-name: tendermint-rpc
  dependency-version: 0.40.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 16:22:00 +01:00
benedetta davico 08b6be93c4 Update publish-nym-binaries.yml 2025-04-15 15:29:46 +02:00
benedetta davico f0d3d41a1f Update publish-nym-binaries.yml 2025-04-15 14:27:34 +02:00
import this 9a42cab16d testing menu change (#5711) 2025-04-15 12:17:11 +00:00
import this 970db22702 [DOCs]: Menu change (#5710) 2025-04-15 12:00:33 +00:00
benedettadavico b9dcafa04f update wallet changelog 2025-04-14 15:39:16 +02:00
benedettadavico 260a7de083 update changelog 2025-04-14 15:06:28 +02:00
benedetta davico 51ca727ff2 revert nym-api version bump 2025-04-14 15:03:37 +02:00
409 changed files with 14664 additions and 7155 deletions
+1 -11
View File
@@ -14,7 +14,6 @@
# contracts
/contracts/mixnet @durch @jstuczyn
/contracts/vesting @durch @jstuczyn
/contracts/service-provider-directory @octol
# crypto code
/common/crypto/ @jstuczyn
@@ -22,14 +21,5 @@
/common/dkg/ @jstuczyn
/common/nymsphinx/ @jstuczyn
# rust sdk
/sdk/rust/ @octol
# nym-connect (rust)
/nym-connect/desktop/src-tauri/ @octol
# nym-wallet (rust)
/nym-wallet/src-tauri/ @octol
# documentation
/documentation @mfahampshire
/documentation @mfahampshire
-2
View File
@@ -31,5 +31,3 @@ updates:
update-types:
- "patch"
open-pull-requests-limit: 10
assignees:
- "octol"
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
uses: pnpm/action-setup@v4.1.0
with:
version: 9
- uses: actions/setup-node@v4
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
run: sudo apt-get install -y rsync
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
uses: pnpm/action-setup@v4.1.0
with:
version: 9
- uses: actions/setup-node@v4
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Rust
uses: actions-rs/toolchain@v1
+1 -3
View File
@@ -19,9 +19,7 @@ jobs:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
strategy:
fail-fast: false
matrix:
platform: [custom-ubuntu-22.04]
runs-on: ${{ matrix.platform }}
runs-on: arc-ubuntu-22.04
outputs:
release_id: ${{ steps.create-release.outputs.id }}
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
@@ -31,7 +31,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+51
View File
@@ -0,0 +1,51 @@
name: Build and upload Nym APU container to harbor.nymte.ch
on:
workflow_dispatch:
env:
WORKING_DIRECTORY: "."
CONTAINER_NAME: "nym-api"
jobs:
build-container:
runs-on: arc-ubuntu-22.04-dind
steps:
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.nymte.ch
username: ${{ secrets.HARBOR_ROBOT_USERNAME }}
password: ${{ secrets.HARBOR_ROBOT_SECRET }}
- name: Checkout repo
uses: actions/checkout@v4
- name: Configure git identity
run: |
git config --global user.email "lawrence@nymtech.net"
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
- name: Remove existing tag if exists
run: |
echo "Checking if tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} exists..."
if git rev-parse ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then
echo "Tag ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} already exists"
git push --delete origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
git tag -d ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
fi
- name: Create tag
run: |
git tag -a ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}"
git push origin ${{ env.CONTAINER_NAME }}-${{ steps.get_version.outputs.result }}
- name: BuildAndPushImageOnHarbor
run: |
docker build -f nym-api.dockerfile ${{ env.WORKING_DIRECTORY }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+4
View File
@@ -59,3 +59,7 @@ nym-api/redocly/formatted-openapi.json
*.sqlite
.build
**/settings.sql
**/enter_db.sh
CLAUDE.md
+74
View File
@@ -4,6 +4,80 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.9-appenzeller] (2025-05-13)
- build(deps): bump clap from 4.5.36 to 4.5.37 in the patch-updates group ([#5722])
- build(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 in /wasm/mix-fetch/go-mix-conn ([#5720])
- build(deps-dev): bump http-proxy-middleware from 2.0.6 to 2.0.9 in /wasm/client/internal-dev ([#5719])
- Add /account/{address} ([#5673])
- Add contains ticketbook data db query ([#5670])
[#5722]: https://github.com/nymtech/nym/pull/5722
[#5720]: https://github.com/nymtech/nym/pull/5720
[#5719]: https://github.com/nymtech/nym/pull/5719
[#5673]: https://github.com/nymtech/nym/pull/5673
[#5670]: https://github.com/nymtech/nym/pull/5670
## [2025.8-tourist] (2025-04-29)
- add reserved byte to reply surb serialisation ([#5731])
- Remove inactive peers ([#5721])
- Update Hickory DNS "0.24.4" to "0.25" ([#5709])
- build(deps): bump the patch-updates group across 1 directory with 7 updates ([#5708])
- Peer handle should die more gracefully ([#5704])
- build(deps): bump crossbeam-channel from 0.5.14 to 0.5.15 ([#5702])
- build(deps): bump actions/checkout from 3 to 4 ([#5700])
- Feature/updated sphinx payload keys ([#5698])
- Bump the nym-vpn deb metapackage to 1.0 ([#5697])
- Make mix hops optional for Mixnet Client ([#5696])
- build(deps): bump tokio from 1.44.1 to 1.44.2 ([#5693])
- Feature/replay protection ([#5682])
- Adding fresh nym-api tests and workflow ([#5659])
- build(deps): bump next from 14.2.21 to 14.2.25 ([#5655])
- build(deps): bump pnpm/action-setup from 4.0.0 to 4.1.0 ([#5436])
[#5731]: https://github.com/nymtech/nym/pull/5731
[#5721]: https://github.com/nymtech/nym/pull/5721
[#5709]: https://github.com/nymtech/nym/pull/5709
[#5708]: https://github.com/nymtech/nym/pull/5708
[#5704]: https://github.com/nymtech/nym/pull/5704
[#5702]: https://github.com/nymtech/nym/pull/5702
[#5700]: https://github.com/nymtech/nym/pull/5700
[#5698]: https://github.com/nymtech/nym/pull/5698
[#5697]: https://github.com/nymtech/nym/pull/5697
[#5696]: https://github.com/nymtech/nym/pull/5696
[#5693]: https://github.com/nymtech/nym/pull/5693
[#5682]: https://github.com/nymtech/nym/pull/5682
[#5659]: https://github.com/nymtech/nym/pull/5659
[#5655]: https://github.com/nymtech/nym/pull/5655
[#5436]: https://github.com/nymtech/nym/pull/5436
## [2025.7-tex] (2025-04-14)
- Expand /v3/nym-nodes with geodata ([#5686])
- chore: clippy for 1.86 ([#5685])
- Featrure: Bash scripts to init and configure VMs conveniently and update docs ([#5681])
- Update node versions in CI ([#5677])
- build(deps): bump the patch-updates group across 1 directory with 8 updates ([#5668])
- Update log crate ([#5667])
- Minor fixes involving key cloning and hashing ([#5664])
- mix throughput tester ([#5661])
- build(deps): bump blake3 from 1.6.1 to 1.7.0 ([#5658])
- build(deps): bump elliptic from 6.5.5 to 6.6.1 ([#5483])
- Move all workflows on ubuntu-20 to ubuntu-22 ([#5455])
[#5686]: https://github.com/nymtech/nym/pull/5686
[#5685]: https://github.com/nymtech/nym/pull/5685
[#5681]: https://github.com/nymtech/nym/pull/5681
[#5677]: https://github.com/nymtech/nym/pull/5677
[#5668]: https://github.com/nymtech/nym/pull/5668
[#5667]: https://github.com/nymtech/nym/pull/5667
[#5664]: https://github.com/nymtech/nym/pull/5664
[#5661]: https://github.com/nymtech/nym/pull/5661
[#5658]: https://github.com/nymtech/nym/pull/5658
[#5483]: https://github.com/nymtech/nym/pull/5483
[#5455]: https://github.com/nymtech/nym/pull/5455
## [2025.6-chuckles] (2025-03-31)
- Remove Google public DNS ([#5660])
Generated
+1087 -993
View File
File diff suppressed because it is too large Load Diff
+15 -22
View File
@@ -123,7 +123,6 @@ members = [
"service-providers/ip-packet-router",
"service-providers/network-requester",
"tools/echo-server",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/mixnet-connectivity-check",
@@ -161,12 +160,7 @@ default-members = [
"tools/nymvisor",
]
exclude = [
"explorer",
"contracts",
"nym-wallet",
"cpu-cycles",
]
exclude = ["explorer", "contracts", "nym-wallet", "cpu-cycles"]
[workspace.package]
authors = ["Nym Technologies SA"]
@@ -185,7 +179,7 @@ aes = "0.8.1"
aes-gcm = "0.10.1"
aes-gcm-siv = "0.11.1"
ammonia = "4"
anyhow = "1.0.97"
anyhow = "1.0.98"
arc-swap = "1.7.1"
argon2 = "0.5.0"
async-trait = "0.1.88"
@@ -209,9 +203,9 @@ celes = "2.6.0"
cfg-if = "1.0.0"
chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.40"
chrono = "0.4.41"
cipher = "0.4.3"
clap = "4.5.34"
clap = "4.5.38"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
@@ -236,12 +230,12 @@ dotenvy = "0.15.6"
ecdsa = "0.16"
ed25519-dalek = "2.1"
encoding_rs = "0.8.35"
env_logger = "0.11.7"
env_logger = "0.11.8"
envy = "0.4"
etherparse = "0.13.0"
eyre = "0.6.9"
fastrand = "2.1.1"
flate2 = "1.1.0"
flate2 = "1.1.1"
futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
@@ -251,7 +245,7 @@ handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.4"
hickory-resolver = "0.25"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
@@ -286,7 +280,6 @@ pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.1"
pin-project-lite = "0.2.16"
pretty_env_logger = "0.4.0"
publicsuffix = "2.3.0"
quote = "1"
rand = "0.8.5"
@@ -310,7 +303,7 @@ serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
sha2 = "0.10.9"
si-scale = "0.2.3"
sphinx-packet = "=0.6.0"
sqlx = "0.7.4"
@@ -330,8 +323,8 @@ tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
tokio-tungstenite = { version = "0.20.1" }
tokio-util = "0.7.14"
toml = "0.8.20"
tokio-util = "0.7.15"
toml = "0.8.22"
tower = "0.5.2"
tower-http = "0.5.2"
tracing = "0.1.41"
@@ -342,7 +335,7 @@ tracing-tree = "0.2.2"
tracing-indicatif = "0.3.9"
ts-rs = "10.1.0"
tungstenite = { version = "0.20.1", default-features = false }
uniffi = "0.29.1"
uniffi = "0.29.2"
uniffi_build = "0.29.0"
url = "2.5"
utoipa = "5.2"
@@ -355,7 +348,7 @@ wasm-bindgen-test = "0.3.49"
x25519-dalek = "2.0.0"
zeroize = "1.7.0"
prometheus = { version = "0.13.0" }
prometheus = { version = "0.14.0" }
# coconut/DKG related
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
@@ -385,15 +378,15 @@ bip32 = { version = "0.5.3", default-features = false }
cosmrs = { version = "0.21.1" }
tendermint = "0.40.0"
tendermint-rpc = "0.40.0"
tendermint = "0.40.4"
tendermint-rpc = "0.40.4"
prost = { version = "0.13", default-features = false }
# wasm-related dependencies
gloo-utils = "0.2.0"
gloo-net = "0.6.0"
indexed_db_futures = "0.6.1"
indexed_db_futures = "0.6.4"
js-sys = "0.3.76"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.53"
version = "1.1.55"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -46,6 +46,7 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
nym-bin-common = { path = "../../common/bin-common", features = [
"output_format",
"clap",
"basic_tracing",
] }
nym-client-core = { path = "../../common/client-core", features = [
"fs-credentials-storage",
+1
View File
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_13;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
mod persistence;
mod template;
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::persistence::ClientPaths;
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
use crate::client::config::{default_config_filepath, Socket, SocketType};
use crate::error::ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
@@ -14,6 +14,8 @@ use std::io;
use std::net::{IpAddr, Ipv4Addr};
use std::path::Path;
use super::old_config_v1_1_54::ConfigV1_1_54;
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
pub struct ClientPathsV1_1_33 {
#[serde(flatten)]
@@ -33,6 +35,21 @@ pub struct ConfigV1_1_33 {
pub logging: LoggingSettings,
}
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
type Error = ClientError;
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
Ok(ConfigV1_1_54 {
base: value.base.into(),
socket: value.socket.into(),
storage_paths: ClientPaths {
common_paths: value.storage_paths.common_paths.upgrade_default()?,
},
logging: value.logging,
})
}
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
@@ -41,17 +58,6 @@ impl ConfigV1_1_33 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, ClientError> {
Ok(Config {
base: self.base.into(),
socket: self.socket.into(),
storage_paths: ClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
@@ -0,0 +1,41 @@
use std::{io, path::Path};
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseConfigV1_1_54;
use nym_config::read_config_from_toml_file;
use serde::{Deserialize, Serialize};
use crate::error::ClientError;
use super::{default_config_filepath, persistence::ClientPaths, Config, Socket};
#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)]
pub struct ConfigV1_1_54 {
#[serde(flatten)]
pub base: BaseConfigV1_1_54,
pub socket: Socket,
pub storage_paths: ClientPaths,
pub logging: LoggingSettings,
}
impl ConfigV1_1_54 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, ClientError> {
Ok(Config {
base: self.base.into(),
socket: self.socket,
storage_paths: self.storage_paths,
logging: self.logging,
})
}
}
@@ -92,10 +92,6 @@ host = '{{ socket.host }}'
[debug]
[debug.traffic]
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
[debug.acknowledgements]
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
+28 -4
View File
@@ -5,6 +5,7 @@ use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
use crate::client::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::client::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::client::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::client::config::old_config_v1_1_54::ConfigV1_1_54;
use crate::client::config::{BaseClientConfig, Config};
use crate::commands::ecash::Ecash;
use crate::error::ClientError;
@@ -177,7 +178,8 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
let old_paths = updated_step3.storage_paths.clone();
let updated = updated_step3.try_upgrade()?;
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
let updated = updated_step4.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -205,7 +207,8 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
let updated_step1: ConfigV1_1_20_2 = old_config.into();
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
let old_paths = updated_step2.storage_paths.clone();
let updated = updated_step2.try_upgrade()?;
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
let updated = updated_step3.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -229,7 +232,8 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
let (updated_step1, gateway_config) = old_config.upgrade()?;
let old_paths = updated_step1.storage_paths.clone();
let updated = updated_step1.try_upgrade()?;
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
let updated = updated_step2.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -252,7 +256,8 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
info!("It is going to get updated to the current specification.");
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
let updated = updated_step1.try_upgrade()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
@@ -265,6 +270,22 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, ClientError> {
Ok(true)
}
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, ClientError> {
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.54 config template.");
info!("It is going to get updated to the current specification.");
let updated = old_config.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_13_config(id).await? {
return Ok(());
@@ -278,6 +299,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), ClientError> {
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_54_config(id).await? {
return Ok(());
}
Ok(())
}
+2 -2
View File
@@ -4,7 +4,7 @@
use std::error::Error;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
use nym_network_defaults::setup_env;
pub mod client;
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
setup_logging();
setup_tracing_logger();
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.53"
version = "1.1.55"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -27,6 +27,7 @@ zeroize = { workspace = true }
nym-bin-common = { path = "../../common/bin-common", features = [
"output_format",
"clap",
"basic_tracing",
] }
nym-client-core = { path = "../../common/client-core", features = [
"fs-credentials-storage",
+36 -10
View File
@@ -7,6 +7,7 @@ use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_20_2::ConfigV1_1_20_2;
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
use crate::config::old_config_v1_1_54::ConfigV1_1_54;
use crate::config::{BaseClientConfig, Config};
use crate::error::Socks5ClientError;
use clap::CommandFactory;
@@ -204,15 +205,16 @@ async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = updated_step3.storage_paths.clone();
let updated_step4: ConfigV1_1_33 = updated_step3.into();
let updated = updated_step4.try_upgrade()?;
let updated_step5: ConfigV1_1_54 = updated_step4.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step5.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step5.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -234,15 +236,16 @@ async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = updated_step2.storage_paths.clone();
let updated_step3: ConfigV1_1_33 = updated_step2.into();
let updated = updated_step3.try_upgrade()?;
let updated_step4: ConfigV1_1_54 = updated_step3.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step4.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step4.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -261,15 +264,17 @@ async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientErro
let old_paths = updated_step1.storage_paths.clone();
let updated_step2: ConfigV1_1_33 = updated_step1.into();
let updated = updated_step2.try_upgrade()?;
let updated_step3: ConfigV1_1_54 = updated_step2.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step3.storage_paths.common_paths,
Some(gateway_config),
)
.await?;
let updated = updated_step3.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -287,15 +292,16 @@ async fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = old_config.storage_paths.clone();
let updated_step1: ConfigV1_1_33 = old_config.into();
let updated = updated_step1.try_upgrade()?;
let updated_step2: ConfigV1_1_54 = updated_step1.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step2.storage_paths.common_paths,
None,
)
.await?;
let updated = updated_step2.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -312,15 +318,32 @@ async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool, Socks5ClientError>
let old_paths = old_config.storage_paths.clone();
let updated = old_config.try_upgrade()?;
let updated_step1: ConfigV1_1_54 = old_config.try_into()?;
v1_1_33::migrate_gateway_details(
&old_paths.common_paths,
&updated.storage_paths.common_paths,
&updated_step1.storage_paths.common_paths,
None,
)
.await?;
let updated = updated_step1.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
async fn try_upgrade_v1_1_54_config(id: &str) -> Result<bool, Socks5ClientError> {
// explicitly load it as v1.1.54 (which is incompatible with the current one, i.e. +1.1.55)
let Ok(old_config) = ConfigV1_1_54::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the client is using <= v1.1.54 config template.");
info!("It is going to get updated to the current specification.");
let updated = old_config.try_upgrade()?;
updated.save_to_default_location()?;
Ok(true)
}
@@ -341,6 +364,9 @@ async fn try_upgrade_config(id: &str) -> Result<(), Socks5ClientError> {
if try_upgrade_v1_1_33_config(id).await? {
return Ok(());
}
if try_upgrade_v1_1_54_config(id).await? {
return Ok(());
}
Ok(())
}
+1
View File
@@ -25,6 +25,7 @@ pub mod old_config_v1_1_20;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
mod persistence;
mod template;
+17 -11
View File
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::{default_config_filepath, Config, SocksClientPaths};
use crate::config::{default_config_filepath, SocksClientPaths};
use crate::error::Socks5ClientError;
use nym_bin_common::logging::LoggingSettings;
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
@@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize};
use std::io;
use std::path::Path;
use super::old_config_v1_1_54::ConfigV1_1_54;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SocksClientPathsV1_1_33 {
#[serde(flatten)]
@@ -28,6 +30,20 @@ pub struct ConfigV1_1_33 {
pub logging: LoggingSettings,
}
impl TryFrom<ConfigV1_1_33> for ConfigV1_1_54 {
type Error = Socks5ClientError;
fn try_from(value: ConfigV1_1_33) -> Result<Self, Self::Error> {
Ok(ConfigV1_1_54 {
core: value.core.into(),
storage_paths: SocksClientPaths {
common_paths: value.storage_paths.common_paths.upgrade_default()?,
},
logging: value.logging,
})
}
}
impl ConfigV1_1_33 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
@@ -36,14 +52,4 @@ impl ConfigV1_1_33 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
Ok(Config {
core: self.core.into(),
storage_paths: SocksClientPaths {
common_paths: self.storage_paths.common_paths.upgrade_default()?,
},
logging: self.logging,
})
}
}
@@ -0,0 +1,39 @@
use std::{io, path::Path};
use nym_bin_common::logging::LoggingSettings;
use nym_config::read_config_from_toml_file;
use nym_socks5_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as CoreConfigV1_1_54;
use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::error::Socks5ClientError;
use super::{default_config_filepath, SocksClientPaths};
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_54 {
pub core: CoreConfigV1_1_54,
pub storage_paths: SocksClientPaths,
pub logging: LoggingSettings,
}
impl ConfigV1_1_54 {
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
read_config_from_toml_file(path)
}
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
Self::read_from_toml_file(default_config_filepath(id))
}
pub fn try_upgrade(self) -> Result<Config, Socks5ClientError> {
Ok(Config {
core: self.core.into(),
storage_paths: self.storage_paths,
logging: self.logging,
})
}
}
-4
View File
@@ -98,10 +98,6 @@ send_anonymously = {{ core.socks5.send_anonymously }}
[core.debug]
[core.debug.traffic]
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
[core.debug.acknowledgements]
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
+2 -2
View File
@@ -4,7 +4,7 @@
use std::error::Error;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::{maybe_print_banner, setup_logging};
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
use nym_network_defaults::setup_env;
mod commands;
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
setup_logging();
setup_tracing_logger();
if let Err(err) = commands::execute(args).await {
log::error!("{err}");
-1
View File
@@ -13,7 +13,6 @@ clap_complete = { workspace = true, optional = true }
clap_complete_fig = { workspace = true, optional = true }
const-str = { workspace = true }
log = { workspace = true }
pretty_env_logger = { workspace = true }
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
-23
View File
@@ -21,29 +21,6 @@ pub struct LoggingSettings {
// well, we need to implement something here at some point...
}
// I'd argue we should start transitioning from `log` to `tracing`
pub fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
log_builder.parse_filters(&s);
} else {
// default to 'Info'
log_builder.filter(None, log::LevelFilter::Info);
}
log_builder
.filter_module("hyper", log::LevelFilter::Warn)
.filter_module("tokio_reactor", log::LevelFilter::Warn)
.filter_module("reqwest", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.init();
}
// don't call init so that we could attach additional layers
#[cfg(feature = "basic_tracing")]
pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
+1
View File
@@ -27,6 +27,7 @@ thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
tokio = { workspace = true, features = ["macros"] }
time = { workspace = true }
tracing = { workspace = true }
zeroize = { workspace = true }
# internal
@@ -19,6 +19,7 @@ nym-pemstore = { path = "../../pemstore", optional = true }
# those are pulling so many deps T.T
nym-sphinx-params = { path = "../../nymsphinx/params" }
nym-sphinx-addressing = { path = "../../nymsphinx/addressing" }
nym-statistics-common = { path = "../../statistics" }
[features]
+67 -3
View File
@@ -5,6 +5,7 @@ use nym_config::defaults::NymNetworkDetails;
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
@@ -22,7 +23,7 @@ const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(50);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
@@ -375,14 +376,12 @@ pub struct Traffic {
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
@@ -414,6 +413,12 @@ pub struct Traffic {
pub use_legacy_sphinx_format: bool,
pub packet_type: PacketType,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
pub disable_mix_hops: bool,
}
impl Traffic {
@@ -444,6 +449,7 @@ impl Default for Traffic {
// we should use the legacy format until sufficient number of nodes understand the
// improved encoding
use_legacy_sphinx_format: true,
disable_mix_hops: false,
}
}
}
@@ -711,6 +717,9 @@ pub struct DebugConfig {
/// Defines all configuration options related to the forget me flag.
pub forget_me: ForgetMe,
/// Defines all configuration options related to the remember me flag.
pub remember_me: RememberMe,
}
impl DebugConfig {
@@ -734,6 +743,7 @@ impl Default for DebugConfig {
reply_surbs: Default::default(),
stats_reporting: Default::default(),
forget_me: Default::default(),
remember_me: Default::default(),
}
}
}
@@ -799,3 +809,57 @@ impl ForgetMe {
}
}
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct RememberMe {
/// Signal that this client should be accounted for in the stats
stats: bool,
/// Type of the session to remember, if it should be remembered
session_type: SessionType,
}
impl RememberMe {
pub fn new_vpn() -> Self {
Self {
stats: true,
session_type: SessionType::Vpn,
}
}
pub fn new_mixnet() -> Self {
Self {
stats: true,
session_type: SessionType::Mixnet,
}
}
pub fn new_native() -> Self {
Self {
stats: true,
session_type: SessionType::Native,
}
}
pub fn new(stats: bool, session_type: SessionType) -> Self {
Self {
stats,
session_type,
}
}
pub fn new_none() -> Self {
Self {
stats: false,
session_type: SessionType::Unknown,
}
}
pub fn session_type(&self) -> SessionType {
self.session_type
}
pub fn stats(&self) -> bool {
self.stats
}
}
@@ -6,6 +6,7 @@ pub mod v2;
pub mod v3;
pub mod v4;
pub mod v5;
pub mod v6;
// aliases for backwards compatibility
pub use v1 as old_config_v1_1_13;
@@ -13,3 +14,4 @@ pub use v2 as old_config_v1_1_20;
pub use v3 as old_config_v1_1_20_2;
pub use v4 as old_config_v1_1_30;
pub use v5 as old_config_v1_1_33;
pub use v6 as old_config_v1_1_54;
+12 -14
View File
@@ -1,16 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, GatewayConnection, ReplySurbs,
Topology, Traffic,
};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
use super::v6::*;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
@@ -87,18 +85,18 @@ pub struct ConfigV5 {
pub debug: DebugConfigV5,
}
impl From<ConfigV5> for Config {
impl From<ConfigV5> for ConfigV6 {
fn from(value: ConfigV5) -> Self {
Config {
client: Client {
ConfigV6 {
client: ClientV6 {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
debug: DebugConfigV6 {
traffic: TrafficV6 {
average_packet_delay: value.debug.traffic.average_packet_delay,
message_sending_average_delay: value
.debug
@@ -113,7 +111,7 @@ impl From<ConfigV5> for Config {
packet_type: value.debug.traffic.packet_type,
..Default::default()
},
cover_traffic: CoverTraffic {
cover_traffic: CoverTrafficV6 {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
@@ -127,18 +125,18 @@ impl From<ConfigV5> for Config {
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_connection: GatewayConnectionV6 {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
acknowledgements: AcknowledgementsV6 {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology: TopologyV6 {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
@@ -148,7 +146,7 @@ impl From<ConfigV5> for Config {
.max_startup_gateway_waiting_period,
..Default::default()
},
reply_surbs: ReplySurbs {
reply_surbs: ReplySurbsV6 {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
@@ -0,0 +1,623 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, ForgetMe, GatewayConnection,
RememberMe, ReplySurbs, StatsReporting, Topology, Traffic,
};
use nym_config::serde_helpers::{de_maybe_stringified, ser_maybe_stringified};
use nym_sphinx_addressing::Recipient;
use nym_sphinx_params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use url::Url;
// 'DEBUG'
const DEFAULT_ACK_WAIT_MULTIPLIER: f64 = 1.5;
const DEFAULT_ACK_WAIT_ADDITION: Duration = Duration::from_millis(1_500);
const DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(200);
const DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY: Duration = Duration::from_millis(20);
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // every 5min
const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000);
// the same values as our current (10.06.24) blacklist
const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
const DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD: Duration = Duration::from_secs(70 * 60); // 70min -> full epoch (1h) + a bit of overhead
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
// bandwidth bridging protocol, we can come back to a smaller timeout value
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
const DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO: f64 = 0.70;
// reply-surbs related:
// define when to request
// clients/client-core/src/client/replies/reply_storage/surb_storage.rs
const DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD: usize = 200;
const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
// define how much to request at once
// clients/client-core/src/client/replies/reply_controller.rs
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
const DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD: Duration = Duration::from_secs(10);
const DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD: Duration = Duration::from_secs(5 * 60);
// 12 hours
const DEFAULT_MAXIMUM_REPLY_SURB_AGE: Duration = Duration::from_secs(12 * 60 * 60);
// 24 hours
const DEFAULT_MAXIMUM_REPLY_KEY_AGE: Duration = Duration::from_secs(24 * 60 * 60);
// stats reporting related
/// Time interval between reporting statistics to the given provider if it exists
const STATS_REPORT_INTERVAL_SECS: Duration = Duration::from_secs(300);
// aliases for backwards compatibility
pub type ConfigV1_1_54 = ConfigV6;
pub type ClientV1_1_54 = ClientV6;
pub type DebugConfigV1_1_54 = DebugConfigV6;
pub type TrafficV1_1_54 = TrafficV6;
pub type CoverTrafficV1_1_54 = CoverTrafficV6;
pub type GatewayConnectionV1_1_54 = GatewayConnectionV6;
pub type AcknowledgementsV1_1_54 = AcknowledgementsV6;
pub type TopologyV1_1_54 = TopologyV6;
pub type ReplySurbsV1_1_54 = ReplySurbsV6;
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV6 {
pub client: ClientV6,
#[serde(default)]
pub debug: DebugConfigV6,
}
impl From<ConfigV6> for Config {
fn from(value: ConfigV6) -> Self {
Config {
client: Client {
version: value.client.version,
id: value.client.id,
disabled_credentials_mode: value.client.disabled_credentials_mode,
nyxd_urls: value.client.nyxd_urls,
nym_api_urls: value.client.nym_api_urls,
},
debug: DebugConfig {
traffic: Traffic {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: value
.debug
.traffic
.message_sending_average_delay,
disable_main_poisson_packet_distribution: value
.debug
.traffic
.disable_main_poisson_packet_distribution,
primary_packet_size: value.debug.traffic.primary_packet_size,
secondary_packet_size: value.debug.traffic.secondary_packet_size,
packet_type: value.debug.traffic.packet_type,
deterministic_route_selection: value
.debug
.traffic
.deterministic_route_selection,
maximum_number_of_retransmissions: value
.debug
.traffic
.maximum_number_of_retransmissions,
use_legacy_sphinx_format: value.debug.traffic.use_legacy_sphinx_format,
disable_mix_hops: value.debug.traffic.disable_mix_hops,
},
cover_traffic: CoverTraffic {
loop_cover_traffic_average_delay: value
.debug
.cover_traffic
.loop_cover_traffic_average_delay,
cover_traffic_primary_size_ratio: value
.debug
.cover_traffic
.cover_traffic_primary_size_ratio,
disable_loop_cover_traffic_stream: value
.debug
.cover_traffic
.disable_loop_cover_traffic_stream,
},
gateway_connection: GatewayConnection {
gateway_response_timeout: value
.debug
.gateway_connection
.gateway_response_timeout,
},
acknowledgements: Acknowledgements {
average_ack_delay: value.debug.acknowledgements.average_ack_delay,
ack_wait_multiplier: value.debug.acknowledgements.ack_wait_multiplier,
ack_wait_addition: value.debug.acknowledgements.ack_wait_addition,
},
topology: Topology {
topology_refresh_rate: value.debug.topology.topology_refresh_rate,
topology_resolution_timeout: value.debug.topology.topology_resolution_timeout,
disable_refreshing: value.debug.topology.disable_refreshing,
max_startup_gateway_waiting_period: value
.debug
.topology
.max_startup_gateway_waiting_period,
minimum_mixnode_performance: value.debug.topology.minimum_mixnode_performance,
minimum_gateway_performance: value.debug.topology.minimum_gateway_performance,
use_extended_topology: value.debug.topology.use_extended_topology,
ignore_egress_epoch_role: value.debug.topology.ignore_egress_epoch_role,
ignore_ingress_epoch_role: value.debug.topology.ignore_ingress_epoch_role,
},
reply_surbs: ReplySurbs {
minimum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.minimum_reply_surb_storage_threshold,
maximum_reply_surb_storage_threshold: value
.debug
.reply_surbs
.maximum_reply_surb_storage_threshold,
minimum_reply_surb_request_size: value
.debug
.reply_surbs
.minimum_reply_surb_request_size,
maximum_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_reply_surb_request_size,
maximum_allowed_reply_surb_request_size: value
.debug
.reply_surbs
.maximum_allowed_reply_surb_request_size,
maximum_reply_surb_rerequest_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_rerequest_waiting_period,
maximum_reply_surb_drop_waiting_period: value
.debug
.reply_surbs
.maximum_reply_surb_drop_waiting_period,
maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age,
maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age,
surb_mix_hops: value.debug.reply_surbs.surb_mix_hops,
minimum_reply_surb_threshold_buffer: value
.debug
.reply_surbs
.minimum_reply_surb_threshold_buffer,
fresh_sender_tags: value.debug.reply_surbs.fresh_sender_tags,
},
stats_reporting: StatsReporting {
enabled: value.debug.stats_reporting.enabled,
provider_address: value.debug.stats_reporting.provider_address,
reporting_interval: value.debug.stats_reporting.reporting_interval,
},
forget_me: ForgetMe {
client: value.debug.forget_me.client,
stats: value.debug.forget_me.stats,
},
remember_me: RememberMe {
stats: value.debug.remember_me.stats,
session_type: value.debug.remember_me.session_type.into(),
},
},
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
// note: the deny_unknown_fields is VITAL here to allow upgrades from v1.1.20_2
#[serde(deny_unknown_fields)]
pub struct ClientV6 {
/// Version of the client for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular client.
pub id: String,
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
// TODO: this should be moved to `debug.gateway_connection`
#[serde(default)]
pub disabled_credentials_mode: bool,
/// Addresses to nyxd validators via which the client can communicate with the chain.
#[serde(alias = "validator_urls")]
pub nyxd_urls: Vec<Url>,
/// Addresses to APIs running on validator from which the client gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TrafficV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent packet is going to be delayed at any given mix node.
/// So for a packet going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_packet_delay: Duration,
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take another 'real traffic stream' message to be sent.
/// If no real packets are available and cover traffic is enabled,
/// a loop cover message is sent instead in order to preserve the rate.
#[serde(with = "humantime_serde")]
pub message_sending_average_delay: Duration,
/// Controls whether the main packet stream constantly produces packets according to the predefined
/// poisson distribution.
pub disable_main_poisson_packet_distribution: bool,
/// Specify whether route selection should be determined by the packet header.
pub deterministic_route_selection: bool,
/// Specify how many times particular packet can be retransmitted
/// None - no limit
pub maximum_number_of_retransmissions: Option<u32>,
/// Specifies the packet size used for sent messages.
/// Do not override it unless you understand the consequences of that change.
pub primary_packet_size: PacketSize,
/// Specifies the optional auxiliary packet size for optimizing message streams.
/// Note that its use decreases overall anonymity.
/// Do not set it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
/// Specify whether any constructed sphinx packets should use the legacy format,
/// where the payload keys are explicitly attached rather than using the seeds
/// this affects any forward packets, acks and reply surbs
/// this flag should remain disabled until sufficient number of nodes on the network has upgraded
/// and support updated format.
/// in the case of reply surbs, the recipient must also understand the new encoding
pub use_legacy_sphinx_format: bool,
pub packet_type: PacketType,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
pub disable_mix_hops: bool,
}
impl Default for TrafficV6 {
fn default() -> Self {
TrafficV6 {
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
disable_main_poisson_packet_distribution: false,
deterministic_route_selection: false,
maximum_number_of_retransmissions: None,
primary_packet_size: PacketSize::RegularPacket,
secondary_packet_size: None,
packet_type: PacketType::Mix,
// we should use the legacy format until sufficient number of nodes understand the
// improved encoding
use_legacy_sphinx_format: true,
disable_mix_hops: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct CoverTrafficV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// it is going to take for another loop cover traffic message to be sent.
#[serde(with = "humantime_serde")]
pub loop_cover_traffic_average_delay: Duration,
/// Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
/// Only applicable if `secondary_packet_size` is enabled.
pub cover_traffic_primary_size_ratio: f64,
/// Controls whether the dedicated loop cover traffic stream should be enabled.
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
pub disable_loop_cover_traffic_stream: bool,
}
impl Default for CoverTrafficV6 {
fn default() -> Self {
CoverTrafficV6 {
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
cover_traffic_primary_size_ratio: DEFAULT_COVER_TRAFFIC_PRIMARY_SIZE_RATIO,
disable_loop_cover_traffic_stream: false,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct GatewayConnectionV6 {
/// How long we're willing to wait for a response to a message sent to the gateway,
/// before giving up on it.
#[serde(with = "humantime_serde")]
pub gateway_response_timeout: Duration,
}
impl Default for GatewayConnectionV6 {
fn default() -> Self {
GatewayConnectionV6 {
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct AcknowledgementsV6 {
/// The parameter of Poisson distribution determining how long, on average,
/// sent acknowledgement is going to be delayed at any given mix node.
/// So for an ack going through three mix nodes, on average, it will take three times this value
/// until the packet reaches its destination.
#[serde(with = "humantime_serde")]
pub average_ack_delay: Duration,
/// Value multiplied with the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 1.
pub ack_wait_multiplier: f64,
/// Value added to the expected round trip time of an acknowledgement packet before
/// it is assumed it was lost and retransmission of the data packet happens.
/// In an ideal network with 0 latency, this value would have been 0.
#[serde(with = "humantime_serde")]
pub ack_wait_addition: Duration,
}
impl Default for AcknowledgementsV6 {
fn default() -> Self {
AcknowledgementsV6 {
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct TopologyV6 {
/// The uniform delay every which clients are querying the directory server
/// to try to obtain a compatible network topology to send sphinx packets through.
#[serde(with = "humantime_serde")]
pub topology_refresh_rate: Duration,
/// During topology refresh, test packets are sent through every single possible network
/// path. This timeout determines waiting period until it is decided that the packet
/// did not reach its destination.
#[serde(with = "humantime_serde")]
pub topology_resolution_timeout: Duration,
/// Specifies whether the client should not refresh the network topology after obtaining
/// the first valid instance.
/// Supersedes `topology_refresh_rate_ms`.
pub disable_refreshing: bool,
/// Defines how long the client is going to wait on startup for its gateway to come online,
/// before abandoning the procedure.
#[serde(with = "humantime_serde")]
pub max_startup_gateway_waiting_period: Duration,
/// Specifies a minimum performance of a mixnode that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_mixnode_performance: u8,
/// Specifies a minimum performance of a gateway that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_gateway_performance: u8,
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
/// Specifies whether this client should ignore the current epoch role of the ingress node
/// when attempting to establish new connection
pub ignore_ingress_epoch_role: bool,
}
impl Default for TopologyV6 {
fn default() -> Self {
TopologyV6 {
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
disable_refreshing: false,
max_startup_gateway_waiting_period: DEFAULT_MAX_STARTUP_GATEWAY_WAITING_PERIOD,
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
ignore_egress_epoch_role: true,
ignore_ingress_epoch_role: true,
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ReplySurbsV6 {
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
/// It can only allow to go below that value if its to request additional reply surbs.
pub minimum_reply_surb_storage_threshold: usize,
/// Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
pub maximum_reply_surb_storage_threshold: usize,
/// Defines the soft threshold ontop of the minimum reply surb storage threshold for when the client
/// should proactively request additional reply surbs.
pub minimum_reply_surb_threshold_buffer: usize,
/// Defines the minimum number of reply surbs the client would request.
pub minimum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs the client would request.
pub maximum_reply_surb_request_size: u32,
/// Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
pub maximum_allowed_reply_surb_request_size: u32,
/// Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
/// for more even though in theory they wouldn't need to.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_rerequest_waiting_period: Duration,
/// Defines maximum amount of time the client is going to wait for reply surbs before
/// deciding it's never going to get them and would drop all pending messages
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_drop_waiting_period: Duration,
/// Defines maximum amount of time given reply surb is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_surb_age: Duration,
/// Defines maximum amount of time given reply key is going to be valid for.
/// This is going to be superseded by key rotation once implemented.
#[serde(with = "humantime_serde")]
pub maximum_reply_key_age: Duration,
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl Default for ReplySurbsV6 {
fn default() -> Self {
ReplySurbsV6 {
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
minimum_reply_surb_threshold_buffer: DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER,
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
maximum_reply_surb_rerequest_waiting_period:
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
fresh_sender_tags: false,
}
}
}
#[derive(Debug, Default, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct DebugConfigV6 {
/// Defines all configuration options related to traffic streams.
pub traffic: TrafficV6,
/// Defines all configuration options related to cover traffic stream(s).
pub cover_traffic: CoverTrafficV6,
/// Defines all configuration options related to the gateway connection.
pub gateway_connection: GatewayConnectionV6,
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
pub acknowledgements: AcknowledgementsV6,
/// Defines all configuration options related topology, such as refresh rates or timeouts.
pub topology: TopologyV6,
/// Defines all configuration options related to reply SURBs.
pub reply_surbs: ReplySurbsV6,
/// Defines all configuration options related to stats reporting.
pub stats_reporting: StatsReportingV6,
/// Defines all configuration options related to the forget me flag.
pub forget_me: ForgetMeV6,
/// Defines all configuration options related to the remember me flag.
pub remember_me: RememberMeV6,
}
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct StatsReportingV6 {
/// Is stats reporting enabled
pub enabled: bool,
/// Address of the stats collector. If this is none, no reporting will happen, regardless of `enabled`
#[serde(
serialize_with = "ser_maybe_stringified",
deserialize_with = "de_maybe_stringified"
)]
pub provider_address: Option<Recipient>,
/// With what frequence will statistics be sent
#[serde(with = "humantime_serde")]
pub reporting_interval: Duration,
}
impl Default for StatsReportingV6 {
fn default() -> Self {
StatsReportingV6 {
enabled: true,
provider_address: None,
reporting_interval: STATS_REPORT_INTERVAL_SECS,
}
}
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct ForgetMeV6 {
client: bool,
stats: bool,
}
#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize, Copy)]
pub struct RememberMeV6 {
/// Signal that this client should be accounted for in the stats
stats: bool,
/// Type of the session to remember, if it should be remembered
session_type: SessionTypeV6,
}
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize, Default, Debug)]
pub enum SessionTypeV6 {
Vpn,
Mixnet,
Wasm,
Native,
Socks5,
#[default]
Unknown,
}
impl From<SessionTypeV6> for SessionType {
fn from(value: SessionTypeV6) -> Self {
match value {
SessionTypeV6::Vpn => Self::Vpn,
SessionTypeV6::Mixnet => Self::Mixnet,
SessionTypeV6::Wasm => Self::Wasm,
SessionTypeV6::Native => Self::Native,
SessionTypeV6::Socks5 => Self::Socks5,
SessionTypeV6::Unknown => Self::Unknown,
}
}
}
@@ -36,7 +36,7 @@ use crate::{config, spawn_future};
use futures::channel::mpsc;
use log::*;
use nym_bandwidth_controller::BandwidthController;
use nym_client_core_config_types::ForgetMe;
use nym_client_core_config_types::{ForgetMe, RememberMe};
use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore};
use nym_credential_storage::storage::Storage as CredentialStorage;
use nym_crypto::asymmetric::{ed25519, x25519};
@@ -238,6 +238,12 @@ where
self
}
#[must_use]
pub fn with_remember_me(mut self, remember_me: &RememberMe) -> Self {
self.config.debug.remember_me = *remember_me;
self
}
#[must_use]
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
self.setup_method = setup;
@@ -930,6 +936,7 @@ where
task_handle: shutdown,
client_request_sender,
forget_me: self.config.debug.forget_me,
remember_me: self.config.debug.remember_me,
})
}
}
@@ -944,4 +951,5 @@ pub struct BaseClient {
pub client_request_sender: ClientRequestSender,
pub task_handle: TaskHandle,
pub forget_me: ForgetMe,
pub remember_me: RememberMe,
}
@@ -36,6 +36,9 @@ pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError>;
/// Check if the websocket connection to the gateway is alive
fn is_connection_alive(&self) -> bool;
}
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
@@ -90,6 +93,11 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
log::debug!("Sent client request: {:?}", message);
Ok(())
}
#[inline]
fn is_connection_alive(&self) -> bool {
(**self).is_connection_alive()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -147,6 +155,10 @@ where
) -> Result<(), GatewayClientError> {
self.gateway_client.send_client_request(message).await
}
fn is_connection_alive(&self) -> bool {
self.gateway_client.is_connection_alive()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -234,6 +246,11 @@ mod nonwasm_sealed {
) -> Result<(), GatewayClientError> {
Ok(())
}
fn is_connection_alive(&self) -> bool {
// LocalGateway is always "connected" since it's in-process
true
}
}
#[async_trait]
@@ -316,4 +333,9 @@ impl GatewayTransceiver for MockGateway {
) -> Result<(), GatewayClientError> {
Ok(())
}
fn is_connection_alive(&self) -> bool {
// MockGateway is always "connected" for testing purposes
true
}
}
@@ -9,7 +9,6 @@ use crate::client::real_messages_control::{AckActionSender, Action};
use crate::client::replies::reply_controller::MaxRetransmissions;
use crate::client::replies::reply_storage::{ReceivedReplySurbsMap, SentReplyKeys, UsedSenderTags};
use crate::client::topology_control::{TopologyAccessor, TopologyReadPermit};
use log::{debug, error, info, trace, warn};
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessage, ReplyMessage};
@@ -27,6 +26,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use tracing::{debug, error, info, trace, warn};
// TODO: move that error elsewhere since it seems to be contaminating different files
#[derive(Debug, Error)]
@@ -98,6 +98,12 @@ pub(crate) struct Config {
/// Specify whether route selection should be determined by the packet header.
deterministic_route_selection: bool,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
disable_mix_hops: bool,
/// Average delay a data packet is going to get delay at a single mixnode.
average_packet_delay: Duration,
@@ -133,6 +139,7 @@ impl Config {
primary_packet_size: PacketSize::default(),
secondary_packet_size: None,
use_legacy_sphinx_format: use_legacy_reply_surb_format,
disable_mix_hops: false,
}
}
@@ -147,6 +154,12 @@ impl Config {
self.secondary_packet_size = packet_size;
self
}
/// Configure whether messages senders using this config should use mix hops or not when sending messages.
pub fn disable_mix_hops(mut self, disable_mix_hops: bool) -> Self {
self.disable_mix_hops = disable_mix_hops;
self
}
}
#[derive(Clone)]
@@ -193,6 +206,7 @@ where
config.average_packet_delay,
config.average_ack_delay,
config.use_legacy_sphinx_format,
config.disable_mix_hops,
);
MessageHandler {
config,
@@ -284,7 +298,7 @@ where
) -> Result<(), SurbWrappedPreparationError> {
let msg = NymMessage::new_reply(message);
let packet_size = self.optimal_packet_size(&msg);
debug!("Using {packet_size} packets for {msg}");
trace!("Using {packet_size} packets for {msg}");
let mut fragment = self
.message_preparer
@@ -348,7 +362,7 @@ where
pub(crate) fn split_reply_message(&mut self, message: Vec<u8>) -> Vec<Fragment> {
let msg = NymMessage::new_reply(ReplyMessage::new_data_message(message));
let packet_size = self.optimal_packet_size(&msg);
debug!("Using {packet_size} packets for {msg}");
trace!("Using {packet_size} packets for {msg}");
self.message_preparer
.pad_and_split_message(msg, packet_size)
@@ -481,7 +495,7 @@ where
} else {
self.optimal_packet_size(&message)
};
debug!("Using {packet_size} packets for {message}");
trace!("Using {packet_size} packets for {message}");
let fragments = self
.message_preparer
.pad_and_split_message(message, packet_size);
@@ -103,6 +103,7 @@ impl<'a> From<&'a Config> for message_handler::Config {
)
.with_custom_primary_packet_size(cfg.traffic.primary_packet_size)
.with_custom_secondary_packet_size(cfg.traffic.secondary_packet_size)
.disable_mix_hops(cfg.traffic.disable_mix_hops)
}
}
@@ -157,6 +157,12 @@ impl TopologyRefresher {
let mut interval =
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
// We already have an initial topology, so no need to refresh it immediately.
// My understanding is that js setInterval does not fire immediately, so it's not
// needed there.
#[cfg(not(target_arch = "wasm32"))]
interval.next().await;
while !self.task_client.is_shutdown() {
tokio::select! {
_ = interval.next() => {
@@ -70,6 +70,10 @@ impl NymApiTopologyProvider {
}
}
pub fn disable_bincode(&mut self) {
self.validator_client.use_bincode = false;
}
fn use_next_nym_api(&mut self) {
if self.nym_api_urls.len() == 1 {
warn!("There's only a single nym API available - it won't be possible to use a different one");
@@ -82,20 +86,13 @@ impl NymApiTopologyProvider {
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let rewarded_set = self
.validator_client
.get_current_rewarded_set()
.await
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
.ok()?;
let rewarded_set_fut = self.validator_client.get_current_rewarded_set();
let mut topology = NymTopology::new_empty(rewarded_set);
let topology = if self.config.use_extended_topology {
let all_nodes_fut = self.validator_client.get_all_basic_nodes();
if self.config.use_extended_topology {
let all_nodes = self
.validator_client
.get_all_basic_nodes()
.await
// Join rewarded_set_fut and all_nodes_fut concurrently
let (rewarded_set, all_nodes) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
.inspect_err(|err| error!("failed to get network nodes: {err}"))
.ok()?;
@@ -103,26 +100,28 @@ impl NymApiTopologyProvider {
"there are {} nodes on the network (before filtering)",
all_nodes.len()
);
let mut topology = NymTopology::new_empty(rewarded_set);
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
n.performance.round_to_integer() >= self.config.min_node_performance()
}));
topology
} else {
// if we're not using extended topology, we're only getting active set mixnodes and gateways
let mixnodes = self
let mixnodes_fut = self
.validator_client
.get_all_basic_active_mixing_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network mixnodes: {err}"))
.ok()?;
.get_all_basic_active_mixing_assigned_nodes();
// TODO: we really should be getting ACTIVE gateways only
let gateways = self
.validator_client
.get_all_basic_entry_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network gateways: {err}"))
.ok()?;
let gateways_fut = self.validator_client.get_all_basic_entry_assigned_nodes();
let (rewarded_set, mixnodes, gateways) =
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
.inspect_err(|err| {
error!("failed to get network nodes: {err}");
})
.ok()?;
debug!(
"there are {} mixnodes and {} gateways in total (before performance filtering)",
@@ -130,12 +129,15 @@ impl NymApiTopologyProvider {
gateways.len()
);
let mut topology = NymTopology::new_empty(rewarded_set);
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_mixnode_performance
}));
topology.add_additional_nodes(gateways.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_gateway_performance
}));
topology
};
if !topology.is_minimally_routable() {
+1 -1
View File
@@ -4,6 +4,6 @@
pub use nym_client_core_config_types::disk_persistence;
pub use nym_client_core_config_types::old::{
old_config_v1_1_13, old_config_v1_1_20, old_config_v1_1_20_2, old_config_v1_1_30,
old_config_v1_1_33,
old_config_v1_1_33, old_config_v1_1_54,
};
pub use nym_client_core_config_types::*;
+3
View File
@@ -166,6 +166,9 @@ pub enum ClientCoreError {
#[error("there are no gateways supporting the wss protocol available")]
NoWssGateways,
#[error("there are no gateways with compatible protocol versions available")]
NoGatewaysWithCompatibleProtocol,
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
UnsupportedWssProtocol { gateway: String },
+184
View File
@@ -7,6 +7,7 @@ use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_crypto::asymmetric::ed25519;
use nym_gateway_client::GatewayClient;
use nym_gateway_requests::{ClientControlRequest, ServerResponse, CURRENT_PROTOCOL_VERSION};
use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::UserAgent;
@@ -131,6 +132,63 @@ pub async fn gateways_for_init<R: Rng>(
Ok(valid_gateways)
}
pub async fn gateways_for_init_with_protocol_validation<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
ignore_epoch_roles: bool,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
// First get the initial list of gateways
let gateways = gateways_for_init(
rng,
nym_apis,
user_agent,
minimum_performance,
ignore_epoch_roles,
)
.await?;
info!(
"Checking protocol compatibility for {} gateways...",
gateways.len()
);
// Filter out gateways with invalid protocols concurrently
let validated_gateways = Arc::new(tokio::sync::Mutex::new(Vec::new()));
futures::stream::iter(&gateways)
.for_each_concurrent(CONCURRENT_GATEWAYS_MEASURED, |gateway| async {
let id = gateway.identity();
trace!("validating protocol compatibility with {id}...");
match validate_gateway_protocol(gateway).await {
Ok(()) => {
debug!("{id}: protocol check successful");
validated_gateways.lock().await.push(gateway.clone());
}
Err(err) => {
warn!("failed to check protocol for {id}: {err}");
}
}
})
.await;
let validated_gateways = validated_gateways.lock().await;
info!(
"Protocol check complete: {}/{} gateways responded successfully",
validated_gateways.len(),
gateways.len()
);
if validated_gateways.is_empty() {
return Err(ClientCoreError::NoGatewaysWithCompatibleProtocol);
}
Ok(validated_gateways.clone())
}
#[cfg(not(target_arch = "wasm32"))]
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
@@ -210,6 +268,132 @@ where
Ok(GatewayWithLatency::new(gateway, avg))
}
async fn validate_gateway_protocol<G>(gateway: &G) -> Result<(), ClientCoreError>
where
G: ConnectableGateway,
{
let Some(addr) = gateway.clients_address(false) else {
return Err(ClientCoreError::UnsupportedEntry {
id: gateway.node_id(),
identity: gateway.identity().to_string(),
});
};
trace!(
"validating protocol compatibility with {} ({addr})...",
gateway.identity(),
);
let mut stream = connect(&addr).await?;
// Send protocol version request
let protocol_request = ClientControlRequest::SupportedProtocol {};
// Send the request as JSON text message
stream.send(Message::from(protocol_request)).await?;
// Wait for response with timeout
let protocol_timeout = Duration::from_millis(2000);
let response_future = stream.next();
match tokio::time::timeout(protocol_timeout, response_future).await {
Err(_) => {
warn!("Gateway {} protocol check timed out", gateway.identity());
Err(ClientCoreError::GatewayConnectionTimeout)
}
Ok(Some(Ok(Message::Text(response_text)))) => {
// Try to deserialize the response
let response = ServerResponse::try_from(response_text).map_err(|_| {
ClientCoreError::GatewayClientError {
gateway_id: gateway.identity().to_base58_string(),
source: *Box::new(
nym_gateway_client::error::GatewayClientError::MalformedResponse,
),
}
})?;
match response {
ServerResponse::SupportedProtocol { version } => {
debug!(
"Gateway {} supports protocol version {}, ours: {}",
gateway.identity(),
version,
CURRENT_PROTOCOL_VERSION
);
// Check protocol compatibility
if version > CURRENT_PROTOCOL_VERSION {
warn!(
"Gateway {} uses newer protocol version {} (client supports {}). \
Gateway should gracefully degrade, but consider updating your client.",
gateway.identity(),
version,
CURRENT_PROTOCOL_VERSION
);
}
trace!(
"Gateway {} protocol validation successful (gateway: v{}, client: v{})",
gateway.identity(),
version,
CURRENT_PROTOCOL_VERSION
);
Ok(())
}
ServerResponse::Error { message } => {
warn!(
"Gateway {} returned error during protocol check: {}",
gateway.identity(),
message
);
Err(ClientCoreError::GatewayClientError {
gateway_id: gateway.identity().to_base58_string(),
source: *Box::new(
nym_gateway_client::error::GatewayClientError::GatewayError(message),
),
})
}
_ => {
warn!(
"Gateway {} returned unexpected response during protocol check",
gateway.identity()
);
Err(ClientCoreError::GatewayClientError {
gateway_id: gateway.identity().to_base58_string(),
source: *Box::new(
nym_gateway_client::error::GatewayClientError::UnexpectedResponse {
name: response.name().to_string(),
},
),
})
}
}
}
Ok(Some(Ok(_))) => {
warn!(
"Gateway {} sent non-text response during protocol check",
gateway.identity()
);
Err(ClientCoreError::GatewayConnectionAbruptlyClosed)
}
Ok(Some(Err(e))) => {
warn!(
"WebSocket error during protocol check with {}: {}",
gateway.identity(),
e
);
Err(e.into())
}
Ok(None) => {
warn!(
"Gateway {} closed connection during protocol check",
gateway.identity()
);
Err(ClientCoreError::GatewayConnectionAbruptlyClosed)
}
}
}
pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
rng: &mut R,
gateways: &[G],
@@ -27,7 +27,6 @@ nym-credential-storage = { path = "../../credential-storage" }
nym-credentials-interface = { path = "../../credentials-interface" }
nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../gateway-requests" }
nym-http-api-client = { path = "../../http-api-client" }
nym-network-defaults = { path = "../../network-defaults" }
nym-sphinx = { path = "../../nymsphinx" }
nym-statistics-common = { path = "../../statistics" }
@@ -165,6 +165,24 @@ impl<C, St> GatewayClient<C, St> {
self.bandwidth.remaining()
}
pub fn is_connection_established(&self) -> bool {
self.connection.is_established()
}
/// Check if the websocket connection is actually alive at the socket level
pub fn is_connection_alive(&self) -> bool {
// First check if we have an established connection
if !self.connection.is_established() {
return false;
}
// Get the file descriptor and check if the socket is alive
match self.ws_fd() {
Some(fd) => socket_is_alive(fd),
None => false,
}
}
#[cfg(not(target_arch = "wasm32"))]
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
@@ -1128,3 +1146,49 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
}
}
}
/// Check if a socket file descriptor is alive and responsive
///
/// This function performs socket-level checks to determine if the connection is actually alive.
/// It's cross-platform compatible and works on both Unix and non-Unix systems.
fn socket_is_alive(fd: RawFd) -> bool {
#[cfg(unix)]
{
use std::io::ErrorKind;
use std::net::TcpStream;
use std::os::unix::io::FromRawFd;
unsafe {
// Create a TcpStream from the raw fd to perform socket operations
let stream = TcpStream::from_raw_fd(fd);
// Try to peek at the socket to see if it's still connected
// We peek with a zero-length buffer to avoid consuming data
let mut buf = [0u8; 0];
let result = match stream.peek(&mut buf) {
Ok(_) => true, // Socket is alive and readable
Err(e) => match e.kind() {
ErrorKind::WouldBlock => true, // Socket is alive but no data available
ErrorKind::ConnectionReset
| ErrorKind::ConnectionAborted
| ErrorKind::BrokenPipe
| ErrorKind::NotConnected => false, // Socket is clearly dead
_ => true, // Other errors might be temporary, assume alive
},
};
// Prevent the TcpStream from closing the fd when it's dropped
// since we don't own the fd
std::mem::forget(stream);
result
}
}
#[cfg(not(unix))]
{
// On non-Unix systems, we can't easily check socket state
// Fall back to assuming the connection is alive if we have an fd
fd != 0
}
}
@@ -1,6 +1,5 @@
use crate::error::GatewayClientError;
use nym_http_api_client::HickoryDnsResolver;
#[cfg(unix)]
use std::{
os::fd::{AsRawFd, RawFd},
@@ -20,7 +19,6 @@ pub(crate) async fn connect_async(
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
use tokio::net::TcpSocket;
let resolver = HickoryDnsResolver::default();
let uri =
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
@@ -29,18 +27,18 @@ pub(crate) async fn connect_async(
.host()
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
// Get address for tcp connection, using system DNS resolver
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
// Do a DNS lookup for the domain using system DNS resolver
tokio::net::lookup_host((domain, port))
.await
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: err.into(),
})?
.collect()
}
};
@@ -49,10 +49,6 @@ pub enum GatewayClientError {
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
@@ -10,7 +10,7 @@ use futures::channel::oneshot;
use futures::stream::{SplitSink, SplitStream};
use futures::{SinkExt, StreamExt};
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_gateway_requests::{ServerResponse, SimpleGatewayRequestsError};
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
use nym_task::TaskClient;
use si_scale::helpers::bibytes2;
use std::os::raw::c_int as RawFd;
@@ -188,6 +188,34 @@ impl PartiallyDelegatedRouter {
}
}
}
ServerResponse::EncryptedResponse { ciphertext, nonce } => {
match SensitiveServerResponse::decrypt(
&ciphertext,
&nonce,
self.shared_key.as_ref(),
) {
Ok(response) => match response {
SensitiveServerResponse::ForgetMeAck {} => {
info!("received forget me acknowledgement");
}
SensitiveServerResponse::RememberMeAck {} => {
info!("received remember me acknowledgement");
}
SensitiveServerResponse::KeyUpgradeAck {} => {
warn!(
"received illegal key upgrade acknowledgement in an authenticated client"
);
}
_ => {
warn!("received unknown SensitiveServerResponse");
}
},
Err(e) => {
error!("failed to handle encrypted response: {e}");
}
}
Ok(())
}
other => {
let name = other.name();
warn!("received illegal message of type '{name}' in an authenticated client");
@@ -345,25 +345,47 @@ impl<C, S> Client<C, S> {
#[derive(Clone)]
pub struct NymApiClient {
pub use_bincode: bool,
pub nym_api: nym_api::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
// we could re-implement the communication with the REST API on port 1317
}
impl From<nym_api::Client> for NymApiClient {
fn from(nym_api: nym_api::Client) -> Self {
NymApiClient {
use_bincode: false,
nym_api,
}
}
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient { nym_api }
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout));
NymApiClient { nym_api }
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[must_use]
pub fn with_bincode(mut self, use_bincode: bool) -> Self {
self.use_bincode = use_bincode;
self
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
@@ -373,7 +395,10 @@ impl NymApiClient {
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
NymApiClient { nym_api }
NymApiClient {
use_bincode: false,
nym_api,
}
}
pub fn api_url(&self) -> &Url {
@@ -410,7 +435,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_entry_assigned_nodes(false, Some(page), None)
.get_basic_entry_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -436,7 +461,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_active_mixing_assigned_nodes(false, Some(page), None)
.get_basic_active_mixing_assigned_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -462,7 +487,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_mixing_capable_nodes(false, Some(page), None)
.get_basic_mixing_capable_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -485,7 +510,7 @@ impl NymApiClient {
loop {
let mut res = self
.nym_api
.get_basic_nodes(false, Some(page), None)
.get_basic_nodes(false, Some(page), None, self.use_bincode)
.await?;
nodes.append(&mut res.nodes.data);
@@ -318,6 +318,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -333,7 +334,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -355,6 +360,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -370,7 +376,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -392,6 +402,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -407,7 +418,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -427,6 +442,7 @@ pub trait NymApiClientExt: ApiClient {
no_legacy: bool,
page: Option<u32>,
per_page: Option<u32>,
use_bincode: bool,
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
let mut params = Vec::new();
@@ -442,7 +458,11 @@ pub trait NymApiClientExt: ApiClient {
params.push(("per_page", per_page.to_string()))
}
self.get_json(
if use_bincode {
params.push(("output", "bincode".to_string()))
}
self.get_response(
&[
routes::API_VERSION,
"unstable",
@@ -73,6 +73,7 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
pub const SUBMIT_ROUTE: &str = "submit-route-monitoring-results";
pub const SERVICE_PROVIDERS: &str = "services";
@@ -98,11 +98,10 @@ impl SqliteEcashTicketbookManager {
pub(crate) async fn contains_ticketbook_data(&self, data: &[u8]) -> Result<bool, sqlx::Error> {
let exists = sqlx::query(
r#"
SELECT EXISTS (
SELECT 1
FROM ecash_ticketbook
WHERE ticketbook_data = ?
)
SELECT 1
FROM ecash_ticketbook
WHERE ticketbook_data = ?
"#,
)
.bind(data)
+1
View File
@@ -28,6 +28,7 @@ nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
nym-pemstore = { path = "../pemstore" }
nym-sphinx = { path = "../nymsphinx" }
nym-serde-helpers = { path = "../serde-helpers", features = ["base64"] }
nym-statistics-common = { path = "../statistics" }
nym-task = { path = "../task" }
nym-credentials = { path = "../credentials" }
@@ -11,6 +11,7 @@ use crate::{
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::ed25519;
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use tungstenite::Message;
@@ -29,6 +30,9 @@ pub enum ClientRequest {
client: bool,
stats: bool,
},
RememberMe {
session_type: SessionType,
},
}
impl ClientRequest {
@@ -12,6 +12,7 @@ use tungstenite::Message;
pub enum SensitiveServerResponse {
KeyUpgradeAck {},
ForgetMeAck {},
RememberMeAck {},
}
impl SensitiveServerResponse {
-1
View File
@@ -22,7 +22,6 @@ thiserror = { workspace = true }
tracing = { workspace = true }
nym-sphinx = { path = "../nymsphinx" }
nym-credentials-interface = { path = "../credentials-interface" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
nym-statistics-common = { path = "../statistics" }
@@ -0,0 +1,7 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
ALTER TABLE sessions_active
ADD COLUMN remember INTEGER NOT NULL default 0;
+12 -1
View File
@@ -3,8 +3,9 @@
use error::StatsStorageError;
use models::StoredFinishedSession;
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
use nym_sphinx::DestinationAddressBytes;
use nym_statistics_common::types::SessionType;
use sessions::SessionManager;
use sqlx::{
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
@@ -147,6 +148,16 @@ impl PersistentStatsStorage {
.await?)
}
pub async fn remember_active_session(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StatsStorageError> {
Ok(self
.session_manager
.remember_active_session(client_address.as_base58_string())
.await?)
}
pub async fn update_active_session_type(
&self,
client_address: DestinationAddressBytes,
+4 -18
View File
@@ -1,12 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
use nym_node_metrics::entry::{ActiveSession, FinishedSession};
use nym_statistics_common::types::SessionType;
use sqlx::prelude::FromRow;
use time::OffsetDateTime;
pub use nym_credentials_interface::TicketType;
#[derive(FromRow)]
pub struct StoredFinishedSession {
duration_ms: i64,
@@ -22,25 +21,11 @@ impl From<StoredFinishedSession> for FinishedSession {
}
}
pub trait ToSessionType {
fn to_session_type(&self) -> SessionType;
}
impl ToSessionType for TicketType {
fn to_session_type(&self) -> SessionType {
match self {
TicketType::V1MixnetEntry => SessionType::Mixnet,
TicketType::V1MixnetExit => SessionType::Mixnet,
TicketType::V1WireguardEntry => SessionType::Vpn,
TicketType::V1WireguardExit => SessionType::Vpn,
}
}
}
#[derive(FromRow)]
pub(crate) struct StoredActiveSession {
start_time: OffsetDateTime,
typ: String,
remember: u8,
}
impl From<StoredActiveSession> for ActiveSession {
@@ -48,6 +33,7 @@ impl From<StoredActiveSession> for ActiveSession {
ActiveSession {
start: value.start_time,
typ: SessionType::from_string(&value.typ),
remember: value.remember != 0,
}
}
}
+18 -6
View File
@@ -107,7 +107,7 @@ impl SessionManager {
typ: String,
) -> Result<()> {
sqlx::query!(
"INSERT INTO sessions_active (client_address, start_time, typ) VALUES (?, ?, ?)",
"INSERT INTO sessions_active (client_address, start_time, typ, remember) VALUES (?, ?, ?, 0)",
client_address_b58,
start_time,
typ
@@ -117,6 +117,16 @@ impl SessionManager {
Ok(())
}
pub(crate) async fn remember_active_session(&self, client_address_b58: String) -> Result<()> {
sqlx::query!(
"UPDATE sessions_active SET remember = 1 WHERE client_address = ?",
client_address_b58,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn update_active_session_type(
&self,
client_address_b58: String,
@@ -136,14 +146,16 @@ impl SessionManager {
&self,
client_address_b58: String,
) -> Result<Option<StoredActiveSession>> {
sqlx::query_as("SELECT start_time, typ FROM sessions_active WHERE client_address = ?")
.bind(client_address_b58)
.fetch_optional(&self.connection_pool)
.await
sqlx::query_as(
"SELECT start_time, typ, remember FROM sessions_active WHERE client_address = ?",
)
.bind(client_address_b58)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn get_all_active_sessions(&self) -> Result<Vec<StoredActiveSession>> {
sqlx::query_as("SELECT start_time, typ FROM sessions_active")
sqlx::query_as("SELECT start_time, typ, remember FROM sessions_active")
.fetch_all(&self.connection_pool)
.await
}
+5 -3
View File
@@ -12,7 +12,8 @@ license.workspace = true
[dependencies]
async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip"] }
bincode = { workspace = true }
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd"] }
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
@@ -26,11 +27,11 @@ bytes = { workspace = true }
encoding_rs = { workspace = true }
mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
@@ -39,3 +40,4 @@ features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
+22 -27
View File
@@ -35,10 +35,10 @@ use std::{
};
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::{ResolveError, ResolveErrorKind},
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ServerOrderingStrategy},
lookup_ip::{LookupIp, LookupIpIntoIter},
TokioAsyncResolver,
name_server::TokioConnectionProvider,
ResolveError, TokioResolver,
};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
@@ -92,8 +92,8 @@ pub struct HickoryDnsResolver {
// Since we might not have been called in the context of a
// Tokio Runtime in initialization, so we must delay the actual
// construction of the resolver.
state: Arc<OnceCell<TokioAsyncResolver>>,
fallback: Arc<OnceCell<TokioAsyncResolver>>,
state: Arc<OnceCell<TokioResolver>>,
fallback: Arc<OnceCell<TokioResolver>>,
dont_use_shared: bool,
}
@@ -118,11 +118,8 @@ impl Resolve for HickoryDnsResolver {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = fallback.get_or_try_init(|| {
// using a closure here is slightly gross, but this makes sure that if the
@@ -166,11 +163,8 @@ impl HickoryDnsResolver {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
match e.kind() {
ResolveErrorKind::NoRecordsFound { .. } => {}
_ => {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
if !e.is_no_records_found() {
warn!("primary DNS failed w/ error {e}: using system fallback");
}
let resolver = self
.fallback
@@ -190,7 +184,7 @@ impl HickoryDnsResolver {
}
}
fn new_resolver(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
fn new_resolver(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver()
} else {
@@ -198,7 +192,7 @@ impl HickoryDnsResolver {
}
}
fn new_resolver_system(&self) -> Result<TokioAsyncResolver, HickoryDnsError> {
fn new_resolver_system(&self) -> Result<TokioResolver, HickoryDnsError> {
if self.dont_use_shared {
new_resolver_system()
} else {
@@ -212,29 +206,30 @@ impl HickoryDnsResolver {
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
fn new_resolver() -> Result<TokioResolver, HickoryDnsError> {
let mut name_servers = NameServerConfigGroup::quad9_tls();
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
let mut resolver_builder =
TokioResolver::builder_with_config(config, TokioConnectionProvider::default());
let mut opts = ResolverOpts::default();
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
// Would like to enable this when 0.25 stabilizes
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
resolver_builder.options_mut().server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
Ok(TokioAsyncResolver::tokio(config, opts))
Ok(resolver_builder.build())
}
/// Create a new resolver with the default configuration, which reads from the system DNS config
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
/// addresses to work with "happy eyeballs" algorithm.
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(TokioAsyncResolver::tokio(config, opts))
fn new_resolver_system() -> Result<TokioResolver, HickoryDnsError> {
let mut resolver_builder = TokioResolver::builder_tokio()?;
resolver_builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(resolver_builder.build())
}
#[cfg(test)]
+131 -38
View File
@@ -136,19 +136,23 @@
//! ```
#![warn(missing_docs)]
pub use reqwest::{IntoUrl, StatusCode};
use async_trait::async_trait;
use reqwest::header::HeaderValue;
use reqwest::{RequestBuilder, Response, StatusCode};
use reqwest::{RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::{instrument, warn};
use tracing::{debug, instrument, warn};
use url::Url;
use bytes::Bytes;
use http::header::CONTENT_TYPE;
use http::HeaderMap;
pub use reqwest::IntoUrl;
use mime::Mime;
#[cfg(not(target_arch = "wasm32"))]
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
@@ -210,17 +214,36 @@ pub enum HttpClientError<E: Display = String> {
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[error("failed to decode response body: {source} from {content}")]
ResponseDecodeFailure {
source: serde_json::Error,
content: String,
},
#[error("failed to decode response body: {message} from {content}")]
ResponseDecodeFailure { message: String, content: String },
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
}
impl HttpClientError {
/// Returns true if the error is a timeout.
pub fn is_timeout(&self) -> bool {
match self {
HttpClientError::ReqwestClientError { source } => source.is_timeout(),
#[cfg(target_arch = "wasm32")]
HttpClientError::RequestTimeout => true,
_ => false,
}
}
/// Returns the HTTP status code if available.
pub fn status_code(&self) -> Option<StatusCode> {
match self {
HttpClientError::RequestFailure { status } => Some(*status),
HttpClientError::EmptyResponse { status } => Some(*status),
HttpClientError::EndpointFailure { status, .. } => Some(*status),
_ => None,
}
}
}
/// A `ClientBuilder` can be used to create a [`Client`] with custom configuration applied consistently
/// and state tracked across subsequent requests.
pub struct ClientBuilder {
@@ -265,14 +288,18 @@ impl ClientBuilder {
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
// Note: I believe the manual enable calls for the compression methods are extra
// as the various compression features for `reqwest` crate should be enabled
// just by including the feature which:
// `"Enable[s] auto decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
// I am going to leave these here anyways so that removing a decompression method
// from the features list will throw an error if it is not also removed here.
reqwest::ClientBuilder::new()
.gzip(true)
.deflate(true)
.brotli(true)
.zstd(true)
};
ClientBuilder {
@@ -519,6 +546,7 @@ pub trait ApiClientCore {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ApiClientCore for Client {
#[instrument(level = "debug", skip_all, fields(path=?path))]
fn create_request<B, K, V>(
&self,
method: reqwest::Method,
@@ -535,11 +563,6 @@ impl ApiClientCore for Client {
let mut request = self.reqwest_client.request(method.clone(), url);
// Indicate that compressed responses are preferred, but if not supported other encodings are fine.
// TODO: Down the road we can be more selective about adding this, but it's inclusion here guarantees
// that we use compression when available.
request = request.header(reqwest::header::ACCEPT_ENCODING, "gzip;q=1.0, *;q=0.5");
if let Some(body) = json_body {
request = request.json(body);
}
@@ -697,12 +720,30 @@ pub trait ApiClient: ApiClientCore {
/// 'get' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T`.
#[instrument(level = "debug", skip_all)]
#[instrument(level = "debug", skip_all, fields(path=?path))]
// TODO: deprecate in favour of get_response that works based on mime type in the response
async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.get_response(path, params).await
}
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
/// into the provided type `T` based on the content type header
async fn get_response<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
@@ -877,14 +918,10 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
url
}
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
use encoding_rs::{Encoding, UTF_8};
use mime::Mime;
let content_type = headers
.get(http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let content_type = try_get_mime_type(headers);
let encoding_name = content_type
.as_ref()
@@ -897,7 +934,7 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
text.into_owned()
}
/// Attempt to parse a json object from an HTTP response
/// Attempt to parse a response object from an HTTP response
#[instrument(level = "debug", skip_all)]
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where
@@ -919,16 +956,7 @@ where
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
// (and similarly does the same thing for text())
let full = res.bytes().await?;
match serde_json::from_slice(&full) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&full, headers);
Err(HttpClientError::ResponseDecodeFailure {
source: err,
content,
})
}
}
decode_raw_response(&headers, full)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
@@ -947,6 +975,71 @@ where
}
}
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
match serde_json::from_slice(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
use bincode::Options;
let opts = nym_http_api_common::make_bincode_serializer();
match opts.deserialize(&content) {
Ok(data) => Ok(data),
Err(err) => {
let content = decode_as_text(&content, headers);
Err(HttpClientError::ResponseDecodeFailure {
message: err.to_string(),
content,
})
}
}
}
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
// if content type header is missing, fallback to our old default, json
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
debug!("attempting to parse response as {mime}");
// unfortunately we can't use stronger typing for subtype as "bincode" is not a defined mime type
match (mime.type_(), mime.subtype().as_str()) {
(mime::APPLICATION, "json") => decode_as_json(headers, content),
(mime::APPLICATION, "bincode") => decode_as_bincode(headers, content),
(_, _) => {
debug!("unrecognised mime type {mime}. falling back to json decoding...");
decode_as_json(headers, content)
}
}
}
fn try_get_mime_type(headers: &HeaderMap) -> Option<Mime> {
headers
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok())
}
#[cfg(test)]
mod tests {
use super::*;
+35 -11
View File
@@ -11,20 +11,44 @@ license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum-client-ip.workspace = true
axum.workspace = true
bytes = { workspace = true }
colored.workspace = true
futures = { workspace = true }
mime = { workspace = true }
axum = { workspace = true, optional = true }
axum-client-ip = { workspace = true, optional = true }
bincode = { workspace = true }
bytes = { workspace = true, optional = true }
colored = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
mime = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
serde_yaml = { workspace = true }
subtle.workspace = true
tower = { workspace = true }
serde_yaml = { workspace = true, optional = true }
subtle = { workspace = true, optional = true }
time = { workspace = true, optional = true, features = ["macros"] }
tower = { workspace = true, optional = true }
tracing.workspace = true
utoipa = { workspace = true, optional = true }
zeroize = { workspace = true }
zeroize = { workspace = true, optional = true }
[features]
default = []
output = [
"axum",
"bytes",
"mime",
"serde_yaml",
"time",
"time/formatting"
]
middleware = [
"axum",
"axum-client-ip",
"colored",
"futures",
"subtle",
"tower",
"zeroize"
]
utoipa = ["dep:utoipa"]
[lints]
workspace = true
+13 -85
View File
@@ -1,92 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2023-2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use bytes::{BufMut, BytesMut};
use serde::{Deserialize, Serialize};
#[cfg(feature = "middleware")]
pub mod middleware;
#[derive(Debug, Clone)]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
}
#[cfg(feature = "output")]
pub mod response;
impl<T> IntoResponse for FormattedResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
}
}
}
// don't break existing imports
#[cfg(feature = "output")]
pub use response::*;
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum Output {
#[default]
Json,
Yaml,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml(data)),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct Yaml<T>(pub T);
impl<T> From<T> for Yaml<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0) {
Ok(()) => (
[(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
)],
buf.into_inner().freeze(),
)
.into_response(),
Err(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response(),
}
}
// be explicit about those values because bincode uses different defaults in different places
pub fn make_bincode_serializer() -> impl ::bincode::Options {
use ::bincode::Options;
::bincode::DefaultOptions::new()
.with_little_endian()
.with_varint_encoding()
}
@@ -9,13 +9,35 @@ use axum::response::IntoResponse;
use axum_client_ip::InsecureClientIp;
use colored::Colorize;
use std::time::Instant;
use tracing::info;
use tracing::{debug, info};
enum LogLevel {
Debug,
Info,
}
pub async fn log_request_info(
insecure_client_ip: InsecureClientIp,
request: Request,
next: Next,
) -> impl IntoResponse {
log_request(insecure_client_ip, request, next, LogLevel::Info).await
}
pub async fn log_request_debug(
insecure_client_ip: InsecureClientIp,
request: Request,
next: Next,
) -> impl IntoResponse {
log_request(insecure_client_ip, request, next, LogLevel::Debug).await
}
/// Simple logger for requests
pub async fn logger(
async fn log_request(
InsecureClientIp(addr): InsecureClientIp,
request: Request,
next: Next,
level: LogLevel,
) -> impl IntoResponse {
// TODO dz use `OriginalUri` extractor to get full URI even for nested
// routers if routes aren't logged correctly in handlers
@@ -58,7 +80,14 @@ pub async fn logger(
let agent_str = "agent".bold();
info!("[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}");
match level {
LogLevel::Debug => debug!(
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
),
LogLevel::Info => info!(
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
),
}
res
}
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Bincode<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Bincode<T> {
fn from(response: T) -> Self {
Bincode(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/bincode"),
))
}
}
impl<T> Bincode<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Bincode<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
use bincode::Options;
let mut buf = BytesMut::with_capacity(128).writer();
match crate::make_bincode_serializer().serialize_into(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
@@ -0,0 +1,49 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
use utoipa::gen::serde_json;
// don't use axum's Json directly as we need to be able to define custom headers
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Json<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Json<T> {
fn from(response: T) -> Self {
Json(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
))
}
}
impl<T> Json<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_json::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
+190
View File
@@ -0,0 +1,190 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::format_description::BorrowedFormatItem;
use time::macros::{format_description, offset};
use time::OffsetDateTime;
pub mod bincode;
pub mod json;
pub mod yaml;
pub use json::Json;
pub use yaml::Yaml;
pub use bincode::Bincode;
#[derive(Debug, Clone, Default)]
pub(crate) struct ResponseWrapper<T> {
data: T,
headers: HeaderMap,
}
impl<T> ResponseWrapper<T> {
pub(crate) fn new(response: T) -> ResponseWrapper<T> {
ResponseWrapper {
data: response,
headers: Default::default(),
}
}
#[must_use]
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.headers.insert(name, value.into());
self
}
}
#[derive(Debug, Clone)]
pub enum FormattedResponse<T> {
Json(Json<T>),
Yaml(Yaml<T>),
Bincode(Bincode<T>),
}
impl<T> FormattedResponse<T> {
pub fn into_inner(self) -> T {
match self {
FormattedResponse::Json(inner) => inner.0.data,
FormattedResponse::Yaml(inner) => inner.0.data,
FormattedResponse::Bincode(inner) => inner.0.data,
}
}
#[must_use]
pub fn with_header(
self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> FormattedResponse<T> {
match self {
FormattedResponse::Json(inner) => {
FormattedResponse::Json(inner.with_header(name, value))
}
FormattedResponse::Yaml(inner) => {
FormattedResponse::Yaml(inner.with_header(name, value))
}
FormattedResponse::Bincode(inner) => {
FormattedResponse::Bincode(inner.with_header(name, value))
}
}
}
/// Set the `expires` header on the response to the provided expiration.
/// Internally it will perform conversions to make sure the value is set in GMT offset,
/// e.g. `Expires: Wed, 21 Oct 2015 07:28:00 GMT`
#[must_use]
pub fn with_expires_header(self, expiration: OffsetDateTime) -> FormattedResponse<T> {
// as per RFC-7234 (section 5.3) EXPIRES header has to use value formatted
// as defined in RFC-7231 (section 7.1.1.1)
// (preferred format (IMF-fixdate) uses RFC-5322 (section 3.3)
let formatted = format_rfc5352(expiration);
// SAFETY: our formatted datetime doesn't contain forbidden characters
#[allow(clippy::unwrap_used)]
self.with_header(header::EXPIRES, HeaderValue::try_from(formatted).unwrap())
}
/// Work similarly to `with_expires_header`, but rather than setting explicit expiration value,
/// it adds the provided time delta to the current time instead.
#[must_use]
pub fn with_expires_header_delta(self, expires_in: Duration) -> FormattedResponse<T> {
self.with_expires_header(OffsetDateTime::now_utc() + expires_in)
}
}
impl<T> IntoResponse for FormattedResponse<T>
where
T: Serialize,
{
fn into_response(self) -> Response {
match self {
FormattedResponse::Json(json_response) => json_response.into_response(),
FormattedResponse::Yaml(yaml_response) => yaml_response.into_response(),
FormattedResponse::Bincode(bincode_response) => bincode_response.into_response(),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum Output {
#[default]
Json,
Yaml,
Bincode,
}
#[derive(Default, Debug, Serialize, Deserialize, Copy, Clone)]
#[cfg_attr(feature = "utoipa", derive(utoipa::IntoParams, utoipa::ToSchema))]
#[serde(default)]
pub struct OutputParams {
pub output: Option<Output>,
}
impl Output {
pub fn to_response<T: Serialize>(self, data: T) -> FormattedResponse<T> {
match self {
Output::Json => FormattedResponse::Json(Json::from(data)),
Output::Yaml => FormattedResponse::Yaml(Yaml::from(data)),
Output::Bincode => FormattedResponse::Bincode(Bincode::from(data)),
}
}
}
pub(crate) fn error_response<E: ToString>(err: E) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
[(
header::CONTENT_TYPE,
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
)],
err.to_string(),
)
.into_response()
}
// SAFETY: this hardcoded datetime formatter is valid
#[allow(clippy::unwrap_used)]
fn format_rfc5352(datetime: OffsetDateTime) -> String {
// the time must be using GMT (UTC) offset
let normalised = datetime.to_offset(offset!(UTC));
normalised.format(&rfc5322()).unwrap()
}
// NOTE: this function is purposely not made public as it cannot guarantee caller
// has correctly ensured their date is using correct GMT offset
fn rfc5322() -> &'static [BorrowedFormatItem<'static>] {
// D, d M Y H:i:s T
format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
)
}
#[cfg(test)]
mod tests {
use crate::response::format_rfc5352;
use time::macros::datetime;
#[test]
fn rfc5322_formatting() {
let utc_date = datetime!(2021-08-23 12:13:14 UTC);
let non_utc_date = datetime!(2021-08-23 12:13:14 -1);
assert_eq!("Mon, 23 Aug 2021 12:13:14 GMT", format_rfc5352(utc_date));
assert_eq!(
"Mon, 23 Aug 2021 13:13:14 GMT",
format_rfc5352(non_utc_date)
);
}
}
@@ -0,0 +1,47 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::response::{error_response, ResponseWrapper};
use axum::http::header::IntoHeaderName;
use axum::http::{header, HeaderValue};
use axum::response::{IntoResponse, Response};
use bytes::{BufMut, BytesMut};
use serde::Serialize;
#[derive(Debug, Clone, Default)]
#[must_use]
pub struct Yaml<T>(pub(crate) ResponseWrapper<T>);
impl<T> From<T> for Yaml<T> {
fn from(response: T) -> Self {
Yaml(ResponseWrapper::new(response).with_header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/yaml"),
))
}
}
impl<T> Yaml<T> {
pub(crate) fn with_header(
mut self,
name: impl IntoHeaderName,
value: impl Into<HeaderValue>,
) -> Self {
self.0.headers.insert(name, value.into());
self
}
}
impl<T> IntoResponse for Yaml<T>
where
T: Serialize,
{
// replicates axum's Json
fn into_response(self) -> Response {
let mut buf = BytesMut::with_capacity(128).writer();
match serde_yaml::to_writer(&mut buf, &self.0.data) {
Ok(()) => (self.0.headers, buf.into_inner().freeze()).into_response(),
Err(err) => error_response(err),
}
}
}
@@ -179,7 +179,7 @@ fn _aggregate_indices_signatures<B>(
validate_shares: bool,
) -> Result<Vec<CoinIndexSignature>>
where
B: Borrow<PartialCoinIndexSignature>,
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
{
// Check if all indices are unique
if signatures_shares
@@ -271,7 +271,7 @@ pub fn aggregate_indices_signatures<B>(
signatures_shares: &[CoinIndexSignatureShare<B>],
) -> Result<Vec<CoinIndexSignature>>
where
B: Borrow<PartialCoinIndexSignature>,
B: Borrow<PartialCoinIndexSignature> + Send + Sync,
{
_aggregate_indices_signatures(params, vk, signatures_shares, true)
}
@@ -38,7 +38,7 @@ impl From<AnnotatedExpirationDateSignature> for ExpirationDateSignature {
pub struct ExpirationDateSignatureShare<B = PartialExpirationDateSignature>
where
B: Borrow<PartialExpirationDateSignature>,
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
pub index: SignerIndex,
pub key: VerificationKeyAuth,
@@ -205,7 +205,7 @@ fn _aggregate_expiration_signatures<B>(
validate_shares: bool,
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<ExpirationDateSignature>,
B: Borrow<ExpirationDateSignature> + Send + Sync,
{
// Check if all indices are unique
if signatures_shares
@@ -304,7 +304,7 @@ pub fn aggregate_expiration_signatures<B>(
signatures_shares: &[ExpirationDateSignatureShare<B>],
) -> Result<Vec<ExpirationDateSignature>>
where
B: Borrow<PartialExpirationDateSignature>,
B: Borrow<PartialExpirationDateSignature> + Send + Sync,
{
_aggregate_expiration_signatures(vk, expiration_date, signatures_shares, true)
}
+1 -1
View File
@@ -8,7 +8,7 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
log = { workspace = true }
tracing = { workspace = true }
rand = { workspace = true }
rand_distr = { workspace = true }
rand_chacha = { workspace = true }
@@ -47,11 +47,17 @@ impl SurbAck {
average_delay: time::Duration,
topology: &NymRouteProvider,
packet_type: PacketType,
disable_mix_hops: bool,
) -> Result<Self, NymTopologyError>
where
R: RngCore + CryptoRng,
{
let route = topology.random_route_to_egress(rng, recipient.gateway())?;
let route = if disable_mix_hops {
topology.empty_route_to_egress(recipient.gateway())?
} else {
topology.random_route_to_egress(rng, recipient.gateway())?
};
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
@@ -9,7 +9,6 @@ use nym_sphinx_addressing::nodes::{
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_types::constants::PAYLOAD_KEY_SEED_SIZE;
use nym_sphinx_types::{
NymPacket, SURBMaterial, SphinxError, HEADER_SIZE, NODE_ADDRESS_LENGTH, SURB,
X25519_WITH_EXPLICIT_PAYLOAD_KEYS_VERSION,
@@ -106,6 +105,7 @@ impl ReplySurb {
average_delay: Duration,
use_legacy_surb_format: bool,
topology: &NymRouteProvider,
_disable_mix_hops: bool, // TODO: support SURBs with no mix hops after changes to surb format / construction
) -> Result<Self, NymTopologyError>
where
R: RngCore + CryptoRng,
@@ -126,12 +126,6 @@ impl ReplySurb {
})
}
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization using the new encoding format.
/// Useful for deserialization from a bytes stream.
pub fn v2_serialised_len(num_hops: u8) -> usize {
Self::BASE_OVERHEAD + num_hops as usize * PAYLOAD_KEY_SEED_SIZE
}
pub fn encryption_key(&self) -> &SurbEncryptionKey {
&self.encryption_key
}
@@ -32,24 +32,28 @@ fn v2_reply_surbs_serialised_len(surbs: &[ReplySurb]) -> usize {
}
}
// when serialising surbs are always prepended with u16-encoded count an u8-encoded number of hops
3 + num_surbs * v2_reply_surb_serialised_len(num_hops)
// when serialising surbs are always prepended with:
// - u16-encoded count,
// - u8-encoded number of hops
// - u8 reserved value
4 + num_surbs * v2_reply_surb_serialised_len(num_hops)
}
// NUM_SURBS (u16) || HOPS (u8) || SURB_DATA
// NUM_SURBS (u16) || HOPS (u8) || RESERVED (u8) || SURB_DATA
fn recover_reply_surbs_v2(
bytes: &[u8],
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
if bytes.len() < 2 {
if bytes.len() < 4 {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
// we're not attaching more than 65k surbs...
let num_surbs = u16::from_be_bytes([bytes[0], bytes[1]]);
let num_hops = bytes[2];
let mut consumed = 3;
let _reserved = bytes[3];
let mut consumed = 4;
let surb_size = ReplySurb::v2_serialised_len(num_hops);
let surb_size = v2_reply_surb_serialised_len(num_hops);
if bytes[consumed..].len() < num_surbs as usize * surb_size {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
@@ -69,11 +73,13 @@ fn recover_reply_surbs_v2(
fn reply_surbs_bytes_v2(reply_surbs: &[ReplySurb]) -> impl Iterator<Item = u8> + use<'_> {
let num_surbs = reply_surbs.len() as u16;
let num_hops = reply_surbs_hops(reply_surbs);
let reserved = 0;
num_surbs
.to_be_bytes()
.into_iter()
.chain(once(num_hops))
.chain(once(reserved))
.chain(reply_surbs.iter().flat_map(|surb| surb.to_bytes()))
}
@@ -272,6 +272,10 @@ impl Fragment {
self.payload
}
pub fn payload(&self) -> &[u8] {
&self.payload
}
/// Tries to recover `Fragment` from slice of bytes extracted from received sphinx packet.
/// It can fail if payload would not fully fit in a single `Fragment` or some of the metadata
/// is malformed or self-contradictory, for example if current_fragment > total_fragments.
@@ -124,7 +124,7 @@ impl ReconstructionBuffer {
// TODO: what to do in that case? give up on the message? overwrite it? panic?
// it *might* be due to lock ack-packet, but let's keep the `warn` level in case
// it could be somehow exploited
warn!(
debug!(
"duplicate fragment received! - frag - {} (set id: {})",
fragment.current_fragment(),
fragment.id()
+1
View File
@@ -53,6 +53,7 @@ where
average_ack_delay,
topology,
packet_type,
false, // make sure mix hops are enabled
)?)
}
+2 -2
View File
@@ -216,7 +216,7 @@ impl NymMessage {
chunking::number_of_required_fragments(serialized_len, plaintext_per_packet);
// by chunking I mean that currently the fragments hold variable amount of plaintext in them (I wish I had time to rewrite it...)
log::trace!(
tracing::trace!(
"this message will use {serialized_len} bytes of PLAINTEXT (This does not account for Ack or chunking overhead). \
With {packet_size:?} PacketSize ({plaintext_per_packet} of usable plaintext available) it will require {num_fragments} packet(s).",
);
@@ -242,7 +242,7 @@ impl NymMessage {
let wasted_space_percentage =
(space_left as f32 / (bytes.len() + 1 + space_left) as f32) * 100.0;
log::trace!(
tracing::trace!(
"Padding {self_display}: {} of raw plaintext bytes are required. \
They're going to be put into {packets_used} sphinx packets with {space_left} bytes \
of leftover space. {wasted_space_percentage:.1}% of packet capacity is going to \
+31 -6
View File
@@ -3,7 +3,6 @@
use crate::message::{NymMessage, ACK_OVERHEAD, OUTFOX_ACK_OVERHEAD};
use crate::NymPayloadBuilder;
use log::debug;
use nym_crypto::asymmetric::x25519;
use nym_crypto::Digest;
use nym_sphinx_acknowledgements::surb_ack::SurbAck;
@@ -19,6 +18,7 @@ use nym_sphinx_types::{Delay, NymPacket};
use nym_topology::{NymRouteProvider, NymTopologyError};
use rand::{CryptoRng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use tracing::*;
use nym_sphinx_chunking::monitoring;
use std::time::Duration;
@@ -52,6 +52,10 @@ pub trait FragmentPreparer {
type Rng: CryptoRng + Rng;
fn use_legacy_sphinx_format(&self) -> bool;
fn mix_hops_disabled(&self) -> bool {
// Unless otherwise configured, mix hops are enabled
false
}
fn deterministic_route_selection(&self) -> bool;
fn rng(&mut self) -> &mut Self::Rng;
@@ -69,6 +73,7 @@ pub trait FragmentPreparer {
) -> Result<SurbAck, NymTopologyError> {
let ack_delay = self.average_ack_delay();
let use_legacy_sphinx_format = self.use_legacy_sphinx_format();
let disable_mix_hops = self.mix_hops_disabled();
SurbAck::construct(
self.rng(),
@@ -79,6 +84,7 @@ pub trait FragmentPreparer {
ack_delay,
topology,
packet_type,
disable_mix_hops,
)
}
@@ -101,6 +107,8 @@ pub trait FragmentPreparer {
packet_sender: &Recipient,
packet_type: PacketType,
) -> Result<PreparedFragment, NymTopologyError> {
debug!("Preparing reply chunk for sending");
// each reply attaches the digest of the encryption key so that the recipient could
// lookup correct key for decryption,
let reply_overhead = ReplySurbKeyDigestAlgorithm::output_size();
@@ -222,15 +230,17 @@ pub trait FragmentPreparer {
Err(_e) => return Err(NymTopologyError::PayloadBuilder),
};
// generate pseudorandom route for the packet
log::trace!("Preparing chunk for sending");
let route = if self.deterministic_route_selection() {
log::trace!("using deterministic route selection");
// generate pseudorandom route for the packet. Unless mix hops are disabled then build an empty route.
trace!("Preparing chunk for sending");
let route = if self.mix_hops_disabled() {
topology.empty_route_to_egress(destination)?
} else if self.deterministic_route_selection() {
trace!("using deterministic route selection");
let seed = fragment_header.seed().wrapping_mul(self.nonce());
let mut rng = ChaCha8Rng::seed_from_u64(seed as u64);
topology.random_route_to_egress(&mut rng, destination)?
} else {
log::trace!("using pseudorandom route selection");
trace!("using pseudorandom route selection");
let mut rng = self.rng();
topology.random_route_to_egress(&mut rng, destination)?
};
@@ -316,6 +326,12 @@ pub struct MessagePreparer<R> {
use_legacy_sphinx_format: bool,
nonce: i32,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
pub disable_mix_hops: bool,
}
impl<R> MessagePreparer<R>
@@ -329,6 +345,7 @@ where
average_packet_delay: Duration,
average_ack_delay: Duration,
use_legacy_sphinx_format: bool,
disable_mix_hops: bool,
) -> Self {
let mut rng = rng;
let nonce = rng.gen();
@@ -340,6 +357,7 @@ where
average_ack_delay,
use_legacy_sphinx_format,
nonce,
disable_mix_hops,
}
}
@@ -355,6 +373,8 @@ where
topology: &NymRouteProvider,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
let mut reply_surbs = Vec::with_capacity(amount);
let disabled_mix_hops = self.mix_hops_disabled();
for _ in 0..amount {
let reply_surb = ReplySurb::construct(
&mut self.rng,
@@ -362,6 +382,7 @@ where
self.average_packet_delay,
use_legacy_reply_surb_format,
topology,
disabled_mix_hops, // TODO: support SURBs with no mix hops after changes to surb format / construction
)?;
reply_surbs.push(reply_surb)
}
@@ -442,6 +463,10 @@ where
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
type Rng = R;
fn mix_hops_disabled(&self) -> bool {
self.disable_mix_hops
}
fn use_legacy_sphinx_format(&self) -> bool {
self.use_legacy_sphinx_format
}
@@ -13,6 +13,7 @@ use std::str::FromStr;
pub mod old_config_v1_1_20_2;
pub mod old_config_v1_1_30;
pub mod old_config_v1_1_33;
pub mod old_config_v1_1_54;
pub use nym_service_providers_common::interface::ProviderInterfaceVersion;
pub use nym_socks5_requests::Socks5ProtocolVersion;
@@ -1,7 +1,8 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{Config, Socks5, Socks5Debug};
use super::old_config_v1_1_54::ConfigV1_1_54;
use super::{Socks5, Socks5Debug};
pub use nym_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as BaseClientConfigV1_1_33;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
@@ -23,9 +24,9 @@ pub struct ConfigV1_1_33 {
pub socks5: Socks5V1_1_33,
}
impl From<ConfigV1_1_33> for Config {
impl From<ConfigV1_1_33> for ConfigV1_1_54 {
fn from(value: ConfigV1_1_33) -> Self {
Config {
ConfigV1_1_54 {
base: value.base.into(),
socks5: value.socks5.into(),
}
@@ -0,0 +1,23 @@
use super::Config;
pub use nym_client_core::config::old_config_v1_1_54::ConfigV1_1_54 as BaseClientConfigV1_1_54;
use serde::{Deserialize, Serialize};
use super::Socks5;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_54 {
#[serde(flatten)]
pub base: BaseClientConfigV1_1_54,
pub socks5: Socks5,
}
impl From<ConfigV1_1_54> for Config {
fn from(value: ConfigV1_1_54) -> Self {
Config {
base: value.base.into(),
socks5: value.socks5,
}
}
}
+1
View File
@@ -20,6 +20,7 @@ thiserror = { workspace = true }
time = { workspace = true }
tokio = { workspace = true }
si-scale = { workspace = true }
strum = { workspace = true }
nym-crypto = { path = "../crypto" }
nym-sphinx = { path = "../nymsphinx" }
+9 -19
View File
@@ -1,10 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::TicketType;
use nym_sphinx::DestinationAddressBytes;
use time::OffsetDateTime;
use crate::types::SessionType;
/// Channel for receiving incoming Stats events
pub type GatewayStatsReceiver = tokio::sync::mpsc::UnboundedReceiver<GatewayStatsEvent>;
@@ -51,15 +52,9 @@ pub enum GatewaySessionEvent {
/// Address of the remote client opening the connection
client: DestinationAddressBytes,
},
/// A new ecash ticket has been added / requested
EcashTicket {
/// Type of ecash ticket that has been created as part of the session
ticket_type: TicketType,
/// Address of the remote client opening the connection
client: DestinationAddressBytes,
},
SessionDelete {
/// Address of the remote client opening the connection
/// An active session should be given a type and remembered
SessionRemember {
session_type: SessionType,
client: DestinationAddressBytes,
},
}
@@ -81,18 +76,13 @@ impl GatewaySessionEvent {
}
}
/// A new ecash ticket has been added / requested
pub fn new_ecash_ticket(
pub fn new_session_remember(
session_type: SessionType,
client: DestinationAddressBytes,
ticket_type: TicketType,
) -> GatewaySessionEvent {
GatewaySessionEvent::EcashTicket {
ticket_type,
GatewaySessionEvent::SessionRemember {
session_type,
client,
}
}
pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent {
GatewaySessionEvent::SessionDelete { client }
}
}
+2
View File
@@ -22,6 +22,8 @@ pub mod error;
pub mod gateways;
/// Statistics reporting abstractions and implementations.
pub mod report;
/// Statistics related types.
pub mod types;
const CLIENT_ID_PREFIX: &str = "client_stats_id";
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
#[derive(
PartialEq,
Copy,
Clone,
strum::Display,
strum::EnumString,
Serialize,
Deserialize,
Default,
Debug,
)]
pub enum SessionType {
Vpn,
Mixnet,
Wasm,
Native,
Socks5,
#[default]
Unknown,
}
impl SessionType {
pub fn from_string<S: AsRef<str>>(s: S) -> Self {
s.as_ref().parse().unwrap_or_default()
}
}
+15
View File
@@ -176,6 +176,17 @@ impl NymRouteProvider {
.random_route_to_egress(rng, egress_identity, self.ignore_egress_epoch_roles)
}
/// Returns a route directly to the egress point, which can be any known node
pub fn empty_route_to_egress(
&self,
egress_identity: NodeIdentity,
) -> Result<Vec<SphinxNode>, NymTopologyError> {
let egress = self
.topology
.egress_node_by_identity(egress_identity, self.ignore_egress_epoch_roles)?;
Ok(vec![egress])
}
pub fn random_path_to_egress<R>(
&self,
rng: &mut R,
@@ -213,6 +224,10 @@ impl NymTopology {
serde_json::from_reader(file).map_err(Into::into)
}
pub fn node_details(&self) -> &HashMap<NodeId, RoutingNode> {
&self.node_details
}
pub fn add_skimmed_nodes(&mut self, nodes: &[SkimmedNode]) {
self.add_additional_nodes(nodes.iter())
}
+28 -7
View File
@@ -11,6 +11,27 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
nm
});
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
pub struct RouteResult {
pub layer1: u32,
pub layer2: u32,
pub layer3: u32,
pub gw: u32,
pub success: bool,
}
impl RouteResult {
pub fn new(layer1: u32, layer2: u32, layer3: u32, gw: u32, success: bool) -> Self {
RouteResult {
layer1,
layer2,
layer3,
gw,
success,
}
}
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, ToSchema)]
pub struct NodeResult {
#[schema(value_type = u32)]
@@ -29,23 +50,23 @@ impl NodeResult {
}
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
#[serde(untagged)]
pub enum MonitorResults {
Mixnode(Vec<NodeResult>),
Gateway(Vec<NodeResult>),
Node(Vec<NodeResult>),
Route(Vec<RouteResult>),
}
#[derive(Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct MonitorMessage {
results: Vec<NodeResult>,
results: MonitorResults,
signature: String,
signer: String,
timestamp: i64,
}
impl MonitorMessage {
fn message_to_sign(results: &[NodeResult], timestamp: i64) -> Vec<u8> {
fn message_to_sign(results: &MonitorResults, timestamp: i64) -> Vec<u8> {
let mut msg = serde_json::to_vec(results).unwrap_or_default();
msg.extend_from_slice(&timestamp.to_le_bytes());
msg
@@ -60,7 +81,7 @@ impl MonitorMessage {
now - self.timestamp < 5
}
pub fn new(results: Vec<NodeResult>, private_key: &PrivateKey) -> Self {
pub fn new(results: MonitorResults, private_key: &PrivateKey) -> Self {
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Time went backwards")
@@ -82,7 +103,7 @@ impl MonitorMessage {
NETWORK_MONITORS.contains(&self.signer)
}
pub fn results(&self) -> &[NodeResult] {
pub fn results(&self) -> &MonitorResults {
&self.results
}
+35 -1
View File
@@ -5,9 +5,10 @@
#![allow(clippy::drop_non_drop)]
use crate::error::WasmCoreError;
use nym_client_core::config::ForgetMe;
use nym_client_core::config::{ForgetMe, RememberMe};
use nym_config::helpers::OptionalSet;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_statistics_common::types::SessionType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use wasm_bindgen::prelude::*;
@@ -113,6 +114,8 @@ pub struct DebugWasm {
pub stats_reporting: StatsReportingWasm,
pub forget_me: ForgetMeWasm,
pub remember_me: RememberMeWasm,
}
impl Default for DebugWasm {
@@ -132,6 +135,7 @@ impl From<DebugWasm> for ConfigDebug {
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: debug.forget_me.into(),
remember_me: debug.remember_me.into(),
}
}
}
@@ -147,6 +151,7 @@ impl From<ConfigDebug> for DebugWasm {
reply_surbs: debug.reply_surbs.into(),
stats_reporting: debug.stats_reporting.into(),
forget_me: ForgetMeWasm::from(debug.forget_me),
remember_me: RememberMeWasm::from(debug.remember_me),
}
}
}
@@ -191,6 +196,12 @@ pub struct TrafficWasm {
/// Controls whether the sent packets should use outfox as opposed to the default sphinx.
pub use_outfox: bool,
/// Indicates whether to mix hops or not. If mix hops are enabled, traffic
/// will be routed as usual, to the entry gateway, through three mix nodes, egressing
/// through the exit gateway. If mix hops are disabled, traffic will be routed directly
/// from the entry gateway to the exit gateway, bypassing the mix nodes.
pub disable_mix_hops: bool,
}
impl Default for TrafficWasm {
@@ -224,6 +235,7 @@ impl From<TrafficWasm> for ConfigTraffic {
secondary_packet_size: use_extended_packet_size,
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
packet_type,
disable_mix_hops: traffic.disable_mix_hops,
}
}
}
@@ -241,6 +253,7 @@ impl From<ConfigTraffic> for TrafficWasm {
use_legacy_sphinx_format: traffic.use_legacy_sphinx_format,
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
use_outfox: traffic.packet_type == PacketType::Outfox,
disable_mix_hops: traffic.disable_mix_hops,
}
}
}
@@ -589,6 +602,27 @@ impl From<ForgetMe> for ForgetMeWasm {
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Serialize, Default)]
#[serde(deny_unknown_fields)]
pub struct RememberMeWasm {
pub stats: bool,
}
impl From<RememberMeWasm> for RememberMe {
fn from(value: RememberMeWasm) -> Self {
RememberMe::new(value.stats, SessionType::Wasm)
}
}
impl From<RememberMe> for RememberMeWasm {
fn from(value: RememberMe) -> Self {
Self {
stats: value.stats(),
}
}
}
#[wasm_bindgen(inspectable)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
+22 -1
View File
@@ -9,7 +9,7 @@
use super::{
AcknowledgementsWasm, CoverTrafficWasm, DebugWasm, ForgetMeWasm, GatewayConnectionWasm,
ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
RememberMeWasm, ReplySurbsWasm, StatsReportingWasm, TopologyWasm, TrafficWasm,
};
use crate::config::ConfigDebug;
use serde::{Deserialize, Serialize};
@@ -50,6 +50,9 @@ pub struct DebugWasmOverride {
#[tsify(optional)]
pub forget_me: Option<ForgetMeWasmOverride>,
#[tsify(optional)]
pub remember_me: Option<RememberMeWasmOverride>,
}
impl From<DebugWasmOverride> for DebugWasm {
@@ -63,6 +66,7 @@ impl From<DebugWasmOverride> for DebugWasm {
reply_surbs: value.reply_surbs.map(Into::into).unwrap_or_default(),
stats_reporting: value.stats_reporting.map(Into::into).unwrap_or_default(),
forget_me: value.forget_me.map(Into::into).unwrap_or_default(),
remember_me: value.remember_me.map(Into::into).unwrap_or_default(),
}
}
}
@@ -148,6 +152,7 @@ impl From<TrafficWasmOverride> for TrafficWasm {
.use_extended_packet_size
.unwrap_or(def.use_extended_packet_size),
use_outfox: value.use_outfox.unwrap_or(def.use_outfox),
disable_mix_hops: false, // not configured from js config override yet
}
}
}
@@ -456,6 +461,22 @@ impl From<ForgetMeWasmOverride> for ForgetMeWasm {
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
pub struct RememberMeWasmOverride {
#[tsify(optional)]
pub stats: Option<bool>,
}
impl From<RememberMeWasmOverride> for RememberMeWasm {
fn from(value: RememberMeWasmOverride) -> Self {
RememberMeWasm {
stats: value.stats.unwrap_or_default(),
}
}
}
#[derive(Tsify, Debug, Clone, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]

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