diff --git a/.github/workflows/ci-build-upload-binaries.yml b/.github/workflows/ci-build-upload-binaries.yml index 7df2cd4ad4..3986f1ddae 100644 --- a/.github/workflows/ci-build-upload-binaries.yml +++ b/.github/workflows/ci-build-upload-binaries.yml @@ -38,15 +38,14 @@ jobs: rm -rf ci-builds || true mkdir -p $OUTPUT_DIR echo $OUTPUT_DIR - - name: Install Dependencies (Linux) run: sudo apt-get update && sudo apt-get -y install libudev-dev - name: Sets env vars for tokio if set in manual dispatch inputs - run: | - echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true - + run: | + echo "RUSTFLAGS=--cfg tokio_unstable" >> $GITHUB_ENV + echo "CARGO_FEATURES=--features tokio-console" >> $GITHUB_ENV - name: Install Rust stable uses: actions-rs/toolchain@v1 with: @@ -103,7 +102,6 @@ jobs: if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then cp target/debian/*.deb $OUTPUT_DIR fi - - name: Deploy branch to CI www continue-on-error: true uses: easingthemes/ssh-deploy@main diff --git a/.github/workflows/ci-sonar.yml b/.github/workflows/ci-sonar.yml new file mode 100644 index 0000000000..a06986dfcd --- /dev/null +++ b/.github/workflows/ci-sonar.yml @@ -0,0 +1,19 @@ +name: Run SonarQube Scan +on: + push: + branches: + - develop +# pull_request: +# types: [opened, synchronize, reopened] +jobs: + sonarqube: + name: SonarQube + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: SonarQube Scan + uses: SonarSource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index ccc7a60e1a..b2c156b1d9 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -38,10 +38,9 @@ jobs: 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 }}/Cargo.toml + run: | + yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: cleanup-gateway-probe-ref id: cleanup_gateway_probe_ref diff --git a/.github/workflows/push-node-status-api.yaml b/.github/workflows/push-node-status-api.yaml index 8548841435..9087fda7aa 100644 --- a/.github/workflows/push-node-status-api.yaml +++ b/.github/workflows/push-node-status-api.yaml @@ -32,10 +32,9 @@ jobs: 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 }}/Cargo.toml + run: | + yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - name: Set GIT_TAG variable run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..573e3538ca --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,686 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Nym is a privacy platform that uses mixnet technology to protect against metadata surveillance. The platform consists of several key components: +- Mixnet nodes (mixnodes) for packet mixing +- Gateways (entry/exit points for the network) +- Clients for interacting with the network +- Network monitoring tools +- Validators for network consensus +- Various service providers and integrations + +## Build Commands + +### Rust Components + +```bash +# Default build (debug) +cargo build + +# Release build +cargo build --release + +# Build a specific package +cargo build -p + +# Build main components +make build + +# Build release versions of main binaries and contracts +make build-release + +# Build specific binaries +make build-nym-cli +cargo build -p nym-node --release +cargo build -p nym-api --release +``` + +### Testing + +```bash +# Run clippy, unit tests, and formatting +make test + +# Run all tests including slow tests +make test-all + +# Run clippy on all workspaces +make clippy + +# Run unit tests for a specific package +cargo test -p + +# Run only expensive/ignored tests +cargo test --workspace -- --ignored + +# Run API tests +dotenv -f envs/sandbox.env -- cargo test --test public-api-tests + +# Run tests with specific log level +RUST_LOG=debug cargo test -p + +# Run specific test scripts +./nym-node/tests/test_apis.sh +./scripts/wireguard-exit-policy/exit-policy-tests.sh +``` + +### Linting and Formatting + +```bash +# Run rustfmt on all code +make fmt + +# Check formatting without modifying +cargo fmt --all -- --check + +# Run clippy with all targets +cargo clippy --workspace --all-targets -- -D warnings + +# TypeScript linting +yarn lint +yarn lint:fix +yarn types:lint:fix + +# Check dependencies for security/licensing issues +cargo deny check +``` + +### WASM Components + +```bash +# Build all WASM components +make sdk-wasm-build + +# Build TypeScript SDK +yarn build:sdk +npx lerna run --scope @nymproject/sdk build --stream + +# Build and test WASM components +make sdk-wasm + +# Build specific WASM packages +cd wasm/client && make +cd wasm/mix-fetch && make +cd wasm/node-tester && make +``` + +### Contract Development + +```bash +# Build all contracts +make contracts + +# Build contracts in release mode +make build-release-contracts + +# Generate contract schemas +make contract-schema + +# Run wasm-opt on contracts +make wasm-opt-contracts + +# Check contracts with cosmwasm-check +make cosmwasm-check-contracts +``` + +### Running Components + +```bash +# Run nym-node as a mixnode +cargo run -p nym-node -- run --mode mixnode + +# Run nym-node as a gateway +cargo run -p nym-node -- run --mode gateway + +# Run the network monitor +cargo run -p nym-network-monitor + +# Run the API server +cargo run -p nym-api + +# Run with specific environment +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Start a local network +./scripts/localnet_start.sh +``` + +## Architecture + +The Nym platform consists of various components organized as a monorepo: + +1. **Core Mixnet Infrastructure**: + - `nym-node`: Core binary supporting mixnode and gateway modes + - `common/nymsphinx`: Implementation of the Sphinx packet format + - `common/topology`: Network topology management + - `common/types`: Shared data types across components + +2. **Network Monitoring**: + - `nym-network-monitor`: Monitors the network's reliability and performance + - `nym-api`: API server for network stats and monitoring data + - Metrics tracking for nodes, routes, and overall network health + +3. **Client Implementations**: + - `clients/native`: Native Rust client implementation + - `clients/socks5`: SOCKS5 proxy client for standard applications + - `wasm`: WebAssembly client implementations (for browsers) + - `nym-connect`: Desktop and mobile clients + +4. **Blockchain & Smart Contracts**: + - `common/cosmwasm-smart-contracts`: Smart contract implementations + - `contracts`: CosmWasm contracts for the Nym network + - `common/ledger`: Blockchain integration + +5. **Utilities & Tools**: + - `tools`: Various CLI tools and utilities + - `sdk`: SDKs for different languages and platforms + - `documentation`: Documentation generation and management + +## Packet System + +Nym uses a modified Sphinx packet format for its mixnet: + +1. **Message Chunking**: + - Messages are divided into "sets" and "fragments" + - Each fragment fits in a single Sphinx packet + - The `common/nymsphinx/chunking` module handles message fragmentation + +2. **Routing**: + - Packets traverse through 3 layers of mixnodes + - Routing information is encrypted in layers (onion routing) + - The final gateway receives and processes the messages + +3. **Monitoring**: + - Monitoring system tracks packet delivery through the network + - Routes are analyzed for reliability statistics + - Node performance metrics are collected + +## Network Protocol + +Nym implements the Loopix mixnet design with several key privacy features: + +1. **Continuous-time Mixing**: + - Each mixnode delays messages independently with an exponential distribution + - This creates random reordering of packets, destroying timing correlations + - Offers better anonymity properties than batch mixing approaches + +2. **Cover Traffic**: + - Clients and nodes generate dummy "loop" packets that circulate through the network + - These packets are indistinguishable from real traffic + - Creates a baseline level of traffic that hides actual communication patterns + - Provides unobservability (hiding when and how much real traffic is being sent) + +3. **Stratified Network Architecture**: + - Traffic flows through Entry Gateway → 3 Mixnode Layers → Exit Gateway + - Path selection is independent per-message (unlike Tor) + - Each node connects only to adjacent layers + +4. **Anonymous Replies**: + - Single-Use Reply Blocks (SURBs) allow receiving messages without revealing identity + - Enables bidirectional communication while maintaining privacy + +## Network Monitoring Architecture + +The network monitoring system is a core component that measures mixnet reliability: + +1. The `nym-network-monitor` sends test packets through the network +2. These packets follow predefined routes through multiple mixnodes +3. Metrics are collected about: + - Successful and failed packet deliveries + - Node reliability (percentage of successful packet handling) + - Route reliability (which specific route combinations work best) +4. Results are stored in the database and used by `nym-api` to: + - Present node performance statistics + - Determine network rewards + - Provide route selection guidance to clients + +In the current branch, metrics collection is being enhanced with a fanout approach to submit to multiple API endpoints. + +## Development Environment + +### Required Dependencies + +- Rust toolchain (stable, 1.80+) +- Node.js (v20+) and yarn for TypeScript components +- SQLite for local database development +- PostgreSQL for API database (optional, for full API functionality) +- CosmWasm tools for contract development +- For building contracts: `wasm-opt` tool from `binaryen` +- Python 3.8+ for some scripts +- Docker (optional, for containerized development) +- protoc (Protocol Buffers compiler) for some components + +### Environment Configurations + +The `envs/` directory contains pre-configured environments: + +#### Available Environments + +- **`local.env`**: Local development environment + - Points to local services (localhost) + - Uses test mnemonics and keys + - Ideal for testing without external dependencies + +- **`sandbox.env`**: Sandbox test network + - Public test network with real nodes + - Test tokens available from faucet + - Contract addresses for sandbox deployment + - API: https://sandbox-nym-api1.nymtech.net + +- **`mainnet.env`**: Production mainnet + - Real network with real tokens + - Production contract addresses + - API: https://validator.nymtech.net + - Use with caution! + +- **`canary.env`**: Canary deployment + - Pre-release testing environment + - Tests new features before mainnet + +- **`mainnet-local-api.env`**: Hybrid environment + - Uses mainnet contracts but local API + - Useful for API development against mainnet data + +#### Key Environment Variables + +```bash +# Network configuration +NETWORK_NAME=sandbox # Network identifier +BECH32_PREFIX=n # Address prefix (n for sandbox, n for mainnet) +NYM_API=https://sandbox-nym-api1.nymtech.net/api +NYXD=https://rpc.sandbox.nymtech.net +NYM_API_NETWORK=sandbox + +# Contract addresses (network-specific) +MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav +VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz +# ... other contract addresses + +# Mnemonic for testing (NEVER use in production) +MNEMONIC="clutch captain shoe salt awake harvest setup primary inmate ugly among become" + +# API Keys and tokens +IPINFO_API_TOKEN=your_token_here +AUTHENTICATOR_PASSWORD=password_here + +# Logging +RUST_LOG=info # Options: error, warn, info, debug, trace +RUST_BACKTRACE=1 # Enable backtraces + +# Database +DATABASE_URL=postgresql://user:pass@localhost/nym_api +``` + +#### Using Environment Files + +```bash +# Load environment and run command +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Export to shell +source envs/sandbox.env + +# Use with make targets +dotenv -f envs/sandbox.env -- make run-api-tests +``` + +## Initial Setup + +### First Time Setup + +1. **Install Prerequisites** + ```bash + # Install Rust + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + # Install Node.js and yarn + # Via nvm (recommended): + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash + nvm install 20 + npm install -g yarn + + # Install build tools + # Ubuntu/Debian: + sudo apt-get install build-essential pkg-config libssl-dev protobuf-compiler libpq-dev + + # macOS: + brew install protobuf postgresql + + # Install wasm-opt for contract builds + npm install -g wasm-opt + + # Add wasm target for Rust + rustup target add wasm32-unknown-unknown + ``` + +2. **Clone and Setup Repository** + ```bash + git clone https://github.com/nymtech/nym.git + cd nym/nym + + # Install JavaScript dependencies + yarn install + + # Build the project + make build + ``` + +3. **Database Setup (Optional, for API development)** + ```bash + # Install PostgreSQL + # Create database + createdb nym_api + + # Run migrations (from nym-api directory) + cd nym-api + sqlx migrate run + ``` + +### Quick Start + +```bash +# Run a mixnode locally +dotenv -f envs/sandbox.env -- cargo run -p nym-node -- run --mode mixnode --id my-mixnode + +# Run a gateway locally +dotenv -f envs/sandbox.env -- cargo run -p nym-node -- run --mode gateway --id my-gateway + +# Run the API server +dotenv -f envs/sandbox.env -- cargo run -p nym-api + +# Run a client +cargo run -p nym-client -- init --id my-client +cargo run -p nym-client -- run --id my-client +``` + +## CI/CD Pipeline + +The project uses GitHub Actions for CI/CD with several key workflows: + +1. **Build and Test**: + - `ci-build.yml`: Main build workflow for Rust components + - Tests are run on multiple platforms (Linux, Windows, macOS) + - Includes formatting check (rustfmt) and linting (clippy) + +2. **Release Process**: + - Binary artifacts are published on release tags + - Multiple platform builds are created + +3. **Documentation**: + - Documentation is automatically built and deployed + +## Database Structure + +The system uses SQLite databases with tables like: +- `mixnode_status`: Status information about mixnodes +- `gateway_status`: Status information about gateways +- `routes`: Route performance information (success/failure of specific paths) +- `monitor_run`: Information about monitoring test runs + +## Development Workflows + +### Running a Node + +To run the mixnode or gateway: + +```bash +# Run nym-node as a mixnode with specified identity +cargo run -p nym-node -- run --mode mixnode --id my-mixnode + +# Run nym-node as a gateway +cargo run -p nym-node -- run --mode gateway --id my-gateway +``` + +### Configuration + +Nodes can be configured with files in various locations: +- Command-line arguments +- Environment variables +- `.env` files specified with `--config-env-file` + +### Monitoring + +To monitor the health of your node: +- View logs for real-time information +- Use the node's HTTP API for status information +- Check the explorer for public node statistics + +## Common Libraries + +- `common/types`: Shared data types across all components +- `common/crypto`: Cryptographic primitives and wrappers +- `common/client-core`: Core client functionality +- `common/gateway-client`: Client-gateway communication +- `common/task`: Task management and concurrency utilities +- `common/nymsphinx`: Sphinx packet implementation for mixnet +- `common/topology`: Network topology management +- `common/credentials`: Credential system for privacy-preserving authentication +- `common/bandwidth-controller`: Bandwidth management and accounting + +## Code Conventions + +- Error handling: Use anyhow/thiserror for structured error handling +- Logging: Use the tracing framework for logging and diagnostics +- State management: Generally use Tokio/futures for async code +- Configuration: Use the config crate and env vars with defaults +- Database: Use sqlx for type-safe database queries +- Follow clippy recommendations and rustfmt formatting +- Use semantic commit messages: feat, fix, docs, refactor, test, chore + +## When Making Changes + +- Run `make test` before submitting PRs +- Follow Rust naming conventions +- Use `clippy` to check for common issues +- Update SQLx query caches when modifying DB queries: `cargo sqlx prepare` +- Consider backward compatibility for protocol changes +- Use lefthook pre-commit hooks for TypeScript formatting +- Run `cargo deny check` to verify dependency compliance +- Test against both sandbox and local environments when possible +- Update relevant documentation and CHANGELOG.md + +## Development Tools + +### Useful Cargo Commands + +```bash +# Check for outdated dependencies +cargo outdated + +# Analyze binary size +cargo bloat --release -p nym-node + +# Generate dependency graph +cargo tree -p nym-api + +# Run with instrumentation +cargo run --features profiling -p nym-node + +# Check for security advisories +cargo audit +``` + +### Database Tools + +```bash +# SQLx CLI for migrations +cargo install sqlx-cli + +# Create new migration +cd nym-api && sqlx migrate add + +# Prepare query metadata for offline compilation +cargo sqlx prepare --workspace + +# View database schema +./nym-api/enter_db.sh +``` + +### Development Scripts + +- `scripts/build_topology.py`: Generate network topology files +- `scripts/node_api_check.py`: Verify node API endpoints +- `scripts/network_tunnel_manager.sh`: Manage network tunnels +- `scripts/localnet_start.sh`: Start a local test network +- Various deployment scripts in `deployment/` for different environments + +## Debugging + +- Enable more verbose logging with the RUST_LOG environment variable: + ``` + RUST_LOG=debug,nym_node=trace cargo run -p nym-node -- run --mode mixnode + ``` +- Use the HTTP API endpoints for status information +- Check monitoring data in the database for network performance metrics +- For complex issues, use tracing tools to follow packet flow +- Enable backtraces: `RUST_BACKTRACE=full` +- For WASM debugging: Use browser developer tools with source maps + +## Deployment and Advanced Configurations + +### Deployment Structure + +The `deployment/` directory contains Ansible playbooks and configurations for various deployment scenarios: + +- **`aws/`**: AWS-specific deployment configurations +- **`mixnode/`**: Mixnode deployment playbooks +- **`gateway/`**: Gateway deployment playbooks +- **`validator/`**: Validator node deployment +- **`sandbox-v2/`**: Complete sandbox environment setup +- **`big-dipper-2/`**: Block explorer deployment + +### Sandbox V2 Deployment + +The sandbox-v2 deployment (`deployment/sandbox-v2/`) provides a complete test environment: + +```bash +# Key playbooks: +- deploy.yaml # Main deployment orchestrator +- deploy-mixnodes.yaml # Deploy mixnodes +- deploy-gateways.yaml # Deploy gateways +- deploy-validators.yaml # Deploy validator nodes +- deploy-nym-api.yaml # Deploy API services +``` + +### Custom Environment Setup + +To create a custom environment: + +1. Copy an existing env file: `cp envs/sandbox.env envs/custom.env` +2. Modify the network endpoints and contract addresses +3. Update the `NETWORK_NAME` to your identifier +4. Set appropriate mnemonics and keys (use fresh ones for production!) + +### Contract Addresses + +Contract addresses are network-specific and defined in environment files: +- Mixnet contract: Manages mixnode/gateway registry +- Vesting contract: Handles token vesting schedules +- Coconut contracts: Privacy-preserving credentials +- Name service: Human-readable address mapping +- Ecash contract: Electronic cash functionality + +### Local Network Setup + +For a completely local network: +```bash +# Start local chain +./scripts/localnet_start.sh + +# Deploy contracts +cd contracts +make deploy-local + +# Start nodes with local config +dotenv -f envs/local.env -- cargo run -p nym-node -- run --mode mixnode +``` + +## Common Issues and Troubleshooting + +### Database Issues + +- When modifying database queries, you must update SQLx query caches: + ```bash + cargo sqlx prepare + ``` +- If you see SQLx errors about missing query files, this is likely the cause +- For "database is locked" errors with SQLite, ensure only one process accesses the DB +- For PostgreSQL connection issues, verify DATABASE_URL and that the server is running + +### API Connection Issues + +- Check the environment variables pointing to the APIs (NYM_API, NYXD) +- Verify network connectivity and API health endpoints +- For authentication issues, check node keys and credentials +- Common endpoints to verify: + - API health: `$NYM_API/health` + - Chain status: `$NYXD/status` + - Contract info: `$NYXD/cosmwasm/wasm/v1/contract/$CONTRACT_ADDRESS` + +### Build Problems + +- Clean dependencies with `cargo clean` for a fresh build +- Check for compatible Rust version (1.80+ recommended) +- For smart contract builds, ensure wasm-opt is installed: `npm install -g wasm-opt` +- For cross-compilation issues, check target-specific dependencies +- WASM build issues: Ensure wasm32-unknown-unknown target is installed: + ```bash + rustup target add wasm32-unknown-unknown + ``` +- For "cannot find -lpq" errors, install PostgreSQL development files: + ```bash + # Ubuntu/Debian + sudo apt-get install libpq-dev + # macOS + brew install postgresql + ``` + +### Environment Issues + +- Contract address mismatches: Ensure you're using the correct environment file +- "Account sequence mismatch": The account nonce is out of sync, wait and retry +- Token decimal issues: Sandbox uses different decimal places than mainnet +- API version mismatches: Ensure your local API version matches the network +- "Insufficient funds": Get test tokens from faucet (sandbox) or check balance +- Gateway/mixnode bonding issues: Verify minimum stake requirements + +## Working with Routes and Monitoring + +1. Route monitoring metrics are stored in a `routes` table with: + - Layer node IDs (layer1, layer2, layer3, gw) + - Success flag (boolean) + - Timestamp + +2. To analyze routes: + - Check `NetworkAccount` and `AccountingRoute` in `nym-network-monitor/src/accounting.rs` + - View monitoring logic in `common/nymsphinx/chunking/monitoring.rs` + - Observe how routes are submitted to the database in the `submit_accounting_routes_to_db` function + +## Performance Optimization + +### Profiling and Benchmarking + +```bash +# Run benchmarks +cargo bench -p nym-node + +# Profile with perf (Linux) +cargo build --release --features profiling +perf record --call-graph=dwarf ./target/release/nym-node run --mode mixnode +perf report + +# Generate flamegraph +cargo install flamegraph +cargo flamegraph --bin nym-node -- run --mode mixnode +``` + +### Common Performance Considerations + +- Use bounded channels for backpressure +- Batch database operations where possible +- Monitor memory usage with `RUST_LOG=nym_node::metrics=debug` +- Use connection pooling for database connections +- Consider using `jemalloc` for better memory allocation performance \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e9e17cd839..7d61a368d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,14 +4,14 @@ version = 3 [[package]] name = "accessory" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb3791c4beae5b827e93558ac83a88e63a841aad61759a05d9b577ef16030470" +checksum = "28e416a3ab45838bac2ab2d81b1088d738d7b2d2c5272a54d39366565a29bd80" dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -35,9 +35,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -91,14 +91,14 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -133,9 +133,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ada2ee439075a3e70b6992fce18ac4e407cd05aea9ca3f75d2c0b0c20bbb364" +checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" dependencies = [ "cssparser", "html5ever", @@ -167,9 +167,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -182,36 +182,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] @@ -408,7 +408,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -446,9 +446,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.18" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" dependencies = [ "brotli", "flate2", @@ -471,17 +471,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -501,7 +490,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -512,7 +501,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -555,9 +544,9 @@ checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "autodoc" @@ -567,34 +556,6 @@ dependencies = [ "log", ] -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower 0.4.13", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.7.9" @@ -612,7 +573,41 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "itoa", - "matchit", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +dependencies = [ + "axum-core 0.5.2", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -641,23 +636,6 @@ dependencies = [ "serde", ] -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.4.5" @@ -679,6 +657,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "axum-extra" version = "0.9.6" @@ -711,7 +709,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -725,7 +723,7 @@ dependencies = [ "auto-future", "axum 0.7.9", "bytes", - "bytesize", + "bytesize 1.3.3", "cookie", "http 1.3.1", "http-body-util", @@ -734,7 +732,37 @@ dependencies = [ "mime", "pretty_assertions", "reserve-port", - "rust-multipart-rfc7578_2", + "rust-multipart-rfc7578_2 0.6.1", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower 0.5.2", + "url", +] + +[[package]] +name = "axum-test" +version = "17.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum 0.8.4", + "bytes", + "bytesize 2.0.1", + "cookie", + "http 1.3.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2 0.8.0", "serde", "serde_json", "serde_urlencoded", @@ -746,9 +774,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -785,9 +813,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "base85rs" @@ -797,9 +825,9 @@ checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a" [[package]] name = "basic-toml" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -838,9 +866,9 @@ dependencies = [ [[package]] name = "bip39" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +checksum = "43d193de1f7487df1914d3a568b772458861d33f9c54249612cc2893d6915054" dependencies = [ "bitcoin_hashes", "rand 0.8.5", @@ -874,9 +902,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -916,9 +944,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17679a8d69b6d7fd9cd9801a536cec9fa5e5970b69f9d4747f70b39b031f5e7" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", @@ -961,7 +989,7 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6d7f06817e48ea4e17532fa61bc4e8b9a101437f0623f69d2ea54284f3a817" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "siphasher 1.0.1", ] @@ -989,9 +1017,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1000,9 +1028,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1020,9 +1048,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-tools" @@ -1057,15 +1085,21 @@ dependencies = [ [[package]] name = "bytesize" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" +checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" + +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -1121,9 +1155,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.14" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -1142,9 +1176,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -1241,9 +1275,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.38" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -1251,9 +1285,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.38" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -1263,9 +1297,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.50" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" dependencies = [ "clap", ] @@ -1282,27 +1316,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -1322,7 +1356,7 @@ checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ "crossterm 0.28.1", "unicode-segmentation", - "unicode-width 0.2.0", + "unicode-width 0.2.1", ] [[package]] @@ -1343,17 +1377,31 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.1", "windows-sys 0.59.0", ] [[package]] -name = "console-api" -version = "0.5.0" +name = "console" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" dependencies = [ - "prost 0.11.9", + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "console-api" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857" +dependencies = [ + "futures-core", + "prost", "prost-types", "tonic", "tracing-core", @@ -1361,16 +1409,18 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.10" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cf42660ac07fcebed809cfe561dd8730bcd35b075215e6479c516bcd0d11cb" +checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01" dependencies = [ "console-api", "crossbeam-channel", "crossbeam-utils", - "futures", + "futures-task", "hdrhistogram", "humantime", + "hyper-util", + "prost", "prost-types", "serde", "serde_json", @@ -1463,7 +1513,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "462e1f6a8e005acc8835d32d60cbd7973ed65ea2a8d8473830e675f050956427" dependencies = [ - "prost 0.13.5", + "prost", "tendermint-proto", ] @@ -1526,7 +1576,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1536,7 +1586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6984ab21b47a096e17ae4c73cea2123a704d4b6686c39421247ad67020d76f95" dependencies = [ "cosmwasm-schema-derive", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 1.0.69", @@ -1550,7 +1600,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1569,7 +1619,7 @@ dependencies = [ "hex", "rand_core 0.6.4", "rmp-serde", - "schemars", + "schemars 0.8.22", "serde", "serde-json-wasm", "sha2 0.10.9", @@ -1588,9 +1638,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -1603,9 +1653,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1719,7 +1769,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "crossterm_winapi", "parking_lot", "rustix 0.38.44", @@ -1737,9 +1787,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -1794,7 +1844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1839,9 +1889,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.47" +version = "0.4.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" +checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b" dependencies = [ "curl-sys", "libc", @@ -1849,14 +1899,14 @@ dependencies = [ "openssl-sys", "schannel", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.80+curl-8.12.1" +version = "0.4.82+curl-8.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f7df2eac63200c3ab25bde3b2268ef2ee56af3d238e76d61f01c3c49bff734" +checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be" dependencies = [ "cc", "libc", @@ -1864,7 +1914,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1892,7 +1942,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -1918,7 +1968,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "cw-utils", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -1936,8 +1986,8 @@ dependencies = [ "cw-storage-plus", "cw-utils", "itertools 0.14.0", - "prost 0.13.5", - "schemars", + "prost", + "schemars 0.8.22", "serde", "sha2 0.10.9", "thiserror 2.0.12", @@ -1950,7 +2000,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ "cosmwasm-std", - "schemars", + "schemars 0.8.22", "serde", ] @@ -1962,7 +2012,7 @@ checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -1976,7 +2026,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "thiserror 1.0.69", @@ -1991,7 +2041,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-utils", - "schemars", + "schemars 0.8.22", "serde", ] @@ -2005,7 +2055,7 @@ dependencies = [ "cosmwasm-std", "cw-utils", "cw20", - "schemars", + "schemars 0.8.22", "serde", "thiserror 1.0.69", ] @@ -2019,15 +2069,15 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -2035,27 +2085,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2074,9 +2124,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "defguard_wireguard_rs" @@ -2108,14 +2158,14 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -2151,7 +2201,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2180,7 +2230,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "unicode-xid", ] @@ -2192,7 +2242,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "unicode-xid", ] @@ -2261,7 +2311,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2310,9 +2360,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "easy-addr" @@ -2389,9 +2439,9 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", @@ -2419,9 +2469,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] @@ -2470,7 +2520,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2513,12 +2563,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2560,9 +2610,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.0", "pin-project-lite", @@ -2602,14 +2652,14 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fancy_constructor" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac0fd7f4636276b4bd7b3148d0ba2c1c3fbede2b5214e47e7fedb70b02cde44" +checksum = "28a27643a5d05f3a22f5afd6e0d0e6e354f92d37907006f97b84b9cb79082198" dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2663,9 +2713,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2839,7 +2889,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -2880,15 +2930,16 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "generator" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", "rustversion", - "windows 0.58.0", + "windows 0.61.3", ] [[package]] @@ -2914,41 +2965,41 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", "wasm-bindgen", - "windows-targets 0.52.6", ] [[package]] name = "getset" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3056,9 +3107,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -3066,7 +3117,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3075,9 +3126,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -3085,7 +3136,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -3094,9 +3145,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -3111,7 +3162,7 @@ dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.1", + "quick-error", "serde", "serde_json", ] @@ -3143,9 +3194,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -3158,7 +3209,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.4", ] [[package]] @@ -3176,11 +3227,11 @@ dependencies = [ [[package]] name = "headers" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "headers-core", "http 1.3.1", @@ -3221,15 +3272,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -3251,35 +3296,33 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hickory-proto" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d844af74f7b799e41c78221be863bade11c430d46042c3b49ca8ae0c6d27287" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" dependencies = [ - "async-recursion", "async-trait", "bytes", "cfg-if", - "critical-section", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", - "h2 0.4.9", + "h2 0.4.11", "http 1.3.1", "idna", "ipnet", "once_cell", - "rand 0.9.0", + "rand 0.9.2", "ring", - "rustls 0.23.25", + "rustls 0.23.29", "thiserror 2.0.12", "tinyvec", "tokio", "tokio-rustls 0.26.2", "tracing", "url", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -3295,15 +3338,15 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.0", + "rand 0.9.2", "resolv-conf", - "rustls 0.23.25", + "rustls 0.23.29", "smallvec", "thiserror 2.0.12", "tokio", "tokio-rustls 0.26.2", "tracing", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -3345,25 +3388,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "html5ever" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953cbbe631aae7fc0a112702ad5d3aaf09da38beaf45ea84610d6e1c358f569c" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" dependencies = [ "log", - "mac", "markup5ever", "match_token", ] @@ -3432,9 +3463,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpcodec" @@ -3484,7 +3515,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -3507,6 +3538,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.11", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -3534,47 +3566,51 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http 1.3.1", "hyper 1.6.0", "hyper-util", - "rustls 0.23.25", + "rustls 0.23.29", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", "tower-service", - "webpki-roots 0.26.8", + "webpki-roots 1.0.2", ] [[package]] name = "hyper-timeout" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 0.14.32", + "hyper 1.6.0", + "hyper-util", "pin-project-lite", "tokio", - "tokio-io-timeout", + "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", "hyper 1.6.0", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -3584,16 +3620,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -3607,21 +3644,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -3630,31 +3668,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -3662,67 +3680,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -3742,9 +3747,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -3758,7 +3763,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3827,7 +3832,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -3843,12 +3848,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -3858,10 +3863,23 @@ version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console", + "console 0.15.11", "number_prefix", "portable-atomic", - "unicode-width 0.2.0", + "unicode-width 0.2.1", + "web-time", +] + +[[package]] +name = "indicatif" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +dependencies = [ + "console 0.16.0", + "portable-atomic", + "unicode-width 0.2.1", + "unit-prefix", "vt100", "web-time", ] @@ -3888,9 +3906,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array 0.14.7", @@ -3929,13 +3947,24 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "inventory" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" dependencies = [ "rustversion", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + [[package]] name = "ip_network" version = "0.4.1" @@ -3970,12 +3999,22 @@ dependencies = [ ] [[package]] -name = "is-terminal" -version = "0.4.15" +name = "iri-string" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" dependencies = [ - "hermit-abi 0.4.0", + "memchr", + "serde", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -4031,15 +4070,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", @@ -4050,21 +4089,22 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] @@ -4100,9 +4140,9 @@ checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -4166,23 +4206,23 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] @@ -4200,9 +4240,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.21" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -4218,9 +4258,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "lioness" @@ -4236,26 +4276,20 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" version = "0.4.27" @@ -4275,6 +4309,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -4301,7 +4341,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4313,7 +4353,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4326,7 +4366,7 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4337,30 +4377,24 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" -version = "0.16.1" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a8096766c229e8c88a3900c9b44b7e06aa7f7343cc229158c3e58ef8f9973a" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" dependencies = [ "log", "tendril", "web_atoms", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "match_token" -version = "0.1.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -4378,6 +4412,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -4390,9 +4430,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memoffset" @@ -4437,9 +4477,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -4452,19 +4492,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -4582,7 +4622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" dependencies = [ "anyhow", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "libc", "log", @@ -4648,7 +4688,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cfg-if", "libc", ] @@ -4659,7 +4699,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", @@ -4790,11 +4830,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -4815,18 +4855,16 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.62" +version = "1.1.63" dependencies = [ "anyhow", "async-trait", "axum 0.7.9", - "axum-extra", - "axum-test", + "axum-test 16.4.1", "bincode", "bip39", "bs58", "cfg-if", - "chrono", "clap", "console-subscriber", "cosmwasm-std", @@ -4835,13 +4873,9 @@ dependencies = [ "cw3", "cw4", "dashmap", - "dirs", "dotenv", "futures", - "getset", "humantime-serde", - "itertools 0.14.0", - "k256", "moka", "nym-api-requests 0.1.0", "nym-bandwidth-controller", @@ -4856,12 +4890,11 @@ dependencies = [ "nym-crypto 0.4.0", "nym-dkg", "nym-ecash-contract-common 0.1.0", + "nym-ecash-signer-check", "nym-ecash-time 0.1.0", "nym-gateway-client", "nym-http-api-common 0.1.0", - "nym-inclusion-probability", "nym-mixnet-contract-common 0.6.0", - "nym-multisig-contract-common 0.1.0", "nym-node-requests 0.1.0", "nym-node-tester-utils", "nym-pemstore 0.3.0", @@ -4873,12 +4906,11 @@ dependencies = [ "nym-topology 0.1.0", "nym-types", "nym-validator-client 0.1.0", - "nym-vesting-contract-common 0.7.0", "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde_json", @@ -4891,7 +4923,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "ts-rs", "url", @@ -4909,14 +4941,15 @@ dependencies = [ "cosmrs", "cosmwasm-std", "ecdsa", - "getset", "hex", "humantime-serde", + "nym-coconut-dkg-common 0.1.0", "nym-compact-ecash 0.1.0", "nym-config 0.1.0", "nym-contracts-common 0.5.0", "nym-credentials-interface 0.1.0", "nym-crypto 0.4.0", + "nym-ecash-signer-check-types", "nym-ecash-time 0.1.0", "nym-mixnet-contract-common 0.6.0", "nym-network-defaults 0.1.0", @@ -4925,7 +4958,7 @@ dependencies = [ "nym-serde-helpers 0.1.0", "nym-ticketbooks-merkle 0.1.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -4941,7 +4974,7 @@ dependencies = [ [[package]] name = "nym-api-requests" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmrs", @@ -4961,7 +4994,7 @@ dependencies = [ "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-ticketbooks-merkle 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -5020,6 +5053,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.12", + "time", "tokio", "tokio-stream", "tokio-util", @@ -5078,7 +5112,7 @@ dependencies = [ "log", "opentelemetry", "opentelemetry-jaeger", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tracing", @@ -5092,11 +5126,11 @@ dependencies = [ [[package]] name = "nym-bin-common" version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "const-str", "log", - "schemars", + "schemars 0.8.22", "serde", "utoipa", "vergen", @@ -5104,7 +5138,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.59" +version = "1.1.60" dependencies = [ "anyhow", "base64 0.22.1", @@ -5179,14 +5213,14 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", - "toml 0.8.22", + "toml 0.8.23", "url", "zeroize", ] [[package]] name = "nym-client" -version = "1.1.59" +version = "1.1.60" dependencies = [ "bs58", "clap", @@ -5228,6 +5262,7 @@ dependencies = [ "async-trait", "base64 0.22.1", "bs58", + "cfg-if", "clap", "comfy-table", "futures", @@ -5377,7 +5412,7 @@ dependencies = [ [[package]] name = "nym-coconut-dkg-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -5415,7 +5450,7 @@ dependencies = [ [[package]] name = "nym-compact-ecash" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bincode", "bls12_381", @@ -5445,14 +5480,14 @@ dependencies = [ "nym-network-defaults 0.1.0", "serde", "thiserror 2.0.12", - "toml 0.8.22", + "toml 0.8.23", "url", ] [[package]] name = "nym-config" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dirs", "handlebars", @@ -5460,7 +5495,7 @@ dependencies = [ "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "serde", "thiserror 2.0.12", - "toml 0.8.22", + "toml 0.8.23", "url", ] @@ -5473,7 +5508,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "thiserror 2.0.12", @@ -5484,13 +5519,13 @@ dependencies = [ [[package]] name = "nym-contracts-common" version = "0.5.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", "vergen", @@ -5550,7 +5585,7 @@ dependencies = [ "nym-network-defaults 0.1.0", "nym-validator-client 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sqlx", @@ -5562,7 +5597,7 @@ dependencies = [ "tokio", "tokio-util", "tower 0.5.2", - "tower-http", + "tower-http 0.5.2", "tracing", "url", "utoipa", @@ -5581,8 +5616,8 @@ dependencies = [ "nym-http-api-client 0.1.0", "nym-http-api-common 0.1.0", "nym-serde-helpers 0.1.0", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "serde_json", "time", @@ -5633,9 +5668,11 @@ dependencies = [ name = "nym-credential-verification" version = "0.1.0" dependencies = [ + "async-trait", "bs58", "cosmwasm-std", "cw-utils", + "dyn-clone", "futures", "nym-api-requests 0.1.0", "nym-credentials", @@ -5695,7 +5732,7 @@ dependencies = [ [[package]] name = "nym-credentials-interface" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bls12_381", "nym-compact-ecash 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -5741,7 +5778,7 @@ dependencies = [ [[package]] name = "nym-crypto" version = "0.4.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "aead", "aes", @@ -5807,7 +5844,7 @@ dependencies = [ [[package]] name = "nym-ecash-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", @@ -5818,6 +5855,36 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "nym-ecash-signer-check" +version = "0.1.0" +dependencies = [ + "futures", + "nym-ecash-signer-check-types", + "nym-network-defaults 0.1.0", + "nym-validator-client 0.1.0", + "semver 1.0.26", + "thiserror 2.0.12", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "nym-ecash-signer-check-types" +version = "0.1.0" +dependencies = [ + "nym-coconut-dkg-common 0.1.0", + "nym-crypto 0.4.0", + "semver 1.0.26", + "serde", + "thiserror 2.0.12", + "time", + "tracing", + "url", + "utoipa", +] + [[package]] name = "nym-ecash-time" version = "0.1.0" @@ -5829,7 +5896,7 @@ dependencies = [ [[package]] name = "nym-ecash-time" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "time", ] @@ -5846,7 +5913,7 @@ dependencies = [ name = "nym-exit-policy" version = "0.1.0" dependencies = [ - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -5857,7 +5924,7 @@ dependencies = [ [[package]] name = "nym-exit-policy" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "serde", "serde_json", @@ -5937,7 +6004,7 @@ name = "nym-gateway-client" version = "0.1.0" dependencies = [ "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "gloo-utils 0.2.0", "nym-bandwidth-controller", "nym-credential-storage", @@ -6019,8 +6086,10 @@ dependencies = [ name = "nym-gateway-storage" version = "0.1.0" dependencies = [ + "async-trait", "bincode", "defguard_wireguard_rs", + "dyn-clone", "nym-credentials-interface 0.1.0", "nym-gateway-requests", "nym-sphinx 0.1.0", @@ -6055,19 +6124,19 @@ dependencies = [ "cosmwasm-schema", "cw-controllers", "cw4", - "schemars", + "schemars 0.8.22", "serde", ] [[package]] name = "nym-group-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cw-controllers", "cw4", - "schemars", + "schemars 0.8.22", "serde", ] @@ -6086,7 +6155,7 @@ dependencies = [ "nym-bin-common 0.6.0", "nym-http-api-common 0.1.0", "once_cell", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6099,7 +6168,7 @@ dependencies = [ [[package]] name = "nym-http-api-client" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "bincode", @@ -6111,7 +6180,7 @@ dependencies = [ "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "once_cell", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6145,7 +6214,7 @@ dependencies = [ [[package]] name = "nym-http-api-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "axum 0.7.9", "axum-client-ip", @@ -6244,7 +6313,7 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -6279,7 +6348,7 @@ dependencies = [ [[package]] name = "nym-metrics" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dashmap", "lazy_static", @@ -6317,7 +6386,7 @@ dependencies = [ "humantime-serde", "nym-contracts-common 0.5.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde_repr", @@ -6330,7 +6399,7 @@ dependencies = [ [[package]] name = "nym-mixnet-contract-common" version = "0.6.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "cosmwasm-schema", @@ -6339,7 +6408,7 @@ dependencies = [ "cw-storage-plus", "humantime-serde", "nym-contracts-common 0.5.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "semver 1.0.26", "serde", "serde-json-wasm", @@ -6386,7 +6455,7 @@ dependencies = [ "cw-utils", "cw3", "cw4", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6394,7 +6463,7 @@ dependencies = [ [[package]] name = "nym-multisig-contract-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -6402,7 +6471,7 @@ dependencies = [ "cw-utils", "cw3", "cw4", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6415,7 +6484,7 @@ dependencies = [ "dotenvy", "log", "regex", - "schemars", + "schemars 0.8.22", "serde", "url", "utoipa", @@ -6424,13 +6493,13 @@ dependencies = [ [[package]] name = "nym-network-defaults" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cargo_metadata 0.18.1", "dotenvy", "log", "regex", - "schemars", + "schemars 0.8.22", "serde", "url", "utoipa", @@ -6459,7 +6528,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "tokio", @@ -6471,7 +6540,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.60" +version = "1.1.61" dependencies = [ "addr", "anyhow", @@ -6505,7 +6574,7 @@ dependencies = [ "publicsuffix", "rand 0.8.5", "regex", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sqlx", @@ -6521,7 +6590,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.15.0" +version = "1.16.0" dependencies = [ "anyhow", "arc-swap", @@ -6534,9 +6603,11 @@ dependencies = [ "bs58", "cargo_metadata 0.19.2", "celes", + "cfg-if", "chacha", "clap", "colored", + "console-subscriber", "criterion", "csv", "cupid", @@ -6544,7 +6615,7 @@ dependencies = [ "hkdf", "human-repr", "humantime-serde", - "indicatif", + "indicatif 0.17.11", "ipnetwork", "lioness", "nym-authenticator", @@ -6591,8 +6662,8 @@ dependencies = [ "time", "tokio", "tokio-util", - "toml 0.8.22", - "tower-http", + "toml 0.8.23", + "tower-http 0.5.2", "tracing", "tracing-indicatif", "tracing-subscriber", @@ -6631,7 +6702,7 @@ dependencies = [ "nym-noise-keys", "nym-wireguard-types 0.1.0", "rand_chacha 0.3.1", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "strum 0.26.3", @@ -6644,7 +6715,7 @@ dependencies = [ [[package]] name = "nym-node-requests" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "celes", @@ -6655,7 +6726,7 @@ dependencies = [ "nym-exit-policy 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-wireguard-types 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "strum 0.26.3", @@ -6666,13 +6737,14 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "1.0.0" +version = "1.0.4" dependencies = [ "anyhow", "clap", - "nym-bin-common 0.6.0", - "nym-crypto 0.4.0", - "nym-node-status-client 0.1.1", + "futures", + "nym-bin-common 0.6.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", + "nym-node-status-client", "rand 0.8.5", "tempfile", "tokio", @@ -6682,11 +6754,12 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "3.1.2" +version = "3.2.2" dependencies = [ "ammonia", "anyhow", "axum 0.7.9", + "axum-test 17.3.0", "bip39", "celes", "clap", @@ -6704,7 +6777,7 @@ dependencies = [ "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-node-metrics", "nym-node-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "nym-node-status-client 0.1.0", + "nym-node-status-client", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-statistics-common 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-task 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -6712,7 +6785,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "reqwest 0.12.15", + "reqwest 0.12.22", "semver 1.0.26", "serde", "serde_json", @@ -6724,7 +6797,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-log 0.2.0", "tracing-subscriber", @@ -6735,29 +6808,13 @@ dependencies = [ [[package]] name = "nym-node-status-client" -version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +version = "0.1.2" dependencies = [ "anyhow", "bincode", - "chrono", "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-http-api-client 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "reqwest 0.12.15", - "serde", - "serde_json", - "tracing", -] - -[[package]] -name = "nym-node-status-client" -version = "0.1.1" -dependencies = [ - "anyhow", - "bincode", - "nym-crypto 0.4.0", - "nym-http-api-client 0.1.0", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "time", @@ -6830,7 +6887,7 @@ name = "nym-noise-keys" version = "0.1.0" dependencies = [ "nym-crypto 0.4.0", - "schemars", + "schemars 0.8.22", "serde", "utoipa", ] @@ -6878,7 +6935,7 @@ dependencies = [ "chacha20poly1305", "criterion", "fastrand 2.3.0", - "getrandom 0.2.15", + "getrandom 0.2.16", "log", "rand 0.8.5", "rayon", @@ -6891,12 +6948,12 @@ dependencies = [ [[package]] name = "nym-outfox" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "blake3", "chacha20", "chacha20poly1305", - "getrandom 0.2.15", + "getrandom 0.2.16", "log", "rand 0.8.5", "rayon", @@ -6918,7 +6975,7 @@ dependencies = [ [[package]] name = "nym-pemstore" version = "0.3.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "pem", "tracing", @@ -6932,7 +6989,7 @@ dependencies = [ "cosmwasm-std", "cw-controllers", "nym-contracts-common 0.5.0", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", ] @@ -6944,7 +7001,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-controllers", - "schemars", + "schemars 0.8.22", "serde", "thiserror 2.0.12", "time", @@ -6991,7 +7048,7 @@ dependencies = [ "nym-validator-client 0.1.0", "parking_lot", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "tap", "tempfile", @@ -7000,7 +7057,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio-util", - "toml 0.8.22", + "toml 0.8.23", "tracing", "tracing-subscriber", "url", @@ -7022,7 +7079,7 @@ dependencies = [ [[package]] name = "nym-serde-helpers" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "base64 0.22.1", "bs58", @@ -7059,7 +7116,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.59" +version = "1.1.60" dependencies = [ "bs58", "clap", @@ -7113,8 +7170,8 @@ dependencies = [ "nym-validator-client 0.1.0", "pin-project", "rand 0.8.5", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "tap", "thiserror 2.0.12", @@ -7181,7 +7238,7 @@ dependencies = [ [[package]] name = "nym-sphinx" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-metrics 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7225,7 +7282,7 @@ dependencies = [ [[package]] name = "nym-sphinx-acknowledgements" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-pemstore 0.3.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7253,7 +7310,7 @@ dependencies = [ [[package]] name = "nym-sphinx-addressing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7282,7 +7339,7 @@ dependencies = [ [[package]] name = "nym-sphinx-anonymous-replies" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bs58", "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7318,7 +7375,7 @@ dependencies = [ [[package]] name = "nym-sphinx-chunking" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "dashmap", "log", @@ -7353,7 +7410,7 @@ dependencies = [ [[package]] name = "nym-sphinx-cover" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7382,7 +7439,7 @@ dependencies = [ [[package]] name = "nym-sphinx-forwarding" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7410,7 +7467,7 @@ dependencies = [ [[package]] name = "nym-sphinx-framing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "bytes", "nym-sphinx-acknowledgements 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7436,7 +7493,7 @@ dependencies = [ [[package]] name = "nym-sphinx-params" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-crypto 0.4.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7456,7 +7513,7 @@ dependencies = [ [[package]] name = "nym-sphinx-routing" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-sphinx-addressing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7475,7 +7532,7 @@ dependencies = [ [[package]] name = "nym-sphinx-types" version = "0.2.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-outfox 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "sphinx-packet", @@ -7504,7 +7561,7 @@ dependencies = [ "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-subscriber", "url", @@ -7540,7 +7597,7 @@ dependencies = [ [[package]] name = "nym-statistics-common" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "futures", "log", @@ -7568,7 +7625,7 @@ dependencies = [ "aes-gcm", "argon2", "generic-array 0.14.7", - "getrandom 0.2.15", + "getrandom 0.2.16", "rand 0.8.5", "serde", "serde_json", @@ -7595,7 +7652,7 @@ dependencies = [ [[package]] name = "nym-task" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cfg-if", "futures", @@ -7618,7 +7675,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rs_merkle", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -7629,12 +7686,12 @@ dependencies = [ [[package]] name = "nym-ticketbooks-merkle" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "nym-credentials-interface 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "rs_merkle", - "schemars", + "schemars 0.8.22", "serde", "sha2 0.10.9", "time", @@ -7652,7 +7709,7 @@ dependencies = [ "nym-sphinx-addressing 0.1.0", "nym-sphinx-types 0.2.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "thiserror 2.0.12", @@ -7666,7 +7723,7 @@ dependencies = [ [[package]] name = "nym-topology" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "nym-api-requests 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", @@ -7677,7 +7734,7 @@ dependencies = [ "nym-sphinx-routing 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-sphinx-types 0.2.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "thiserror 2.0.12", "tracing", @@ -7711,8 +7768,8 @@ dependencies = [ "nym-mixnet-contract-common 0.6.0", "nym-validator-client 0.1.0", "nym-vesting-contract-common 0.7.0", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "serde_json", "sha2 0.10.9", @@ -7759,8 +7816,8 @@ dependencies = [ "nym-performance-contract-common", "nym-serde-helpers 0.1.0", "nym-vesting-contract-common 0.7.0", - "prost 0.13.5", - "reqwest 0.12.15", + "prost", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -7778,7 +7835,7 @@ dependencies = [ [[package]] name = "nym-validator-client" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "async-trait", "base64 0.22.1", @@ -7809,8 +7866,8 @@ dependencies = [ "nym-network-defaults 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-serde-helpers 0.1.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", "nym-vesting-contract-common 0.7.0 (git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar)", - "prost 0.13.5", - "reqwest 0.12.15", + "prost", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -7902,7 +7959,7 @@ dependencies = [ [[package]] name = "nym-vesting-contract-common" version = "0.7.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -7917,7 +7974,7 @@ name = "nym-vpn-api-lib-wasm" version = "0.1.0" dependencies = [ "bs58", - "getrandom 0.2.15", + "getrandom 0.2.16", "js-sys", "nym-bin-common 0.6.0", "nym-compact-ecash 0.1.0", @@ -7960,11 +8017,13 @@ dependencies = [ name = "nym-wireguard" version = "0.1.0" dependencies = [ + "async-trait", "base64 0.22.1", "bincode", "chrono", "dashmap", "defguard_wireguard_rs", + "dyn-clone", "futures", "ip_network", "log", @@ -8002,7 +8061,7 @@ dependencies = [ [[package]] name = "nym-wireguard-types" version = "0.1.0" -source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#e9bb9792ab723a1ad5fe40cb292dc08d4eb40c2f" +source = "git+https://github.com/nymtech/nym.git?branch=release/2025.11-cheddar#0d420fb0a56f010b86562fb037034b1ae477a3b8" dependencies = [ "base64 0.22.1", "log", @@ -8015,7 +8074,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.24" +version = "0.1.25" dependencies = [ "anyhow", "bytes", @@ -8031,7 +8090,7 @@ dependencies = [ "nym-bin-common 0.6.0", "nym-config 0.1.0", "nym-task 0.1.0", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "serde_json", "sha2 0.10.9", @@ -8058,15 +8117,15 @@ dependencies = [ "nym-task 0.1.0", "nym-validator-client 0.1.0", "nyxd-scraper", - "reqwest 0.12.15", - "schemars", + "reqwest 0.12.22", + "schemars 0.8.22", "serde", "sqlx", "thiserror 2.0.12", "time", "tokio", "tokio-util", - "tower-http", + "tower-http 0.5.2", "tracing", "tracing-subscriber", "utoipa", @@ -8118,10 +8177,16 @@ dependencies = [ ] [[package]] -name = "oorandom" -version = "11.1.4" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -8143,9 +8208,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.106" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -8293,9 +8358,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -8303,9 +8368,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -8333,9 +8398,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "peg" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" dependencies = [ "peg-macros", "peg-runtime", @@ -8343,9 +8408,9 @@ dependencies = [ [[package]] name = "peg-macros" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" dependencies = [ "peg-runtime", "proc-macro2", @@ -8354,9 +8419,9 @@ dependencies = [ [[package]] name = "peg-runtime" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "pem" @@ -8386,9 +8451,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -8397,9 +8462,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -8407,24 +8472,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2 0.10.9", ] @@ -8436,7 +8500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -8479,7 +8543,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -8508,7 +8572,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -8546,9 +8610,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -8625,9 +8689,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -8651,7 +8715,7 @@ dependencies = [ "hmac", "md-5", "memchr", - "rand 0.9.0", + "rand 0.9.2", "sha2 0.10.9", "stringprep", ] @@ -8667,6 +8731,15 @@ dependencies = [ "postgres-protocol", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -8675,11 +8748,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -8726,23 +8799,23 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proc_pidinfo" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af53dad2390f8df98dda1e4188322bdf2f91c86cf6001f51d10d64451edf463a" +checksum = "29492a7b48a00ab80202528e235d2f80a04ccff3747540b4ec6881f2f2bc42d1" dependencies = [ "libc", ] @@ -8762,16 +8835,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive 0.11.9", -] - [[package]] name = "prost" version = "0.13.5" @@ -8779,20 +8842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive 0.13.5", -] - -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", + "prost-derive", ] [[package]] @@ -8805,16 +8855,16 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ - "prost 0.11.9", + "prost", ] [[package]] @@ -8839,9 +8889,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.86" +version = "2.1.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e02ed846877ce4044391085ca68b470b0d379cd18a9be0666161764d35448" +checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" dependencies = [ "psl-types", ] @@ -8862,12 +8912,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -8876,9 +8920,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", "cfg_aliases", @@ -8886,7 +8930,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.25", + "rustls 0.23.29", "socket2", "thiserror 2.0.12", "tokio", @@ -8896,16 +8940,17 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.3.1", - "rand 0.9.0", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.25", + "rustls 0.23.29", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -8916,9 +8961,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -8937,6 +8982,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -8956,13 +9007,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.1", - "zerocopy 0.8.20", + "rand_core 0.9.3", ] [[package]] @@ -8982,7 +9032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.1", + "rand_core 0.9.3", ] [[package]] @@ -8991,17 +9041,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.20", + "getrandom 0.3.3", ] [[package]] @@ -9036,11 +9085,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] [[package]] @@ -9049,11 +9098,31 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -9109,7 +9178,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", @@ -9141,9 +9210,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.15" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "async-compression", "base64 0.22.1", @@ -9154,18 +9223,14 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.6.0", - "hyper-rustls 0.27.5", + "hyper-rustls 0.27.7", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.25", - "rustls-pemfile 2.2.0", + "rustls 0.23.29", "rustls-pki-types", "serde", "serde_json", @@ -9173,38 +9238,32 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-rustls 0.26.2", - "tokio-socks", "tokio-util", "tower 0.5.2", + "tower-http 0.6.6", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.8", - "windows-registry", + "webpki-roots 1.0.2", ] [[package]] name = "reserve-port" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "359fc315ed556eb0e42ce74e76f4b1cd807b50fa6307f3de4e51f92dbe86e2d5" +checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "lazy_static", "thiserror 2.0.12", ] [[package]] name = "resolv-conf" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", -] +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "rfc6979" @@ -9218,13 +9277,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -9272,9 +9331,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest 0.10.7", @@ -9292,9 +9351,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9303,22 +9362,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.98", + "syn 2.0.104", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.5.0" +version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ "sha2 0.10.9", "walkdir", @@ -9341,10 +9400,25 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rust-multipart-rfc7578_2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "mime", + "rand 0.9.2", + "thiserror 2.0.12", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -9376,7 +9450,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -9385,15 +9459,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.2", - "windows-sys 0.59.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] @@ -9424,15 +9498,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.4", "subtle 2.6.1", "zeroize", ] @@ -9482,11 +9556,12 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] @@ -9512,9 +9587,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -9523,15 +9598,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -9565,6 +9640,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -9574,7 +9673,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9600,13 +9699,13 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9627,7 +9726,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9656,9 +9755,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" dependencies = [ "cc", ] @@ -9669,7 +9768,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", @@ -9767,7 +9866,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9778,7 +9877,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9789,14 +9888,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -9851,14 +9950,14 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -9872,14 +9971,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -9898,15 +9997,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -9916,14 +10017,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -9932,7 +10033,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "itoa", "ryu", "serde", @@ -10017,9 +10118,9 @@ checksum = "b72e7cd0744e007e382ba320435f1ed1ecd709409b4ebd5cfbc843d77b25a8aa" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -10038,9 +10139,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -10075,12 +10176,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "sluice" @@ -10095,9 +10193,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -10148,9 +10246,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -10231,14 +10329,14 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "hashlink", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.25", + "rustls 0.23.29", "serde", "serde_json", "sha2 0.10.9", @@ -10249,7 +10347,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.26.8", + "webpki-roots 0.26.11", ] [[package]] @@ -10262,7 +10360,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10285,7 +10383,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.98", + "syn 2.0.104", "tokio", "url", ] @@ -10298,7 +10396,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", @@ -10344,7 +10442,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "windows 0.61.1", + "windows 0.61.3", ] [[package]] @@ -10355,7 +10453,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.8.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", @@ -10436,9 +10534,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", @@ -10517,7 +10615,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10560,9 +10658,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -10586,13 +10684,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10660,9 +10758,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.1", + "rustix 1.0.8", "windows-sys 0.59.0", ] @@ -10681,7 +10779,7 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost 0.13.5", + "prost", "ripemd", "serde", "serde_bytes", @@ -10706,7 +10804,7 @@ dependencies = [ "serde", "serde_json", "tendermint", - "toml 0.8.22", + "toml 0.8.23", "url", ] @@ -10718,7 +10816,7 @@ checksum = "d2c40e13d39ca19082d8a7ed22de7595979350319833698f8b1080f29620a094" dependencies = [ "bytes", "flex-error", - "prost 0.13.5", + "prost", "serde", "serde_bytes", "subtle-encoding", @@ -10736,7 +10834,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "peg", "pin-project", "rand 0.8.5", @@ -10787,11 +10885,11 @@ dependencies = [ "bip39", "bs58", "clap", - "console", + "console 0.15.11", "cw-utils", "dkg-bypass-contract", "humantime", - "indicatif", + "indicatif 0.17.11", "nym-bin-common 0.6.0", "nym-coconut-dkg-common 0.1.0", "nym-compact-ecash 0.1.0", @@ -10814,7 +10912,7 @@ dependencies = [ "thiserror 2.0.12", "time", "tokio", - "toml 0.8.22", + "toml 0.8.23", "tracing", "url", "zeroize", @@ -10822,9 +10920,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", ] @@ -10855,7 +10953,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -10866,17 +10964,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -10937,9 +11034,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -10957,9 +11054,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -10972,33 +11069,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.1" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", - "mio 1.0.3", + "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "tracing", "windows-sys 0.52.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.5.0" @@ -11007,7 +11096,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11029,7 +11118,7 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "rand 0.9.0", + "rand 0.9.2", "socket2", "tokio", "tokio-util", @@ -11063,19 +11152,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.25", - "tokio", -] - -[[package]] -name = "tokio-socks" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" -dependencies = [ - "either", - "futures-util", - "thiserror 1.0.69", + "rustls 0.23.29", "tokio", ] @@ -11141,7 +11218,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -11158,9 +11235,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -11170,20 +11247,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", @@ -11193,30 +11270,32 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tonic" -version = "0.9.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ + "async-stream", "async-trait", - "axum 0.6.20", - "base64 0.21.7", + "axum 0.7.9", + "base64 0.22.1", "bytes", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", + "h2 0.4.11", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", "hyper-timeout", + "hyper-util", "percent-encoding", "pin-project", - "prost 0.11.9", + "prost", + "socket2", "tokio", "tokio-stream", "tower 0.4.13", @@ -11268,7 +11347,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", - "bitflags 2.8.0", + "bitflags 2.9.1", "bytes", "futures-core", "futures-util", @@ -11288,6 +11367,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -11314,20 +11411,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -11345,11 +11442,11 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8201ca430e0cd893ef978226fd3516c06d9c494181c8bf4e5b32e30ed4b40aa1" +checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" dependencies = [ - "indicatif", + "indicatif 0.18.0", "tracing", "tracing-core", "tracing-subscriber", @@ -11490,7 +11587,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "termcolor", ] @@ -11517,7 +11614,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11588,15 +11685,15 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -11621,9 +11718,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" @@ -11633,9 +11730,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd1d240101ba3b9d7532ae86d9cb64d9a7ff63e13a2b7b9e94a32a601d8233" +checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" dependencies = [ "anyhow", "camino", @@ -11650,9 +11747,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0525f06d749ea80d8049dc0bb038bb87941e3d909eefa76b6f0a5589b59ac5" +checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" dependencies = [ "anyhow", "askama", @@ -11662,7 +11759,7 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.7.1", + "indexmap 2.10.0", "once_cell", "serde", "tempfile", @@ -11676,9 +11773,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed2f0204e942bb9c11c9f11a323b4abf70cf11b2e5957d60b3f2728430f6c6f" +checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" dependencies = [ "anyhow", "camino", @@ -11687,9 +11784,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fa8eb4d825b4ed095cb13483cba6927c3002b9eb603cef9b7688758cc3772e" +checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" dependencies = [ "anyhow", "bytes", @@ -11699,22 +11796,22 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b547d69d699e52f2129fde4b57ae0d00b5216e59ed5b56097c95c86ba06095" +checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" dependencies = [ "anyhow", - "indexmap 2.7.1", + "indexmap 2.10.0", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "uniffi_macros" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f1de72edc8cb9201c7d650e3678840d143e4499004571aac49e6cb1b17da43" +checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" dependencies = [ "camino", "fs-err", @@ -11722,16 +11819,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.98", + "syn 2.0.104", "toml 0.5.11", "uniffi_meta", ] [[package]] name = "uniffi_meta" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acc9204632f6a555b2cba7c8852c5523bc1aa5f3eff605c64af5054ea28b72e" +checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" dependencies = [ "anyhow", "siphasher 0.3.11", @@ -11741,22 +11838,22 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b5336a9a925b358183837d31541d12590b7fcec373256d3770de02dff24c69" +checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.7.1", + "indexmap 2.10.0", "tempfile", "uniffi_internal_macros", ] [[package]] name = "uniffi_udl" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95e73373d85f04736bc51997d3e6855721144ec4384cae9ca8513c80615e129" +checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" dependencies = [ "anyhow", "textwrap", @@ -11764,6 +11861,12 @@ dependencies = [ "weedle2", ] +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + [[package]] name = "universal-hash" version = "0.5.1" @@ -11810,12 +11913,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -11830,11 +11927,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_json", "utoipa-gen", @@ -11842,14 +11939,14 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.98", + "syn 2.0.104", "uuid", ] @@ -11888,7 +11985,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -11899,18 +11996,20 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -12024,15 +12123,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -12065,7 +12164,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -12100,7 +12199,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12135,7 +12234,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12176,7 +12275,7 @@ name = "wasm-storage" version = "0.1.0" dependencies = [ "async-trait", - "getrandom 0.2.15", + "getrandom 0.2.16", "indexed_db_futures", "js-sys", "nym-store-cipher", @@ -12206,7 +12305,7 @@ version = "0.1.0" dependencies = [ "console_error_panic_hook", "futures", - "getrandom 0.2.15", + "getrandom 0.2.16", "gloo-net", "gloo-utils 0.2.0", "js-sys", @@ -12218,9 +12317,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -12252,9 +12351,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9c5f0bc545ea3b20b423e33b9b457764de0b3730cd957f6c6aa6c301785f6e" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ "phf", "phf_codegen", @@ -12279,9 +12378,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -12297,9 +12405,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -12308,9 +12416,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -12355,19 +12463,9 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core 0.61.2", @@ -12385,15 +12483,6 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.57.0" @@ -12406,19 +12495,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -12429,7 +12505,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-link", "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-strings", ] [[package]] @@ -12451,18 +12527,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12473,7 +12538,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12484,18 +12549,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] @@ -12506,14 +12560,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -12525,17 +12579,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result 0.3.4", - "windows-strings 0.3.1", - "windows-targets 0.53.0", -] - [[package]] name = "windows-result" version = "0.1.2" @@ -12545,15 +12588,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" @@ -12563,25 +12597,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.2" @@ -12627,6 +12642,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -12675,9 +12699,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -12880,9 +12904,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -12899,24 +12923,18 @@ dependencies = [ [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.8.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "wyz" @@ -12941,13 +12959,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", - "linux-raw-sys 0.4.15", - "rustix 0.38.44", + "rustix 1.0.8", ] [[package]] @@ -12958,9 +12975,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -12970,75 +12987,54 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" -dependencies = [ - "zerocopy-derive 0.8.20", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", "synstructure", ] @@ -13059,14 +13055,25 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -13075,27 +13082,27 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.104", ] [[package]] name = "zip" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.7.1", + "indexmap 2.10.0", "memchr", "thiserror 2.0.12", "zopfli", @@ -13108,7 +13115,7 @@ dependencies = [ "anyhow", "async-trait", "bs58", - "getrandom 0.2.15", + "getrandom 0.2.16", "js-sys", "nym-bin-common 0.6.0", "nym-compact-ecash 0.1.0", @@ -13116,7 +13123,7 @@ dependencies = [ "nym-crypto 0.4.0", "nym-http-api-client 0.1.0", "rand 0.8.5", - "reqwest 0.12.15", + "reqwest 0.12.22", "serde", "thiserror 2.0.12", "tokio", @@ -13130,42 +13137,57 @@ dependencies = [ [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zulip-client" +version = "0.1.0" +dependencies = [ + "itertools 0.14.0", + "nym-bin-common 0.6.0", + "nym-http-api-client 0.1.0", + "reqwest 0.12.22", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tracing", + "url", + "zeroize", +] diff --git a/Cargo.toml b/Cargo.toml index 8b3404c545..1bee069b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ members = [ "common/cosmwasm-smart-contracts/ecash-contract", "common/cosmwasm-smart-contracts/group-contract", "common/cosmwasm-smart-contracts/mixnet-contract", - "common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/nym-performance-contract", + "common/cosmwasm-smart-contracts/multisig-contract", + "common/cosmwasm-smart-contracts/nym-performance-contract", "common/cosmwasm-smart-contracts/nym-pool-contract", "common/cosmwasm-smart-contracts/vesting-contract", "common/credential-storage", @@ -49,6 +50,8 @@ members = [ "common/credentials-interface", "common/crypto", "common/dkg", + "common/ecash-signer-check", + "common/ecash-signer-check-types", "common/ecash-time", "common/execute", "common/exit-policy", @@ -99,7 +102,7 @@ members = [ "common/wasm/storage", "common/wasm/utils", "common/wireguard", - "common/wireguard-types", + "common/wireguard-types", "common/zulip-client", "documentation/autodoc", "gateway", "nym-api", @@ -218,7 +221,7 @@ clap_complete_fig = "4.5" colored = "2.2" comfy-table = "7.1.4" console = "0.15.11" -console-subscriber = "0.1.1" +console-subscriber = "0.4.1" console_error_panic_hook = "0.1" const-str = "0.5.6" const_format = "0.2.34" @@ -234,6 +237,7 @@ digest = "0.10.7" dirs = "5.0" doc-comment = "0.3" dotenvy = "0.15.6" +dyn-clone = "1.0.19" ecdsa = "0.16" ed25519-dalek = "2.1" encoding_rs = "0.8.35" @@ -434,6 +438,9 @@ opt-level = 'z' # lto = true opt-level = 'z' +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] } + [workspace.lints.clippy] unwrap_used = "deny" expect_used = "deny" @@ -443,3 +450,4 @@ exit = "deny" panic = "deny" unimplemented = "deny" unreachable = "deny" + diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 22e8312b20..b3083d7f8d 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.59" +version = "1.1.60" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index d32e3e835f..ce36a5eb84 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -111,7 +111,7 @@ impl SocketClient { let dkg_query_client = if self.config.base.client.disabled_credentials_mode { None } else { - Some(default_query_dkg_client_from_config(&self.config.base)) + Some(default_query_dkg_client_from_config(&self.config.base)?) }; let storage = self.initialise_storage().await?; diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index c2e035c8a2..0a4d5f8cd1 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.59" +version = "1.1.60" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/common/authenticator-requests/src/v5/registration.rs b/common/authenticator-requests/src/v5/registration.rs index 28aadf9c6d..3c51776ca0 100644 --- a/common/authenticator-requests/src/v5/registration.rs +++ b/common/authenticator-requests/src/v5/registration.rs @@ -28,8 +28,6 @@ pub type HmacSha256 = Hmac; pub type Nonce = u64; pub type Taken = Option; -pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct IpPair { pub ipv4: Ipv4Addr, diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index 7cca47759f..74663fe807 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -13,6 +13,7 @@ async-trait = { workspace = true } base64 = { workspace = true } bs58 = { workspace = true } clap = { workspace = true, optional = true } +cfg-if = { workspace = true } comfy-table = { workspace = true, optional = true } futures = { workspace = true } humantime = { workspace = true } @@ -123,3 +124,6 @@ fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"] fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"] wasm = ["nym-gateway-client/wasm"] metrics-server = [] + +[lints] +workspace = true diff --git a/common/client-core/src/cli_helpers/client_import_coin_index_signatures.rs b/common/client-core/src/cli_helpers/client_import_coin_index_signatures.rs index 5625250ce3..ce282ef9d1 100644 --- a/common/client-core/src/cli_helpers/client_import_coin_index_signatures.rs +++ b/common/client-core/src/cli_helpers/client_import_coin_index_signatures.rs @@ -58,6 +58,7 @@ where Some(data) => data, None => { // SAFETY: one of those arguments must have been set + #[allow(clippy::unwrap_used)] fs::read(common_args.signatures_path.unwrap())? } }; diff --git a/common/client-core/src/cli_helpers/client_import_credential.rs b/common/client-core/src/cli_helpers/client_import_credential.rs index 77c6dc16f4..30799167ce 100644 --- a/common/client-core/src/cli_helpers/client_import_credential.rs +++ b/common/client-core/src/cli_helpers/client_import_credential.rs @@ -64,6 +64,7 @@ where Some(data) => data, None => { // SAFETY: one of those arguments must have been set + #[allow(clippy::unwrap_used)] fs::read(common_args.credential_path.unwrap())? } }; diff --git a/common/client-core/src/cli_helpers/client_import_expiration_date_signatures.rs b/common/client-core/src/cli_helpers/client_import_expiration_date_signatures.rs index 4dc875e75c..f3752587ab 100644 --- a/common/client-core/src/cli_helpers/client_import_expiration_date_signatures.rs +++ b/common/client-core/src/cli_helpers/client_import_expiration_date_signatures.rs @@ -58,6 +58,7 @@ where Some(data) => data, None => { // SAFETY: one of those arguments must have been set + #[allow(clippy::unwrap_used)] fs::read(common_args.signatures_path.unwrap())? } }; diff --git a/common/client-core/src/cli_helpers/client_import_master_verification_key.rs b/common/client-core/src/cli_helpers/client_import_master_verification_key.rs index b44334b49e..677018226b 100644 --- a/common/client-core/src/cli_helpers/client_import_master_verification_key.rs +++ b/common/client-core/src/cli_helpers/client_import_master_verification_key.rs @@ -58,6 +58,7 @@ where Some(data) => data, None => { // SAFETY: one of those arguments must have been set + #[allow(clippy::unwrap_used)] fs::read(common_args.key_path.unwrap())? } }; diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index 2331b5de68..36cb427780 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -135,9 +135,11 @@ pub enum ClientInputStatus { } impl ClientInputStatus { + #[allow(clippy::panic)] pub fn register_producer(&mut self) -> ClientInput { match std::mem::replace(self, ClientInputStatus::Connected) { ClientInputStatus::AwaitingProducer { client_input } => client_input, + // critical failure implying misuse of software ClientInputStatus::Connected => panic!("producer was already registered before"), } } @@ -149,9 +151,11 @@ pub enum ClientOutputStatus { } impl ClientOutputStatus { + #[allow(clippy::panic)] pub fn register_consumer(&mut self) -> ClientOutput { match std::mem::replace(self, ClientOutputStatus::Connected) { ClientOutputStatus::AwaitingConsumer { client_output } => client_output, + // critical failure implying misuse of software ClientOutputStatus::Connected => panic!("consumer was already registered before"), } } @@ -707,11 +711,14 @@ where })?; let store_clone = mem_store.clone(); - spawn_future(async move { - persistent_storage - .flush_on_shutdown(store_clone, shutdown) - .await - }); + spawn_future!( + async move { + persistent_storage + .flush_on_shutdown(store_clone, shutdown) + .await + }, + "PersistentReplyStorage::flush_on_shutdown" + ); Ok(mem_store) } @@ -732,7 +739,7 @@ where let mut rng = OsRng; let keys = if let Some(derivation_material) = derivation_material { ClientKeys::from_master_key(&mut rng, &derivation_material) - .map_err(|_| ClientCoreError::HkdfDerivationError {})? + .map_err(|_| ClientCoreError::HkdfDerivationError)? } else { ClientKeys::generate_new(&mut rng) }; diff --git a/common/client-core/src/client/base_client/non_wasm_helpers.rs b/common/client-core/src/client/base_client/non_wasm_helpers.rs index 87e7fb62a2..365aabd0e8 100644 --- a/common/client-core/src/client/base_client/non_wasm_helpers.rs +++ b/common/client-core/src/client/base_client/non_wasm_helpers.rs @@ -114,41 +114,32 @@ pub async fn setup_fs_gateways_storage>( }) } -pub fn create_bandwidth_controller( - config: &Config, - storage: St, -) -> BandwidthController { - let nyxd_url = config - .get_validator_endpoints() - .pop() - .expect("No nyxd validator endpoint provided"); - - create_bandwidth_controller_with_urls(nyxd_url, storage) -} - pub fn create_bandwidth_controller_with_urls( nyxd_url: Url, storage: St, -) -> BandwidthController { - let client = default_query_dkg_client(nyxd_url); +) -> Result, ClientCoreError> { + let client = default_query_dkg_client(nyxd_url)?; - BandwidthController::new(storage, client) + Ok(BandwidthController::new(storage, client)) } -pub fn default_query_dkg_client_from_config(config: &Config) -> QueryHttpRpcNyxdClient { +pub fn default_query_dkg_client_from_config( + config: &Config, +) -> Result { let nyxd_url = config .get_validator_endpoints() .pop() - .expect("No nyxd validator endpoint provided"); + .ok_or(ClientCoreError::RpcClientMissingUrl)?; default_query_dkg_client(nyxd_url) } -pub fn default_query_dkg_client(nyxd_url: Url) -> QueryHttpRpcNyxdClient { +pub fn default_query_dkg_client(nyxd_url: Url) -> Result { let details = nym_network_defaults::NymNetworkDetails::new_from_env(); let client_config = nyxd::Config::try_from_nym_network_details(&details) - .expect("failed to construct validator client config"); + .map_err(|source| ClientCoreError::InvalidNetworkDetails { source })?; // overwrite env configuration with config URLs + QueryHttpRpcNyxdClient::connect(client_config, nyxd_url.as_str()) - .expect("Could not construct query client") + .map_err(|source| ClientCoreError::RpcClientCreationFailure { source }) } diff --git a/common/client-core/src/client/cover_traffic_stream.rs b/common/client-core/src/client/cover_traffic_stream.rs index a5c08cca0c..d635fc20c6 100644 --- a/common/client-core/src/client/cover_traffic_stream.rs +++ b/common/client-core/src/client/cover_traffic_stream.rs @@ -235,6 +235,7 @@ impl LoopCoverTrafficStream { tokio::task::yield_now().await; } + #[allow(clippy::panic)] pub fn start(mut self) { if self.cover_traffic.disable_loop_cover_traffic_stream { // we should have never got here in the first place - the task should have never been created to begin with @@ -251,27 +252,30 @@ impl LoopCoverTrafficStream { let mut shutdown = self.task_client.fork("select"); - spawn_future(async move { - debug!("Started LoopCoverTrafficStream with graceful shutdown support"); + spawn_future!( + async move { + debug!("Started LoopCoverTrafficStream with graceful shutdown support"); - while !shutdown.is_shutdown() { - tokio::select! { - biased; - _ = shutdown.recv() => { - tracing::trace!("LoopCoverTrafficStream: Received shutdown"); - } - next = self.next() => { - if next.is_some() { - self.on_new_message().await; - } else { - tracing::trace!("LoopCoverTrafficStream: Stopping since channel closed"); - break; + while !shutdown.is_shutdown() { + tokio::select! { + biased; + _ = shutdown.recv() => { + tracing::trace!("LoopCoverTrafficStream: Received shutdown"); + } + next = self.next() => { + if next.is_some() { + self.on_new_message().await; + } else { + tracing::trace!("LoopCoverTrafficStream: Stopping since channel closed"); + break; + } } } } - } - shutdown.recv_timeout().await; - tracing::debug!("LoopCoverTrafficStream: Exiting"); - }) + shutdown.recv_timeout().await; + tracing::debug!("LoopCoverTrafficStream: Exiting"); + }, + "LoopCoverTrafficStream" + ) } } diff --git a/common/client-core/src/client/mix_traffic/mod.rs b/common/client-core/src/client/mix_traffic/mod.rs index 5b7e5ab7bd..be7a517f57 100644 --- a/common/client-core/src/client/mix_traffic/mod.rs +++ b/common/client-core/src/client/mix_traffic/mod.rs @@ -98,6 +98,8 @@ impl MixTrafficController { debug_assert!(!mix_packets.is_empty()); let result = if mix_packets.len() == 1 { + // SAFETY: we just checked we have one packet + #[allow(clippy::unwrap_used)] let mix_packet = mix_packets.pop().unwrap(); self.gateway_transceiver.send_mix_packet(mix_packet).await } else { @@ -117,51 +119,54 @@ impl MixTrafficController { } pub fn start(mut self) { - spawn_future(async move { - debug!("Started MixTrafficController with graceful shutdown support"); + spawn_future!( + async move { + debug!("Started MixTrafficController with graceful shutdown support"); - while !self.task_client.is_shutdown() { - tokio::select! { - mix_packets = self.mix_rx.recv() => match mix_packets { - Some(mix_packets) => { - if let Err(err) = self.on_messages(mix_packets).await { - error!("Failed to send sphinx packet(s) to the gateway: {err}"); - if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT { - // Disconnect from the gateway. If we should try to re-connect - // is handled at a higher layer. - error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead"); - // Do we need to handle the embedded mixnet client case - // separately? - self.task_client.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages)); - break; + while !self.task_client.is_shutdown() { + tokio::select! { + mix_packets = self.mix_rx.recv() => match mix_packets { + Some(mix_packets) => { + if let Err(err) = self.on_messages(mix_packets).await { + error!("Failed to send sphinx packet(s) to the gateway: {err}"); + if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT { + // Disconnect from the gateway. If we should try to re-connect + // is handled at a higher layer. + error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead"); + // Do we need to handle the embedded mixnet client case + // separately? + self.task_client.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages)); + break; + } } + }, + None => { + tracing::trace!("MixTrafficController: Stopping since channel closed"); + break; } }, - None => { - tracing::trace!("MixTrafficController: Stopping since channel closed"); + client_request = self.client_rx.recv() => match client_request { + Some(client_request) => { + match self.gateway_transceiver.send_client_request(client_request).await { + Ok(_) => (), + Err(e) => error!("Failed to send client request: {e}"), + }; + }, + None => { + tracing::trace!("MixTrafficController, client request channel closed"); + } + }, + _ = self.task_client.recv() => { + tracing::trace!("MixTrafficController: Received shutdown"); break; } - }, - client_request = self.client_rx.recv() => match client_request { - Some(client_request) => { - match self.gateway_transceiver.send_client_request(client_request).await { - Ok(_) => (), - Err(e) => error!("Failed to send client request: {e}"), - }; - }, - None => { - tracing::trace!("MixTrafficController, client request channel closed"); - } - }, - _ = self.task_client.recv() => { - tracing::trace!("MixTrafficController: Received shutdown"); - break; } } - } - self.task_client.recv_timeout().await; + self.task_client.recv_timeout().await; - tracing::debug!("MixTrafficController: Exiting"); - }); + tracing::debug!("MixTrafficController: Exiting"); + }, + "MixTrafficController" + ); } } diff --git a/common/client-core/src/client/mix_traffic/transceiver.rs b/common/client-core/src/client/mix_traffic/transceiver.rs index 3bcc209e05..f749700ab2 100644 --- a/common/client-core/src/client/mix_traffic/transceiver.rs +++ b/common/client-core/src/client/mix_traffic/transceiver.rs @@ -269,6 +269,8 @@ pub struct MockGateway { } impl Default for MockGateway { + // test code + #[allow(clippy::unwrap_used)] fn default() -> Self { MockGateway { dummy_identity: "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7" diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index ad2fa1a3c2..350b400e46 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -194,10 +194,11 @@ impl ActionController { trace!("{frag_id} is updating its delay"); // TODO: is it possible to solve this without either locking or temporarily removing the value? if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.remove(&frag_id) { - // this Action is triggered by `RetransmissionRequestListener` (for 'normal' packets) + // SAFETY: this Action is triggered by `RetransmissionRequestListener` (for 'normal' packets) // or `ReplyController` (for 'reply' packets) which held the other potential // reference to this Arc. HOWEVER, before the Action was pushed onto the queue, the reference // was dropped hence this unwrap is safe. + #[allow(clippy::unwrap_used)] let mut inner_data = Arc::try_unwrap(pending_ack_data).unwrap(); inner_data.update_retransmitted(delay); @@ -209,6 +210,7 @@ impl ActionController { } // note: when the entry expires it's automatically removed from pending_acks_timers + #[allow(clippy::panic)] fn handle_expired_ack_timer(&mut self, expired_ack: Expired) { let frag_id = expired_ack.into_inner(); diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 9414b3a2fe..8185eb5a0a 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -120,6 +120,7 @@ where } } + #[allow(clippy::panic)] async fn on_input_message(&mut self, msg: InputMessage) { match msg { InputMessage::Regular { @@ -213,7 +214,9 @@ where self.handle_premade_packets(msgs, lane).await } // MessageWrappers can't be nested - InputMessage::MessageWrapper { .. } => unimplemented!(), + InputMessage::MessageWrapper { .. } => { + panic!("attempted to use nested MessageWrapper") + } }, }; } diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index 2cad9ef2c2..ea332ebdaf 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -298,29 +298,44 @@ where let mut sent_notification_listener = self.sent_notification_listener; let mut action_controller = self.action_controller; - spawn_future(async move { - acknowledgement_listener.run().await; - debug!("The acknowledgement listener has finished execution!"); - }); + spawn_future!( + async move { + acknowledgement_listener.run().await; + debug!("The acknowledgement listener has finished execution!"); + }, + "AcknowledgementController::AcknowledgementListener" + ); - spawn_future(async move { - input_message_listener.run().await; - debug!("The input listener has finished execution!"); - }); + spawn_future!( + async move { + input_message_listener.run().await; + debug!("The input listener has finished execution!"); + }, + "AcknowledgementController::InputMessageListener" + ); - spawn_future(async move { - retransmission_request_listener.run(packet_type).await; - debug!("The retransmission request listener has finished execution!"); - }); + spawn_future!( + async move { + retransmission_request_listener.run(packet_type).await; + debug!("The retransmission request listener has finished execution!"); + }, + "AcknowledgementController::RetransmissionRequestListener" + ); - spawn_future(async move { - sent_notification_listener.run().await; - debug!("The sent notification listener has finished execution!"); - }); + spawn_future!( + async move { + sent_notification_listener.run().await; + debug!("The sent notification listener has finished execution!"); + }, + "AcknowledgementController::SentNotificationListener" + ); - spawn_future(async move { - action_controller.run().await; - debug!("The controller has finished execution!"); - }); + spawn_future!( + async move { + action_controller.run().await; + debug!("The controller has finished execution!"); + }, + "AcknowledgementController::ActionController" + ); } } diff --git a/common/client-core/src/client/real_messages_control/message_handler.rs b/common/client-core/src/client/real_messages_control/message_handler.rs index 694f56d762..942d88c77a 100644 --- a/common/client-core/src/client/real_messages_control/message_handler.rs +++ b/common/client-core/src/client/real_messages_control/message_handler.rs @@ -35,6 +35,9 @@ pub enum PreparationError { #[error(transparent)] NymTopologyError(#[from] NymTopologyError), + #[error("message wasn't split into any fragments!")] + EmptyFragments, + #[error("message too long for a single SURB, splitting into {fragments} fragments.")] MessageTooLongForSingleSurb { fragments: usize }, @@ -320,6 +323,16 @@ where }); } + if fragment.is_empty() { + error!("CRITICAL FAILURE: our split message didn't result in any sendable fragments"); + return Err(SurbWrappedPreparationError { + source: PreparationError::EmptyFragments, + returned_surbs: Some(vec![reply_surb]), + }); + } + + // SAFETY: we just checked we have one fragment + #[allow(clippy::unwrap_used)] let chunk = fragment.pop().unwrap(); let chunk_clone = chunk.clone(); let prepared_fragment = self @@ -657,6 +670,7 @@ where .zip(reply_surbs.into_iter()) .map(|(fragment, reply_surb)| { // unwrap here is fine as we know we have a valid topology + #[allow(clippy::unwrap_used)] self.message_preparer .prepare_reply_chunk_for_sending( fragment, diff --git a/common/client-core/src/client/real_messages_control/mod.rs b/common/client-core/src/client/real_messages_control/mod.rs index a30b28fc41..b169a9bff8 100644 --- a/common/client-core/src/client/real_messages_control/mod.rs +++ b/common/client-core/src/client/real_messages_control/mod.rs @@ -224,14 +224,20 @@ impl RealMessagesController { let ack_control = self.ack_control; let mut reply_control = self.reply_control; - spawn_future(async move { - out_queue_control.run().await; - debug!("The out queue controller has finished execution!"); - }); - spawn_future(async move { - reply_control.run().await; - debug!("The reply controller has finished execution!"); - }); + spawn_future!( + async move { + out_queue_control.run().await; + debug!("The out queue controller has finished execution!"); + }, + "RealMessagesController::OutQueueControl)" + ); + spawn_future!( + async move { + reply_control.run().await; + debug!("The reply controller has finished execution!"); + }, + "RealMessagesController::ReplyController" + ); ack_control.start(packet_type); } diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index 8c77c1152c..d24947c9d2 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -249,6 +249,8 @@ where } }; + // SAFETY: our topology must be valid at this point + #[allow(clippy::expect_used)] ( generate_loop_cover_packet( &mut self.rng, @@ -439,6 +441,8 @@ where tracing::trace!("handling real_messages: size: {}", real_messages.len()); self.transmission_buffer.store(&conn_id, real_messages); + // SAFETY: we just stored the message + #[allow(clippy::expect_used)] let real_next = self.pop_next_message().expect("Just stored one"); Poll::Ready(Some(StreamMessage::Real(Box::new(real_next)))) @@ -487,6 +491,8 @@ where // First store what we got for the given connection id self.transmission_buffer.store(&conn_id, real_messages); + // SAFETY: we just stored the message + #[allow(clippy::expect_used)] let real_next = self.pop_next_message().expect("we just added one"); Poll::Ready(Some(StreamMessage::Real(Box::new(real_next)))) diff --git a/common/client-core/src/client/received_buffer.rs b/common/client-core/src/client/received_buffer.rs index 743fc79653..9ed1101867 100644 --- a/common/client-core/src/client/received_buffer.rs +++ b/common/client-core/src/client/received_buffer.rs @@ -198,6 +198,7 @@ impl ReceivedMessagesBuffer { } } + #[allow(clippy::panic)] async fn disconnect_sender(&mut self) { let mut guard = self.inner.lock().await; if guard.message_sender.is_none() { @@ -208,6 +209,7 @@ impl ReceivedMessagesBuffer { guard.message_sender = None; } + #[allow(clippy::panic)] async fn connect_sender(&mut self, sender: ReconstructedMessagesSender) { let mut guard = self.inner.lock().await; if guard.message_sender.is_some() { @@ -599,14 +601,20 @@ impl ReceivedMessagesBufferControll let mut fragmented_message_receiver = self.fragmented_message_receiver; let mut request_receiver = self.request_receiver; - spawn_future(async move { - match fragmented_message_receiver.run().await { - Ok(_) => {} - Err(e) => error!("{e}"), - } - }); - spawn_future(async move { - request_receiver.run().await; - }); + spawn_future!( + async move { + match fragmented_message_receiver.run().await { + Ok(_) => {} + Err(e) => error!("{e}"), + } + }, + "ReceivedMessagesBufferController::FragmentedMessageReceiver" + ); + spawn_future!( + async move { + request_receiver.run().await; + }, + "ReceivedMessagesBufferController::RequestReceiver" + ); } } diff --git a/common/client-core/src/client/replies/reply_controller/receiver_controller.rs b/common/client-core/src/client/replies/reply_controller/receiver_controller.rs index d8bbce9aac..11905c3a85 100644 --- a/common/client-core/src/client/replies/reply_controller/receiver_controller.rs +++ b/common/client-core/src/client/replies/reply_controller/receiver_controller.rs @@ -155,8 +155,9 @@ where data: Vec>, ) { trace!("re-inserting pending retransmissions for {recipient}"); - // the underlying entry MUST exist as we've just got data from there + // SAFETY: the underlying entry MUST exist as we've just got data from there // and we hold a mut reference + #[allow(clippy::expect_used)] let map_entry = &mut self .surb_senders .get_mut(recipient) @@ -429,6 +430,7 @@ where .pop_at_most_n_next_messages_at_random(amount) } + #[allow(clippy::panic)] async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) { trace!("trying to clear pending queue"); let available_surbs = self.surbs_storage.available_surbs(&target); diff --git a/common/client-core/src/client/statistics_control.rs b/common/client-core/src/client/statistics_control.rs index 98897ae6b9..6d315f4eb2 100644 --- a/common/client-core/src/client/statistics_control.rs +++ b/common/client-core/src/client/statistics_control.rs @@ -165,9 +165,12 @@ impl StatisticsControl { } pub(crate) fn start(mut self) { - spawn_future(async move { - self.run().await; - }) + spawn_future!( + async move { + self.run().await; + }, + "StatisticsControl" + ) } pub(crate) fn create_and_start( diff --git a/common/client-core/src/client/topology_control/mod.rs b/common/client-core/src/client/topology_control/mod.rs index 1aaff25120..b3c4d61f3f 100644 --- a/common/client-core/src/client/topology_control/mod.rs +++ b/common/client-core/src/client/topology_control/mod.rs @@ -145,36 +145,39 @@ impl TopologyRefresher { } pub fn start(mut self) { - spawn_future(async move { - debug!("Started TopologyRefresher with graceful shutdown support"); + spawn_future!( + async move { + debug!("Started TopologyRefresher with graceful shutdown support"); - #[cfg(not(target_arch = "wasm32"))] - let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval( - self.refresh_rate, - )); + #[cfg(not(target_arch = "wasm32"))] + let mut interval = tokio_stream::wrappers::IntervalStream::new( + tokio::time::interval(self.refresh_rate), + ); - #[cfg(target_arch = "wasm32")] - let mut interval = - gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32); + #[cfg(target_arch = "wasm32")] + 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; + // 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() => { - self.try_refresh().await; - }, - _ = self.task_client.recv() => { - tracing::trace!("TopologyRefresher: Received shutdown"); - }, + while !self.task_client.is_shutdown() { + tokio::select! { + _ = interval.next() => { + self.try_refresh().await; + }, + _ = self.task_client.recv() => { + tracing::trace!("TopologyRefresher: Received shutdown"); + }, + } } - } - self.task_client.recv_timeout().await; - tracing::debug!("TopologyRefresher: Exiting"); - }) + self.task_client.recv_timeout().await; + tracing::debug!("TopologyRefresher: Exiting"); + }, + "TopologyRefresher" + ) } } diff --git a/common/client-core/src/error.rs b/common/client-core/src/error.rs index a97030a24e..1c26e96dfb 100644 --- a/common/client-core/src/error.rs +++ b/common/client-core/src/error.rs @@ -7,7 +7,9 @@ use nym_gateway_client::error::GatewayClientError; use nym_topology::node::RoutingNodeError; use nym_topology::{NodeId, NymTopologyError}; use nym_validator_client::nym_api::error::NymAPIError; +use nym_validator_client::nyxd::error::NyxdError; use nym_validator_client::ValidatorClientError; +use rand::distributions::WeightedError; use std::error::Error; use std::path::PathBuf; @@ -230,7 +232,19 @@ pub enum ClientCoreError { UnexpectedKeyUpgrade { gateway_id: String }, #[error("failed to derive keys from master key")] - HkdfDerivationError {}, + HkdfDerivationError, + + #[error("missing url for constructing RPC client")] + RpcClientMissingUrl, + + #[error("provided nym network details were malformed: {source}")] + InvalidNetworkDetails { source: NyxdError }, + + #[error("failed to construct RPC client: {source}")] + RpcClientCreationFailure { source: NyxdError }, + + #[error("failed to select valid gateway due to incomputable latency")] + GatewaySelectionFailure { source: WeightedError }, } impl From for ClientCoreError { diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index d099431ded..97c3be13b7 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -245,7 +245,7 @@ pub async fn choose_gateway_by_latency( let gateways_with_latency = gateways_with_latency.lock().await; let chosen = gateways_with_latency .choose_weighted(rng, |item| 1. / item.latency.as_secs_f32()) - .expect("invalid selection weight!"); + .map_err(|source| ClientCoreError::GatewaySelectionFailure { source })?; info!( "chose gateway {} with average latency of {:?}", diff --git a/common/client-core/src/lib.rs b/common/client-core/src/lib.rs index ba1d80f4c4..8be8b10133 100644 --- a/common/client-core/src/lib.rs +++ b/common/client-core/src/lib.rs @@ -18,18 +18,54 @@ pub use nym_topology::{ }; #[cfg(target_arch = "wasm32")] -pub(crate) fn spawn_future(future: F) +pub fn spawn_future(future: F) where F: Future + 'static, { wasm_bindgen_futures::spawn_local(future); } +// TODO: expose similar API to the rest of the codebase, +// perhaps with some simple trait for a task to define its name + #[cfg(not(target_arch = "wasm32"))] -pub(crate) fn spawn_future(future: F) +#[track_caller] +pub fn spawn_future(future: F) where F: Future + Send + 'static, F::Output: Send + 'static, { tokio::spawn(future); } + +#[cfg(not(target_arch = "wasm32"))] +#[track_caller] +pub fn spawn_named_future(future: F, name: &str) +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + cfg_if::cfg_if! {if #[cfg(tokio_unstable)] { + #[allow(clippy::expect_used)] + tokio::task::Builder::new().name(name).spawn(future).expect("failed to spawn future"); + } else { + let _ = name; + tracing::debug!(r#"the underlying binary hasn't been built with `RUSTFLAGS="--cfg tokio_unstable"` - the future naming won't do anything"#); + spawn_future(future); + }} +} + +#[macro_export] +macro_rules! spawn_future { + ($future:expr) => {{ + $crate::spawn_future($future) + }}; + ($future:expr, $name:expr) => {{ + cfg_if::cfg_if! {if #[cfg(not(target_arch = "wasm32"))] { + $crate::spawn_named_future($future, $name) + } else { + let _ = $name; + $crate::spawn_future($future) + }} + }}; +} diff --git a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs index 4a39c37b87..6edde091ce 100644 --- a/common/client-core/surb-storage/src/backend/fs_backend/manager.rs +++ b/common/client-core/surb-storage/src/backend/fs_backend/manager.rs @@ -37,7 +37,7 @@ impl StorageManager { .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) .synchronous(SqliteSynchronous::Normal) .auto_vacuum(SqliteAutoVacuum::Incremental) - .filename(&database_path) + .filename(database_path) .create_if_missing(fresh) .disable_statement_logging(); @@ -49,8 +49,7 @@ impl StorageManager { } }; - let connection_pool = - SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool); + let connection_pool = SqlitePoolGuard::new(connection_pool); if let Err(err) = sqlx::migrate!("./fs_surbs_migrations") .run(&*connection_pool) diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 1136952e62..31b7014f64 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -6,16 +6,18 @@ use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG}; use async_trait::async_trait; use nym_api_requests::ecash::models::{ AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse, - BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse, - IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse, - IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse, - IssuedTicketbooksForResponse, VerifyEcashTicketBody, + BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashSignerStatusResponse, + EcashTicketVerificationResponse, IssuedTicketbooksChallengeCommitmentRequest, + IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksDataRequest, + IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse, + VerifyEcashTicketBody, }; use nym_api_requests::ecash::VerificationKeyResponse; use nym_api_requests::models::{ - AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse, - KeyRotationInfoResponse, LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, - NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, + AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse, + ChainStatusResponse, KeyRotationInfoResponse, LegacyDescribedMixNode, NodePerformanceResponse, + NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, + SignerInformationResponse, }; use nym_api_requests::nym_nodes::{ NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1, @@ -1331,6 +1333,22 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn get_chain_blocks_status(&self) -> Result { + self.get_json("/v1/network/chain-blocks-status", NO_PARAMS) + .await + } + + #[instrument(level = "debug", skip(self))] + async fn get_signer_status(&self) -> Result { + self.get_json("/v1/ecash/signer-status", NO_PARAMS).await + } + + #[instrument(level = "debug", skip(self))] + async fn get_signer_information(&self) -> Result { + self.get_json("/v1/api-status/signer-information", NO_PARAMS) + .await + } + #[instrument(level = "debug", skip(self))] async fn get_key_rotation_info(&self) -> Result { self.get_json( diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs index 8674d7cb0a..49b5e56cbe 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/dkg_query_client.rs @@ -8,11 +8,11 @@ use crate::nyxd::CosmWasmClient; use async_trait::async_trait; use cosmrs::AccountId; use cosmwasm_std::Addr; +use nym_coconut_dkg_common::dealer::RegisteredDealerDetails; use nym_coconut_dkg_common::types::{ChunkIndex, NodeIndex, StateAdvanceResponse}; use serde::Deserialize; use tracing::trace; -use nym_coconut_dkg_common::dealer::RegisteredDealerDetails; pub use nym_coconut_dkg_common::{ dealer::{DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse}, dealing::{ @@ -21,7 +21,9 @@ pub use nym_coconut_dkg_common::{ }, msg::QueryMsg as DkgQueryMsg, types::{DealerDetails, DealingIndex, Epoch, EpochId, EpochState, State}, - verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse}, + verification_key::{ + ContractVKShare, PagedVKSharesResponse, VerificationKeyShare, VkShareResponse, + }, }; #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] @@ -41,6 +43,11 @@ pub trait DkgQueryClient { self.query_dkg_contract(request).await } + async fn get_epoch_at_height(&self, height: u64) -> Result, NyxdError> { + let request = DkgQueryMsg::GetEpochStateAtHeight { height }; + self.query_dkg_contract(request).await + } + async fn can_advance_state(&self) -> Result { let request = DkgQueryMsg::CanAdvanceState {}; self.query_dkg_contract(request).await @@ -87,6 +94,34 @@ pub trait DkgQueryClient { self.query_dkg_contract(request).await } + async fn get_epoch_dealers_paged( + &self, + epoch_id: EpochId, + start_after: Option, + limit: Option, + ) -> Result { + let request = DkgQueryMsg::GetEpochDealers { + epoch_id, + start_after, + limit, + }; + self.query_dkg_contract(request).await + } + + async fn get_epoch_dealers_addresses_paged( + &self, + epoch_id: EpochId, + start_after: Option, + limit: Option, + ) -> Result { + let request = DkgQueryMsg::GetEpochDealersAddresses { + epoch_id, + start_after, + limit, + }; + self.query_dkg_contract(request).await + } + async fn get_dealer_indices_paged( &self, start_after: Option, @@ -208,6 +243,20 @@ pub trait PagedDkgQueryClient: DkgQueryClient { collect_paged!(self, get_current_dealers_paged, dealers) } + async fn get_all_epoch_dealers( + &self, + epoch_id: EpochId, + ) -> Result, NyxdError> { + collect_paged!(self, get_epoch_dealers_paged, dealers, epoch_id) + } + + async fn get_all_epoch_dealers_addresses( + &self, + epoch_id: EpochId, + ) -> Result, NyxdError> { + collect_paged!(self, get_epoch_dealers_addresses_paged, dealers, epoch_id) + } + async fn get_all_dealer_indices(&self) -> Result, NyxdError> { collect_paged!(self, get_dealer_indices_paged, indices) } @@ -257,6 +306,9 @@ mod tests { match msg { DkgQueryMsg::GetState {} => client.get_state().ignore(), DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(), + DkgQueryMsg::GetEpochStateAtHeight { height } => { + client.get_epoch_at_height(height).ignore() + } DkgQueryMsg::CanAdvanceState {} => client.can_advance_state().ignore(), DkgQueryMsg::GetCurrentEpochThreshold {} => { client.get_current_epoch_threshold().ignore() @@ -276,6 +328,20 @@ mod tests { DkgQueryMsg::GetCurrentDealers { limit, start_after } => client .get_current_dealers_paged(start_after, limit) .ignore(), + QueryMsg::GetEpochDealers { + epoch_id, + limit, + start_after, + } => client + .get_epoch_dealers_paged(epoch_id, start_after, limit) + .ignore(), + QueryMsg::GetEpochDealersAddresses { + epoch_id, + limit, + start_after, + } => client + .get_epoch_dealers_addresses_paged(epoch_id, start_after, limit) + .ignore(), DkgQueryMsg::GetDealerIndices { limit, start_after } => { client.get_dealer_indices_paged(start_after, limit).ignore() } diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 80dcf01fb0..85b08ab12a 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -139,12 +139,22 @@ impl NyxdClient { }) } + pub fn connect_with_network_details( + endpoint: U, + network_details: NymNetworkDetails, + ) -> Result + where + U: TryInto, + { + let config = Config::try_from_nym_network_details(&network_details)?; + Self::connect(config, endpoint) + } + pub fn connect_to_default_env(endpoint: U) -> Result where U: TryInto, { - let config = Config::try_from_nym_network_details(&NymNetworkDetails::new_from_env())?; - Self::connect(config, endpoint) + Self::connect_with_network_details(endpoint, NymNetworkDetails::new_from_env()) } } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs index 2c0b1746eb..2b74c0c7a7 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/dealer.rs @@ -55,6 +55,14 @@ impl DealerDetailsResponse { } } +#[cw_serde] +pub struct PagedDealerAddressesResponse { + pub dealers: Vec, + + /// Field indicating paging information for the following queries if the caller wishes to get further entries. + pub start_next_after: Option, +} + #[cw_serde] pub struct PagedDealerResponse { pub dealers: Vec, diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs index f9d1dc0bb0..151fb2f8bc 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/msg.rs @@ -12,8 +12,8 @@ use cosmwasm_schema::cw_serde; #[cfg(feature = "schema")] use crate::{ dealer::{ - DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse, - RegisteredDealerDetails, + DealerDetailsResponse, PagedDealerAddressesResponse, PagedDealerIndexResponse, + PagedDealerResponse, RegisteredDealerDetails, }, dealing::{ DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse, @@ -84,6 +84,9 @@ pub enum QueryMsg { #[cfg_attr(feature = "schema", returns(Epoch))] GetCurrentEpochState {}, + #[cfg_attr(feature = "schema", returns(Option))] + GetEpochStateAtHeight { height: u64 }, + #[cfg_attr(feature = "schema", returns(u64))] GetCurrentEpochThreshold {}, @@ -102,6 +105,20 @@ pub enum QueryMsg { #[cfg_attr(feature = "schema", returns(DealerDetailsResponse))] GetDealerDetails { dealer_address: String }, + #[cfg_attr(feature = "schema", returns(PagedDealerAddressesResponse))] + GetEpochDealersAddresses { + epoch_id: EpochId, + limit: Option, + start_after: Option, + }, + + #[cfg_attr(feature = "schema", returns(PagedDealerResponse))] + GetEpochDealers { + epoch_id: EpochId, + limit: Option, + start_after: Option, + }, + #[cfg_attr(feature = "schema", returns(PagedDealerResponse))] GetCurrentDealers { limit: Option, diff --git a/common/credential-storage/src/persistent_storage/mod.rs b/common/credential-storage/src/persistent_storage/mod.rs index 3c057532ad..35df10c318 100644 --- a/common/credential-storage/src/persistent_storage/mod.rs +++ b/common/credential-storage/src/persistent_storage/mod.rs @@ -63,7 +63,7 @@ impl PersistentStorage { .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) .synchronous(SqliteSynchronous::Normal) .auto_vacuum(SqliteAutoVacuum::Incremental) - .filename(&database_path) + .filename(database_path) .create_if_missing(true) .disable_statement_logging(); @@ -75,8 +75,7 @@ impl PersistentStorage { } }; - let connection_pool = - SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool); + let connection_pool = SqlitePoolGuard::new(connection_pool); if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await { error!("Failed to perform migration on the SQLx database: {err}"); diff --git a/common/credential-verification/Cargo.toml b/common/credential-verification/Cargo.toml index a86b7da382..e85589eaae 100644 --- a/common/credential-verification/Cargo.toml +++ b/common/credential-verification/Cargo.toml @@ -11,9 +11,11 @@ rust-version.workspace = true readme.workspace = true [dependencies] +async-trait = { workspace = true } bs58 = { workspace = true } cosmwasm-std = { workspace = true } cw-utils = { workspace = true } +dyn-clone = { workspace = true } futures = { workspace = true } rand = { workspace = true } si-scale = { workspace = true } diff --git a/common/credential-verification/src/bandwidth_storage_manager.rs b/common/credential-verification/src/bandwidth_storage_manager.rs index 5d51091346..17f89a59fb 100644 --- a/common/credential-verification/src/bandwidth_storage_manager.rs +++ b/common/credential-verification/src/bandwidth_storage_manager.rs @@ -7,25 +7,36 @@ use crate::ClientBandwidth; use nym_credentials::ecash::utils::ecash_today; use nym_credentials_interface::Bandwidth; use nym_gateway_requests::ServerResponse; -use nym_gateway_storage::GatewayStorage; +use nym_gateway_storage::traits::BandwidthGatewayStorage; use si_scale::helpers::bibytes2; use time::OffsetDateTime; use tracing::*; const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB -#[derive(Clone)] pub struct BandwidthStorageManager { - pub(crate) storage: GatewayStorage, + pub(crate) storage: Box, pub(crate) client_bandwidth: ClientBandwidth, pub(crate) client_id: i64, pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig, pub(crate) only_coconut_credentials: bool, } +impl Clone for BandwidthStorageManager { + fn clone(&self) -> Self { + Self { + storage: dyn_clone::clone_box(&*self.storage), + client_bandwidth: self.client_bandwidth.clone(), + client_id: self.client_id, + bandwidth_cfg: self.bandwidth_cfg, + only_coconut_credentials: self.only_coconut_credentials, + } + } +} + impl BandwidthStorageManager { pub fn new( - storage: GatewayStorage, + storage: Box, client_bandwidth: ClientBandwidth, client_id: i64, bandwidth_cfg: BandwidthFlushingBehaviourConfig, diff --git a/common/credential-verification/src/ecash/mod.rs b/common/credential-verification/src/ecash/mod.rs index 71d0d993ff..107ac45299 100644 --- a/common/credential-verification/src/ecash/mod.rs +++ b/common/credential-verification/src/ecash/mod.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::Error; +use async_trait::async_trait; use credential_sender::CredentialHandler; use credential_sender::CredentialHandlerConfig; use error::EcashTicketError; use futures::channel::mpsc::{self, UnboundedSender}; use nym_credentials::CredentialSpendingData; use nym_credentials_interface::{ClientTicket, CompactEcashError, NymPayInfo, VerificationKeyAuth}; +use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_gateway_storage::GatewayStorage; use nym_validator_client::nym_api::EpochId; use nym_validator_client::DirectSigningHttpRpcNyxdClient; @@ -20,6 +22,7 @@ pub mod credential_sender; pub mod error; mod helpers; mod state; +pub mod traits; pub const TIME_RANGE_SEC: i64 = 30; @@ -31,44 +34,21 @@ pub struct EcashManager { cred_sender: UnboundedSender, } -impl EcashManager { - pub async fn new( - credential_handler_cfg: CredentialHandlerConfig, - nyxd_client: DirectSigningHttpRpcNyxdClient, - pk_bytes: [u8; 32], - shutdown: nym_task::TaskClient, - storage: GatewayStorage, - ) -> Result { - let shared_state = SharedState::new(nyxd_client, storage).await?; - - let (cred_sender, cred_receiver) = mpsc::unbounded(); - - let cs = - CredentialHandler::new(credential_handler_cfg, cred_receiver, shared_state.clone()) - .await?; - cs.start(shutdown); - - Ok(EcashManager { - shared_state, - pk_bytes, - pay_infos: Default::default(), - cred_sender, - }) - } - - pub async fn verification_key( +#[async_trait] +impl traits::EcashManager for EcashManager { + async fn verification_key( &self, epoch_id: EpochId, ) -> Result, EcashTicketError> { self.shared_state.verification_key(epoch_id).await } - pub fn storage(&self) -> &GatewayStorage { - &self.shared_state.storage + fn storage(&self) -> Box { + dyn_clone::clone_box(&*self.shared_state.storage) } //Check for duplicate pay_info, then check the payment, then insert pay_info if everything succeeded - pub async fn check_payment( + async fn check_payment( &self, credential: &CredentialSpendingData, aggregated_verification_key: &VerificationKeyAuth, @@ -88,6 +68,40 @@ impl EcashManager { .await } + fn async_verify(&self, ticket: ClientTicket) { + // TODO: I guess do something for shutdowns + let _ = self + .cred_sender + .unbounded_send(ticket) + .inspect_err(|_| error!("failed to send the client ticket for verification task")); + } +} + +impl EcashManager { + pub async fn new( + credential_handler_cfg: CredentialHandlerConfig, + nyxd_client: DirectSigningHttpRpcNyxdClient, + pk_bytes: [u8; 32], + shutdown: nym_task::TaskClient, + storage: GatewayStorage, + ) -> Result { + let shared_state = SharedState::new(nyxd_client, Box::new(storage)).await?; + + let (cred_sender, cred_receiver) = mpsc::unbounded(); + + let cs = + CredentialHandler::new(credential_handler_cfg, cred_receiver, shared_state.clone()) + .await?; + cs.start(shutdown); + + Ok(EcashManager { + shared_state, + pk_bytes, + pay_infos: Default::default(), + cred_sender, + }) + } + pub async fn verify_pay_info(&self, pay_info: NymPayInfo) -> Result { //Public key check if pay_info.pk() != self.pk_bytes { @@ -152,12 +166,86 @@ impl EcashManager { inner.insert(index, pay_info); Ok(()) } +} - pub fn async_verify(&self, ticket: ClientTicket) { - // TODO: I guess do something for shutdowns - let _ = self - .cred_sender - .unbounded_send(ticket) - .inspect_err(|_| error!("failed to send the client ticket for verification task")); +pub struct MockEcashManager { + verfication_key: tokio::sync::RwLock, + storage: Box, +} + +impl MockEcashManager { + pub fn new(storage: Box) -> Self { + Self { + verfication_key: tokio::sync::RwLock::new( + VerificationKeyAuth::from_bytes(&[ + 129, 187, 76, 12, 1, 51, 46, 26, 132, 205, 148, 109, 140, 131, 50, 119, 45, + 128, 51, 218, 106, 70, 181, 74, 244, 38, 162, 62, 42, 12, 5, 100, 7, 136, 32, + 155, 18, 219, 195, 182, 3, 56, 168, 16, 93, 154, 249, 230, 16, 202, 90, 134, + 246, 25, 98, 6, 175, 215, 188, 239, 71, 84, 66, 1, 43, 66, 197, 180, 216, 80, + 55, 185, 140, 216, 14, 48, 244, 214, 20, 68, 106, 41, 48, 252, 188, 181, 231, + 170, 23, 211, 215, 12, 91, 147, 47, 7, 4, 0, 0, 0, 0, 0, 0, 0, 174, 31, 237, + 215, 159, 183, 71, 125, 90, 147, 84, 78, 49, 216, 66, 232, 92, 206, 41, 230, + 239, 209, 211, 166, 131, 190, 148, 36, 225, 194, 146, 6, 120, 34, 194, 5, 154, + 155, 234, 41, 191, 119, 227, 51, 91, 128, 151, 240, 129, 208, 253, 171, 234, + 170, 71, 139, 251, 78, 49, 35, 218, 16, 77, 150, 177, 204, 83, 210, 67, 147, + 66, 162, 58, 25, 96, 168, 61, 180, 92, 21, 18, 78, 194, 98, 176, 123, 122, 176, + 81, 150, 187, 20, 64, 69, 0, 134, 142, 3, 84, 108, 3, 55, 107, 111, 73, 31, 46, + 51, 225, 248, 202, 173, 194, 24, 104, 96, 31, 61, 24, 140, 220, 31, 176, 200, + 30, 217, 66, 58, 11, 181, 158, 196, 179, 199, 177, 7, 210, 4, 119, 142, 149, + 59, 3, 186, 145, 27, 230, 125, 230, 246, 197, 196, 119, 70, 239, 115, 99, 215, + 63, 205, 63, 74, 108, 201, 42, 226, 150, 137, 3, 157, 45, 25, 163, 54, 107, + 153, 61, 141, 64, 207, 139, 41, 203, 39, 36, 97, 181, 72, 206, 235, 221, 178, + 171, 60, 4, 6, 170, 181, 213, 10, 216, 53, 28, 32, 33, 41, 224, 60, 247, 206, + 137, 108, 251, 229, 234, 112, 65, 145, 124, 212, 125, 116, 154, 114, 2, 125, + 202, 24, 25, 196, 219, 104, 200, 131, 133, 180, 39, 21, 144, 204, 8, 151, 218, + 99, 64, 209, 47, 5, 42, 13, 214, 139, 54, 112, 224, 53, 238, 250, 56, 42, 105, + 15, 21, 238, 99, 225, 79, 121, 104, 155, 230, 243, 133, 47, 39, 147, 98, 45, + 113, 137, 200, 102, 151, 122, 174, 9, 250, 17, 138, 191, 129, 202, 244, 107, + 75, 48, 141, 136, 89, 168, 124, 88, 174, 251, 17, 35, 146, 88, 76, 134, 102, + 105, 204, 16, 176, 214, 63, 13, 170, 225, 250, 112, 7, 237, 161, 160, 15, 71, + 10, 130, 137, 69, 186, 64, 223, 188, 5, 5, 228, 57, 214, 134, 247, 20, 171, + 140, 43, 230, 57, 29, 127, 136, 169, 80, 14, 137, 130, 200, 205, 222, 81, 143, + 40, 77, 68, 197, 91, 142, 91, 84, 164, 15, 133, 242, 149, 255, 173, 201, 108, + 208, 23, 188, 230, 158, 146, 54, 198, 52, 148, 123, 202, 52, 222, 50, 4, 62, + 211, 208, 176, 61, 104, 151, 227, 192, 224, 200, 132, 53, 187, 240, 254, 150, + 60, 30, 140, 11, 63, 71, 12, 30, 233, 255, 144, 250, 16, 81, 38, 33, 9, 185, + 195, 214, 0, 119, 117, 94, 100, 103, 144, 10, 189, 65, 113, 114, 192, 11, 177, + 214, 223, 218, 36, 139, 183, 2, 206, 247, 245, 88, 62, 231, 183, 50, 46, 95, + 202, 152, 82, 244, 80, 173, 192, 147, 51, 248, 46, 181, 194, 205, 233, 67, 144, + 155, 250, 142, 124, 71, 9, 136, 142, 88, 29, 99, 222, 43, 181, 172, 120, 187, + 179, 172, 240, 231, 57, 236, 195, 158, 182, 203, 19, 49, 220, 180, 212, 101, + 105, 239, 58, 215, 0, 50, 100, 172, 29, 236, 170, 108, 129, 150, 5, 64, 238, + 59, 50, 4, 21, 131, 197, 142, 191, 76, 101, 140, 133, 112, 38, 235, 113, 203, + 22, 161, 204, 84, 73, 125, 219, 70, 62, 67, 119, 52, 130, 208, 180, 231, 78, + 141, 181, 13, 207, 196, 126, 159, 70, 34, 195, 70, + ]) + .unwrap(), + ), + storage: dyn_clone::clone_box(&*storage), + } } } + +#[async_trait] +impl traits::EcashManager for MockEcashManager { + async fn verification_key( + &self, + _epoch_id: EpochId, + ) -> Result, EcashTicketError> { + Ok(self.verfication_key.read().await) + } + + fn storage(&self) -> Box { + dyn_clone::clone_box(&*self.storage) + } + + async fn check_payment( + &self, + _credential: &CredentialSpendingData, + _aggregated_verification_key: &VerificationKeyAuth, + ) -> Result<(), EcashTicketError> { + Ok(()) + } + + fn async_verify(&self, _ticket: ClientTicket) {} +} diff --git a/common/credential-verification/src/ecash/state.rs b/common/credential-verification/src/ecash/state.rs index 2c937fd692..c16db2111f 100644 --- a/common/credential-verification/src/ecash/state.rs +++ b/common/credential-verification/src/ecash/state.rs @@ -6,7 +6,7 @@ use crate::Error; use cosmwasm_std::{from_json, CosmosMsg, WasmMsg}; use nym_credentials_interface::VerificationKeyAuth; use nym_ecash_contract_common::msg::ExecuteMsg; -use nym_gateway_storage::GatewayStorage; +use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_validator_client::coconut::all_ecash_api_clients; use nym_validator_client::nym_api::EpochId; use nym_validator_client::nyxd::contract_traits::{ @@ -22,18 +22,28 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tracing::{error, trace, warn}; // state shared by different subtasks dealing with credentials -#[derive(Clone)] pub(crate) struct SharedState { pub(crate) nyxd_client: Arc>, pub(crate) address: AccountId, pub(crate) epoch_data: Arc>>, - pub(crate) storage: GatewayStorage, + pub(crate) storage: Box, +} + +impl Clone for SharedState { + fn clone(&self) -> Self { + Self { + nyxd_client: self.nyxd_client.clone(), + address: self.address.clone(), + epoch_data: self.epoch_data.clone(), + storage: dyn_clone::clone_box(&*self.storage), + } + } } impl SharedState { pub(crate) async fn new( nyxd_client: DirectSigningHttpRpcNyxdClient, - storage: GatewayStorage, + storage: Box, ) -> Result { let address = nyxd_client.address(); diff --git a/common/credential-verification/src/ecash/traits.rs b/common/credential-verification/src/ecash/traits.rs new file mode 100644 index 0000000000..a7e8197681 --- /dev/null +++ b/common/credential-verification/src/ecash/traits.rs @@ -0,0 +1,23 @@ +use async_trait::async_trait; +use nym_credentials::CredentialSpendingData; +use nym_credentials_interface::{ClientTicket, VerificationKeyAuth}; +use nym_gateway_storage::traits::BandwidthGatewayStorage; +use nym_validator_client::nym_api::EpochId; +use tokio::sync::RwLockReadGuard; + +use crate::ecash::error::EcashTicketError; + +#[async_trait] +pub trait EcashManager { + async fn verification_key( + &self, + epoch_id: EpochId, + ) -> Result, EcashTicketError>; + fn storage(&self) -> Box; + async fn check_payment( + &self, + credential: &CredentialSpendingData, + aggregated_verification_key: &VerificationKeyAuth, + ) -> Result<(), EcashTicketError>; + fn async_verify(&self, ticket: ClientTicket); +} diff --git a/common/credential-verification/src/lib.rs b/common/credential-verification/src/lib.rs index 50d6db1e05..2164c25514 100644 --- a/common/credential-verification/src/lib.rs +++ b/common/credential-verification/src/lib.rs @@ -1,8 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::ecash::traits::EcashManager; use bandwidth_storage_manager::BandwidthStorageManager; -use ecash::EcashManager; use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime}; use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType}; use nym_gateway_requests::models::CredentialSpendingRequest; @@ -20,14 +20,14 @@ pub mod error; pub struct CredentialVerifier { credential: CredentialSpendingRequest, - ecash_verifier: Arc, + ecash_verifier: Arc, bandwidth_storage_manager: BandwidthStorageManager, } impl CredentialVerifier { pub fn new( credential: CredentialSpendingRequest, - ecash_verifier: Arc, + ecash_verifier: Arc, bandwidth_storage_manager: BandwidthStorageManager, ) -> Self { CredentialVerifier { diff --git a/common/ecash-signer-check-types/Cargo.toml b/common/ecash-signer-check-types/Cargo.toml new file mode 100644 index 0000000000..0f07437c71 --- /dev/null +++ b/common/ecash-signer-check-types/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "nym-ecash-signer-check-types" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +semver = { workspace = true } +serde = { workspace = true, features = ["derive"] } +url = { workspace = true } +thiserror = { workspace = true } +time = { workspace = true } +tracing = { workspace = true } +utoipa = { workspace = true } + +nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" } +nym-crypto = { path = "../crypto", features = ["asymmetric"] } + + +[lints] +workspace = true diff --git a/common/ecash-signer-check-types/src/dealer_information.rs b/common/ecash-signer-check-types/src/dealer_information.rs new file mode 100644 index 0000000000..b589fecb10 --- /dev/null +++ b/common/ecash-signer-check-types/src/dealer_information.rs @@ -0,0 +1,97 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_coconut_dkg_common::dealer::DealerDetails; +use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use url::Url; +use utoipa::ToSchema; + +#[derive(Debug, Error)] +pub enum MalformedDealer { + #[error("dealer at {dealer_url} has provided invalid ed25519 pubkey: {source}")] + InvalidDealerPubkey { + dealer_url: String, + source: Ed25519RecoveryError, + }, + + #[error("dealer at {dealer_url} has provided invalid announce url: {source}")] + InvalidDealerAddress { + dealer_url: String, + source: url::ParseError, + }, +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct RawDealerInformation { + pub announce_address: String, + pub owner_address: String, + pub node_index: u64, + pub public_key: String, + pub verification_key_share: Option, + pub share_verified: bool, +} + +impl RawDealerInformation { + pub fn new( + dealer_details: &DealerDetails, + contract_share: Option<&ContractVKShare>, + ) -> RawDealerInformation { + RawDealerInformation { + announce_address: dealer_details.announce_address.clone(), + owner_address: dealer_details.address.to_string(), + node_index: dealer_details.assigned_index, + public_key: dealer_details.ed25519_identity.clone(), + verification_key_share: contract_share.map(|s| s.share.clone()), + share_verified: contract_share.map(|s| s.verified).unwrap_or(false), + } + } + + pub fn parse(&self) -> Result { + Ok(DealerInformation { + announce_address: self.announce_address.parse().map_err(|source| { + MalformedDealer::InvalidDealerAddress { + dealer_url: self.announce_address.clone(), + source, + } + })?, + owner_address: self.owner_address.clone(), + node_index: self.node_index, + public_key: self.public_key.parse().map_err(|source| { + MalformedDealer::InvalidDealerPubkey { + dealer_url: self.announce_address.clone(), + source, + } + })?, + verification_key_share: self.verification_key_share.clone(), + share_verified: self.share_verified, + }) + } +} + +#[derive(Debug)] +pub struct DealerInformation { + pub announce_address: Url, + pub owner_address: String, + pub node_index: u64, + pub public_key: ed25519::PublicKey, + // no need to parse it into the full type as it doesn't get us anything + pub verification_key_share: Option, + pub share_verified: bool, +} + +impl From for RawDealerInformation { + fn from(d: DealerInformation) -> Self { + RawDealerInformation { + announce_address: d.announce_address.to_string(), + owner_address: d.owner_address, + node_index: d.node_index, + public_key: d.public_key.to_base58_string(), + verification_key_share: d.verification_key_share, + share_verified: d.share_verified, + } + } +} diff --git a/common/ecash-signer-check-types/src/helper_traits.rs b/common/ecash-signer-check-types/src/helper_traits.rs new file mode 100644 index 0000000000..c6d6becd33 --- /dev/null +++ b/common/ecash-signer-check-types/src/helper_traits.rs @@ -0,0 +1,127 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::VerificationKeyShare; +use nym_crypto::asymmetric::ed25519; +use std::time::Duration; +use time::OffsetDateTime; +use tracing::{debug, warn}; + +pub trait Verifiable { + fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool; +} + +pub trait TimestampedResponse { + fn timestamp(&self) -> OffsetDateTime; +} + +pub trait LegacyChainResponse { + fn chain_synced(&self, now: OffsetDateTime, stall_threshold: Duration) -> bool; +} + +pub trait ChainResponse: Verifiable + TimestampedResponse { + fn chain_synced(&self) -> bool; + + fn chain_available( + &self, + pub_key: &ed25519::PublicKey, + now: OffsetDateTime, + stale_response_threshold: Duration, + ) -> bool { + if !self.verify_signature(pub_key) { + warn!("failed signature verification on chain status response"); + return false; + } + + // we rely on information provided from the api itself AS LONG AS it's not too outdated + if self.timestamp() + stale_response_threshold < now { + return false; + } + self.chain_synced() + } +} + +pub trait LegacySignerResponse { + fn signer_identity(&self) -> &str; + + fn signer_verification_key(&self) -> &Option; + + fn unprovable_signing_available( + &self, + pub_key: &ed25519::PublicKey, + expected_verification_key: Option, + share_verified: bool, + ) -> bool { + if self.signer_identity() != pub_key.to_base58_string() { + warn!("mismatched identity key on the legacy response"); + return false; + } + + // the contract share hasn't been verified yet, so we're probably in the middle of DKG + // thus if there's a bit of desync in the state, it's fine + if !share_verified { + return true; + } + + if self.signer_verification_key() != &expected_verification_key { + warn!("mismatched [ecash] verification key on the legacy response"); + return false; + } + + true + } +} + +pub trait SignerResponse: Verifiable + TimestampedResponse { + fn has_signing_keys(&self) -> bool; + + fn signer_disabled(&self) -> bool; + + fn is_ecash_signer(&self) -> bool; + + fn dkg_ecash_epoch_id(&self) -> EpochId; + + fn provable_signing_available( + &self, + pub_key: &ed25519::PublicKey, + dkg_epoch_id: EpochId, + now: OffsetDateTime, + stale_response_threshold: Duration, + ) -> bool { + if !self.verify_signature(pub_key) { + warn!("failed signature verification on chain status response"); + return false; + } + + // we rely on information provided from the api itself AS LONG AS it's not too outdated + if self.timestamp() + stale_response_threshold < now { + return false; + } + + if !self.has_signing_keys() { + debug!("missing signing keys"); + return false; + } + + if self.signer_disabled() { + debug!("signer functionalities explicitly disabled"); + return false; + } + + if !self.is_ecash_signer() { + debug!("signer doesn't recognise it's a signer for this epoch"); + return false; + } + + if dkg_epoch_id != self.dkg_ecash_epoch_id() { + debug!( + "mismatched dkg epoch id. current: {dkg_epoch_id}, signer's: {}", + self.dkg_ecash_epoch_id() + ); + return false; + } + + true + } +} diff --git a/common/ecash-signer-check-types/src/lib.rs b/common/ecash-signer-check-types/src/lib.rs new file mode 100644 index 0000000000..60e5ccd667 --- /dev/null +++ b/common/ecash-signer-check-types/src/lib.rs @@ -0,0 +1,6 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod dealer_information; +pub mod helper_traits; +pub mod status; diff --git a/common/ecash-signer-check-types/src/status.rs b/common/ecash-signer-check-types/src/status.rs new file mode 100644 index 0000000000..2a506d6c96 --- /dev/null +++ b/common/ecash-signer-check-types/src/status.rs @@ -0,0 +1,303 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::dealer_information::RawDealerInformation; +use crate::helper_traits::{ + ChainResponse, LegacyChainResponse, LegacySignerResponse, SignerResponse, +}; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_dkg_common::verification_key::VerificationKeyShare; +use nym_crypto::asymmetric::ed25519; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use time::OffsetDateTime; +use utoipa::ToSchema; + +pub(crate) const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60); +pub(crate) const STALE_RESPONSE_THRESHOLD: Duration = Duration::from_secs(5 * 60); + +// the reason for generics is not to remove duplication of code, +// but because without them, we'd be having problems with circular dependencies, +// i.e. nym-api-requests depending on ecash-signer-check-types and +// ecash-signer-check-types needing nym-api-requests +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub enum Status { + /// The API, even though it reports correct version, did not response to the status query + Unreachable, + + /// The API is running an outdated version that does not expose the required endpoint + Outdated, + + /// Response to the legacy (unsigned) status query + ReachableLegacy { response: Box }, + + /// Response to the current (signed) status query + Reachable { response: Box }, +} + +impl Status +where + L: LegacyChainResponse, + T: ChainResponse, +{ + pub fn chain_available(&self, pub_key: ed25519::PublicKey) -> bool { + let now = OffsetDateTime::now_utc(); + + match self { + Status::Unreachable | Status::Outdated => false, + Status::ReachableLegacy { response } => { + response.chain_synced(now, CHAIN_STALL_THRESHOLD) + } + Status::Reachable { response } => { + response.chain_available(&pub_key, now, STALE_RESPONSE_THRESHOLD) + } + } + } + + pub fn chain_provably_stalled(&self, pub_key: ed25519::PublicKey) -> bool { + let now = OffsetDateTime::now_utc(); + + match self { + Status::Unreachable | Status::Outdated | Status::ReachableLegacy { .. } => false, + Status::Reachable { response } => { + !response.chain_available(&pub_key, now, STALE_RESPONSE_THRESHOLD) + } + } + } + + pub fn chain_unprovably_stalled(&self) -> bool { + let now = OffsetDateTime::now_utc(); + + match self { + Status::Unreachable | Status::Outdated | Status::Reachable { .. } => false, + Status::ReachableLegacy { response } => { + !response.chain_synced(now, CHAIN_STALL_THRESHOLD) + } + } + } +} + +impl Status +where + L: LegacySignerResponse, + T: SignerResponse, +{ + pub fn signing_available( + &self, + pub_key: ed25519::PublicKey, + dkg_epoch_id: u64, + expected_verification_key: Option, + share_verified: bool, + ) -> bool { + let now = OffsetDateTime::now_utc(); + + match self { + Status::Unreachable | Status::Outdated => false, + Status::ReachableLegacy { response } => response.unprovable_signing_available( + &pub_key, + expected_verification_key, + share_verified, + ), + Status::Reachable { response } => response.provable_signing_available( + &pub_key, + dkg_epoch_id, + now, + STALE_RESPONSE_THRESHOLD, + ), + } + } + + pub fn signing_provably_unavailable( + &self, + pub_key: ed25519::PublicKey, + dkg_epoch_id: EpochId, + ) -> bool { + let now = OffsetDateTime::now_utc(); + + match self { + Status::Unreachable | Status::Outdated | Status::ReachableLegacy { .. } => false, + Status::Reachable { response } => !response.provable_signing_available( + &pub_key, + dkg_epoch_id, + now, + STALE_RESPONSE_THRESHOLD, + ), + } + } + + pub fn signing_unprovably_unavailable( + &self, + pub_key: ed25519::PublicKey, + expected_verification_key: Option, + share_verified: bool, + ) -> bool { + match self { + Status::Unreachable | Status::Outdated | Status::Reachable { .. } => false, + Status::ReachableLegacy { response } => !response.unprovable_signing_available( + &pub_key, + expected_verification_key, + share_verified, + ), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct SignerResult { + pub dkg_epoch_id: u64, + pub information: RawDealerInformation, + pub status: SignerStatus, +} + +impl SignerResult { + pub fn signer_unreachable(&self) -> bool { + matches!(self.status, SignerStatus::Unreachable) + } + + pub fn malformed_details(&self) -> bool { + self.information.parse().is_err() + } +} + +impl SignerResult +where + LC: LegacyChainResponse, + TC: ChainResponse, +{ + pub fn unknown_chain_status(&self) -> bool { + let Ok(_) = self.information.parse() else { + return true; + }; + if let SignerStatus::Tested { .. } = &self.status { + return false; + } + true + } + + pub fn chain_available(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + result + .local_chain_status + .chain_available(parsed_info.public_key) + } + + pub fn chain_provably_stalled(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + + result + .local_chain_status + .chain_provably_stalled(parsed_info.public_key) + } + + pub fn chain_unprovably_stalled(&self) -> bool { + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + + result.local_chain_status.chain_unprovably_stalled() + } +} + +impl SignerResult +where + LS: LegacySignerResponse, + TS: SignerResponse, +{ + pub fn unknown_signing_status(&self) -> bool { + let Ok(_) = self.information.parse() else { + return true; + }; + if let SignerStatus::Tested { .. } = &self.status { + return false; + } + true + } + + pub fn signing_available(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + result.signing_status.signing_available( + parsed_info.public_key, + self.dkg_epoch_id, + parsed_info.verification_key_share, + parsed_info.share_verified, + ) + } + + pub fn signing_provably_unavailable(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + + result + .signing_status + .signing_provably_unavailable(parsed_info.public_key, self.dkg_epoch_id) + } + + pub fn signing_unprovably_unavailable(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + + result.signing_status.signing_unprovably_unavailable( + parsed_info.public_key, + parsed_info.verification_key_share, + parsed_info.share_verified, + ) + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub enum SignerStatus { + Unreachable, + ProvidedInvalidDetails, + Tested { + result: SignerTestResult, + }, +} + +impl SignerStatus { + pub fn with_details( + self, + information: impl Into, + dkg_epoch_id: u64, + ) -> SignerResult { + SignerResult { + dkg_epoch_id, + status: self, + information: information.into(), + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] +pub struct SignerTestResult { + pub reported_version: String, + pub signing_status: Status, + pub local_chain_status: Status, +} diff --git a/common/ecash-signer-check/Cargo.toml b/common/ecash-signer-check/Cargo.toml new file mode 100644 index 0000000000..be22872ad8 --- /dev/null +++ b/common/ecash-signer-check/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "nym-ecash-signer-check" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +futures = { workspace = true } +thiserror = { workspace = true } +semver = { workspace = true } +tokio = { workspace = true, features = ["time"] } +tracing = { workspace = true } +url = { workspace = true } + + +nym-validator-client = { path = "../client-libs/validator-client" } +nym-network-defaults = { path = "../network-defaults" } +nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" } + +[lints] +workspace = true diff --git a/common/ecash-signer-check/src/client_check.rs b/common/ecash-signer-check/src/client_check.rs new file mode 100644 index 0000000000..b5c367794d --- /dev/null +++ b/common/ecash-signer-check/src/client_check.rs @@ -0,0 +1,225 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::{LocalChainStatus, SigningStatus, TypedSignerResult}; +use nym_ecash_signer_check_types::dealer_information::RawDealerInformation; +use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult}; +use nym_validator_client::client::NymApiClientExt; +use nym_validator_client::models::BinaryBuildInformationOwned; +use nym_validator_client::nyxd::contract_traits::dkg_query_client::{ + ContractVKShare, DealerDetails, +}; +use nym_validator_client::NymApiClient; +use std::time::Duration; +use tracing::{error, warn}; +use url::Url; + +pub(crate) mod chain_status { + + // Dorina + pub(crate) const MINIMUM_VERSION_LEGACY: semver::Version = semver::Version::new(1, 1, 51); + + // Gruyere + pub(crate) const MINIMUM_VERSION: semver::Version = semver::Version::new(1, 1, 64); +} + +pub(crate) mod signing_status { + // Magura (possibly earlier) + pub(crate) const MINIMUM_LEGACY_VERSION: semver::Version = semver::Version::new(1, 1, 46); + + // Gruyere + pub(crate) const MINIMUM_VERSION: semver::Version = semver::Version::new(1, 1, 64); +} + +struct ClientUnderTest { + api_client: NymApiClient, + build_info: Option, +} + +impl ClientUnderTest { + pub(crate) fn new(api_url: &Url) -> Self { + ClientUnderTest { + api_client: NymApiClient::new(api_url.clone()), + build_info: None, + } + } + + pub(crate) async fn try_retrieve_build_information(&mut self) -> bool { + match tokio::time::timeout( + Duration::from_secs(5), + self.api_client.nym_api.build_information(), + ) + .await + { + Ok(Ok(build_information)) => { + self.build_info = Some(build_information); + true + } + Ok(Err(err)) => { + warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url()); + false + } + Err(_timeout) => { + warn!( + "{}: timed out while attempting to retrieve build information", + self.api_client.api_url() + ); + false + } + } + } + + pub(crate) fn version(&self) -> Option { + self.build_info.as_ref().and_then(|build_info| { + build_info + .build_version + .parse() + .inspect_err(|err| { + error!( + "ecash signer '{}' reports invalid version {}: {err}", + self.api_client.api_url(), + build_info.build_version + ) + }) + .ok() + }) + } + + pub(crate) fn supports_legacy_signing_status_query(&self) -> bool { + let Some(version) = self.version() else { + return false; + }; + version >= signing_status::MINIMUM_LEGACY_VERSION + } + + pub(crate) fn supports_signing_status_query(&self) -> bool { + let Some(version) = self.version() else { + return false; + }; + version >= signing_status::MINIMUM_VERSION + } + + pub(crate) fn supports_chain_status_query(&self) -> bool { + let Some(version) = self.version() else { + return false; + }; + version >= chain_status::MINIMUM_VERSION + } + + pub(crate) fn supports_legacy_chain_status_query(&self) -> bool { + let Some(version) = self.version() else { + return false; + }; + version >= chain_status::MINIMUM_VERSION_LEGACY + } + + pub(crate) async fn check_local_chain(&self) -> LocalChainStatus { + // check if it at least supports legacy query + if !self.supports_legacy_chain_status_query() { + return LocalChainStatus::Outdated; + } + + // check if it supports the current query + if self.supports_chain_status_query() { + return match self.api_client.nym_api.get_chain_blocks_status().await { + Ok(status) => LocalChainStatus::Reachable { + response: Box::new(status), + }, + Err(err) => { + warn!( + "{}: failed to retrieve local chain status: {err}", + self.api_client.api_url() + ); + LocalChainStatus::Unreachable + } + }; + } + + // fallback to the legacy query + match self.api_client.nym_api.get_chain_status().await { + Ok(status) => LocalChainStatus::ReachableLegacy { + response: Box::new(status), + }, + Err(err) => { + warn!( + "{}: failed to retrieve [legacy] local chain status: {err}", + self.api_client.api_url() + ); + LocalChainStatus::Unreachable + } + } + } + + pub(crate) async fn check_signing_status(&self) -> SigningStatus { + // check if it at least supports legacy query + if !self.supports_legacy_signing_status_query() { + return SigningStatus::Outdated; + } + + // check if it supports the current query + if self.supports_signing_status_query() { + return match self.api_client.nym_api.get_signer_status().await { + Ok(response) => SigningStatus::Reachable { + response: Box::new(response), + }, + Err(err) => { + warn!( + "{}: failed to retrieve signer chain status: {err}", + self.api_client.api_url() + ); + SigningStatus::Unreachable + } + }; + } + + // fallback to the legacy query + match self.api_client.nym_api.get_signer_information().await { + Ok(status) => SigningStatus::ReachableLegacy { + response: Box::new(status), + }, + Err(err) => { + warn!( + "{}: failed to retrieve [legacy] signer chain status: {err}", + self.api_client.api_url() + ); + // NOTE: this might equally mean the signing is disabled + SigningStatus::Unreachable + } + } + } +} + +pub(crate) async fn check_client( + dealer_details: DealerDetails, + dkg_epoch: u64, + contract_share: Option<&ContractVKShare>, +) -> TypedSignerResult { + let dealer_information = RawDealerInformation::new(&dealer_details, contract_share); + + // 7. attempt to construct client instances out of them + let Ok(parsed_information) = dealer_information.parse() else { + return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch); + }; + + let mut client = ClientUnderTest::new(&parsed_information.announce_address); + + // 8. check basic connection status - can you retrieve build information? + if !client.try_retrieve_build_information().await { + return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch); + } + + // 9. check perceived chain status + let local_chain_status = client.check_local_chain().await; + + // 10. check signer status + let signing_status = client.check_signing_status().await; + + SignerStatus::Tested { + result: SignerTestResult { + reported_version: client.version().map(|v| v.to_string()).unwrap_or_default(), + signing_status, + local_chain_status, + }, + } + .with_details(dealer_information, dkg_epoch) +} diff --git a/common/ecash-signer-check/src/dealer_information.rs b/common/ecash-signer-check/src/dealer_information.rs new file mode 100644 index 0000000000..73f13cc489 --- /dev/null +++ b/common/ecash-signer-check/src/dealer_information.rs @@ -0,0 +1,80 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::SignerCheckError; +use nym_crypto::asymmetric::ed25519; +use nym_validator_client::nyxd::contract_traits::dkg_query_client::{ + ContractVKShare, DealerDetails, VerificationKeyShare, +}; +use url::Url; + +#[derive(Debug)] +pub struct RawDealerInformation { + pub announce_address: String, + pub owner_address: String, + pub node_index: u64, + pub public_key: String, + pub verification_key_share: Option, + pub share_verified: bool, +} + +impl RawDealerInformation { + pub fn new( + dealer_details: &DealerDetails, + contract_share: Option<&ContractVKShare>, + ) -> RawDealerInformation { + RawDealerInformation { + announce_address: dealer_details.announce_address.clone(), + owner_address: dealer_details.address.to_string(), + node_index: dealer_details.assigned_index, + public_key: dealer_details.ed25519_identity.clone(), + verification_key_share: contract_share.map(|s| s.share.clone()), + share_verified: contract_share.map(|s| s.verified).unwrap_or(false), + } + } + + pub fn parse(&self) -> Result { + Ok(DealerInformation { + announce_address: self.announce_address.parse().map_err(|source| { + SignerCheckError::InvalidDealerAddress { + dealer_url: self.announce_address.clone(), + source, + } + })?, + owner_address: self.owner_address.clone(), + node_index: self.node_index, + public_key: self.announce_address.parse().map_err(|source| { + SignerCheckError::InvalidDealerPubkey { + dealer_url: self.announce_address.clone(), + source, + } + })?, + verification_key_share: self.verification_key_share.clone(), + share_verified: self.share_verified, + }) + } +} + +#[derive(Debug)] +pub struct DealerInformation { + pub announce_address: Url, + pub owner_address: String, + pub node_index: u64, + pub public_key: ed25519::PublicKey, + // no need to parse it into the full type as it doesn't get us anything + pub verification_key_share: Option, + pub share_verified: bool, +} + +impl From for RawDealerInformation { + fn from(d: DealerInformation) -> Self { + RawDealerInformation { + announce_address: d.announce_address.to_string(), + owner_address: d.owner_address, + node_index: d.node_index, + public_key: d.public_key.to_base58_string(), + verification_key_share: d.verification_key_share, + share_verified: d.share_verified, + } + } +} diff --git a/common/ecash-signer-check/src/error.rs b/common/ecash-signer-check/src/error.rs new file mode 100644 index 0000000000..bb7c3c719b --- /dev/null +++ b/common/ecash-signer-check/src/error.rs @@ -0,0 +1,24 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_validator_client::nyxd::error::NyxdError; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum SignerCheckError { + #[error("failed to connect to nyxd chain due to invalid connection details: {source}")] + InvalidNyxdConnectionDetails { source: NyxdError }, + + #[error("failed to query the DKG contract: {source}")] + DKGContractQueryFailure { source: NyxdError }, +} + +impl SignerCheckError { + pub fn invalid_nyxd_connection_details(source: NyxdError) -> Self { + Self::InvalidNyxdConnectionDetails { source } + } + + pub fn dkg_contract_query_failure(source: NyxdError) -> Self { + Self::DKGContractQueryFailure { source } + } +} diff --git a/common/ecash-signer-check/src/lib.rs b/common/ecash-signer-check/src/lib.rs new file mode 100644 index 0000000000..2915119a27 --- /dev/null +++ b/common/ecash-signer-check/src/lib.rs @@ -0,0 +1,94 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::client_check::check_client; +use futures::stream::{FuturesUnordered, StreamExt}; +use nym_network_defaults::NymNetworkDetails; +use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient}; +use nym_validator_client::QueryHttpRpcNyxdClient; +use std::collections::HashMap; +use url::Url; + +pub use error::SignerCheckError; +use nym_ecash_signer_check_types::status::{SignerResult, Status}; +use nym_validator_client::ecash::models::EcashSignerStatusResponse; +use nym_validator_client::models::{ + ChainBlocksStatusResponse, ChainStatusResponse, SignerInformationResponse, +}; + +mod client_check; +pub mod error; + +pub type TypedSignerResult = SignerResult< + SignerInformationResponse, + EcashSignerStatusResponse, + ChainStatusResponse, + ChainBlocksStatusResponse, +>; +pub type LocalChainStatus = Status; +pub type SigningStatus = Status; + +pub struct SignersTestResult { + pub threshold: Option, + pub results: Vec, +} + +pub async fn check_signers( + rpc_endpoint: Url, + // details such as denoms, prefixes, etc. + network_details: NymNetworkDetails, +) -> Result { + // 1. create nyx client instance + let client = QueryHttpRpcNyxdClient::connect_with_network_details( + rpc_endpoint.as_str(), + network_details, + ) + .map_err(SignerCheckError::invalid_nyxd_connection_details)?; + + check_signers_with_client(&client).await +} + +pub async fn check_signers_with_client(client: &C) -> Result +where + C: DkgQueryClient + Sync, +{ + // 2. retrieve current dkg epoch + let dkg_epoch = client + .get_current_epoch() + .await + .map_err(SignerCheckError::dkg_contract_query_failure)?; + + // 3. retrieve the dkg threshold as reference point + let threshold = client + .get_epoch_threshold(dkg_epoch.epoch_id) + .await + .map_err(SignerCheckError::dkg_contract_query_failure)?; + + // 4. retrieve information on current DKG dealers (i.e. eligible signers) + let dealers = client + .get_all_current_dealers() + .await + .map_err(SignerCheckError::dkg_contract_query_failure)?; + + // 5. retrieve their published keys (if available) + let shares: HashMap<_, _> = client + .get_all_verification_key_shares(dkg_epoch.epoch_id) + .await + .map_err(SignerCheckError::dkg_contract_query_failure)? + .into_iter() + .map(|share| (share.node_index, share)) + .collect(); + + // 6. for each dealer attempt to perform the checks + let results = dealers + .into_iter() + .map(|d| { + let share = shares.get(&d.assigned_index); + check_client(d, dkg_epoch.epoch_id, share) + }) + .collect::>() + .collect::>() + .await; + + Ok(SignersTestResult { threshold, results }) +} diff --git a/common/ecash-signer-check/src/status.rs b/common/ecash-signer-check/src/status.rs new file mode 100644 index 0000000000..a75a86448c --- /dev/null +++ b/common/ecash-signer-check/src/status.rs @@ -0,0 +1,73 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::chain_status::LocalChainStatus; +use crate::dealer_information::RawDealerInformation; +use crate::signing_status::SigningStatus; +use std::time::Duration; + +pub(crate) const STALE_RESPONSE_THRESHOLD: Duration = Duration::from_secs(5 * 60); + +#[derive(Debug)] +pub struct SignerResult { + pub dkg_epoch_id: u64, + pub information: RawDealerInformation, + pub status: SignerStatus, +} + +impl SignerResult { + pub fn chain_available(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + result.local_chain_status.available(parsed_info.public_key) + } + + pub fn signer_available(&self) -> bool { + let Ok(parsed_info) = self.information.parse() else { + return false; + }; + let SignerStatus::Tested { result } = &self.status else { + return false; + }; + + result.signing_status.available( + parsed_info.public_key, + self.dkg_epoch_id, + parsed_info.verification_key_share, + parsed_info.share_verified, + ) + } +} + +#[derive(Debug)] +pub enum SignerStatus { + Unreachable, + ProvidedInvalidDetails, + Tested { result: SignerTestResult }, +} + +impl SignerStatus { + pub fn with_details( + self, + information: impl Into, + dkg_epoch_id: u64, + ) -> SignerResult { + SignerResult { + dkg_epoch_id, + status: self, + information: information.into(), + } + } +} + +#[derive(Debug)] +pub struct SignerTestResult { + pub reported_version: String, + pub signing_status: SigningStatus, + pub local_chain_status: LocalChainStatus, +} diff --git a/common/gateway-storage/Cargo.toml b/common/gateway-storage/Cargo.toml index 810697f258..4ead02b939 100644 --- a/common/gateway-storage/Cargo.toml +++ b/common/gateway-storage/Cargo.toml @@ -9,8 +9,10 @@ edition.workspace = true license.workspace = true [dependencies] +async-trait = { workspace = true } bincode = { workspace = true } defguard_wireguard_rs = { workspace = true } +dyn-clone = { workspace = true } sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", @@ -21,6 +23,7 @@ sqlx = { workspace = true, features = [ ] } time = { workspace = true } thiserror = { workspace = true } +tokio = { workspace = true, features = ["sync"], optional = true } tracing = { workspace = true } nym-credentials-interface = { path = "../credentials-interface" } @@ -35,3 +38,7 @@ sqlx = { workspace = true, features = [ "macros", "migrate", ] } + +[features] +default = [] +mock = ["tokio"] \ No newline at end of file diff --git a/common/gateway-storage/migrations/20250605120000_trim_wireguard_peer_data.sql b/common/gateway-storage/migrations/20250605120000_trim_wireguard_peer_data.sql new file mode 100644 index 0000000000..cd627850bf --- /dev/null +++ b/common/gateway-storage/migrations/20250605120000_trim_wireguard_peer_data.sql @@ -0,0 +1,19 @@ +/* + * Copyright 2025 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +DELETE FROM wireguard_peer WHERE client_id IS NULL; + +CREATE TABLE wireguard_peer_new +( + public_key TEXT NOT NULL PRIMARY KEY UNIQUE, + allowed_ips BLOB NOT NULL, + client_id INTEGER REFERENCES clients(id) NOT NULL +); + +INSERT INTO wireguard_peer_new (public_key, allowed_ips, client_id) +SELECT public_key, allowed_ips, client_id FROM wireguard_peer; + +DROP TABLE wireguard_peer; +ALTER TABLE wireguard_peer_new RENAME TO wireguard_peer; \ No newline at end of file diff --git a/common/gateway-storage/src/clients.rs b/common/gateway-storage/src/clients.rs index 8bee61e04b..83365f8cfd 100644 --- a/common/gateway-storage/src/clients.rs +++ b/common/gateway-storage/src/clients.rs @@ -3,6 +3,8 @@ use std::str::FromStr; +use nym_credentials_interface::TicketType; + use crate::models::Client; #[derive(Debug, PartialEq, sqlx::Type)] @@ -15,6 +17,17 @@ pub enum ClientType { ExitWireguard, } +impl From for ClientType { + fn from(value: TicketType) -> Self { + match value { + TicketType::V1MixnetEntry => ClientType::EntryMixnet, + TicketType::V1MixnetExit => ClientType::ExitMixnet, + TicketType::V1WireguardEntry => ClientType::EntryWireguard, + TicketType::V1WireguardExit => ClientType::ExitWireguard, + } + } +} + impl FromStr for ClientType { type Err = &'static str; diff --git a/common/gateway-storage/src/error.rs b/common/gateway-storage/src/error.rs index 272d86b557..2600451751 100644 --- a/common/gateway-storage/src/error.rs +++ b/common/gateway-storage/src/error.rs @@ -20,6 +20,18 @@ pub enum GatewayStorageError { #[error("the stored data associated with ticket {ticket_id} is malformed!")] MalformedStoredTicketData { ticket_id: i64 }, - #[error("Failed to convert from type of database: {0}")] - TypeConversion(String), + #[error("Failed to convert from type of database: {field_key}")] + TypeConversion { field_key: &'static str }, + + #[error("Serialization failure for {field_key}")] + Serialize { + field_key: &'static str, + source: bincode::Error, + }, + + #[error("Deserialization failure for {field_key}")] + Deserialize { + field_key: &'static str, + source: bincode::Error, + }, } diff --git a/common/gateway-storage/src/lib.rs b/common/gateway-storage/src/lib.rs index ea534b8c1b..e10fdc3c1e 100644 --- a/common/gateway-storage/src/lib.rs +++ b/common/gateway-storage/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only +use async_trait::async_trait; use bandwidth::BandwidthManager; use clients::{ClientManager, ClientType}; use models::{ @@ -15,10 +16,10 @@ use sqlx::{ sqlite::{SqliteAutoVacuum, SqliteSynchronous}, ConnectOptions, }; -use std::path::Path; +use std::{path::Path, time::Duration}; use tickets::TicketStorageManager; use time::OffsetDateTime; -use tracing::{debug, error}; +use tracing::{debug, error, log::LevelFilter}; pub mod bandwidth; mod clients; @@ -27,11 +28,21 @@ mod inboxes; pub mod models; mod shared_keys; mod tickets; +pub mod traits; mod wireguard_peers; pub use error::GatewayStorageError; pub use inboxes::InboxManager; +use crate::traits::{BandwidthGatewayStorage, InboxGatewayStorage, SharedKeyGatewayStorage}; + +fn make_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} + // note that clone here is fine as upon cloning the same underlying pool will be used #[derive(Clone)] pub struct GatewayStorage { @@ -71,6 +82,21 @@ impl GatewayStorage { &self.wireguard_peer_manager } + pub async fn handle_forget_me( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), GatewayStorageError> { + let client_id = self.get_mixnet_client_id(client_address).await?; + self.inbox_manager() + .remove_messages_for_client(&client_address.as_base58_string()) + .await?; + self.bandwidth_manager().remove_client(client_id).await?; + self.shared_key_manager() + .remove_shared_keys(&client_address.as_base58_string()) + .await?; + Ok(()) + } + /// Initialises `PersistentStorage` using the provided path. /// /// # Arguments @@ -92,6 +118,7 @@ impl GatewayStorage { .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) .synchronous(SqliteSynchronous::Normal) .auto_vacuum(SqliteAutoVacuum::Incremental) + .log_slow_statements(LevelFilter::Warn, Duration::from_millis(250)) .filename(database_path) .create_if_missing(true) .disable_statement_logging(); @@ -123,8 +150,9 @@ impl GatewayStorage { } } -impl GatewayStorage { - pub async fn get_mixnet_client_id( +#[async_trait] +impl SharedKeyGatewayStorage for GatewayStorage { + async fn get_mixnet_client_id( &self, client_address: DestinationAddressBytes, ) -> Result { @@ -134,22 +162,7 @@ impl GatewayStorage { .await?) } - pub async fn handle_forget_me( - &self, - client_address: DestinationAddressBytes, - ) -> Result<(), GatewayStorageError> { - let client_id = self.get_mixnet_client_id(client_address).await?; - self.inbox_manager() - .remove_messages_for_client(&client_address.as_base58_string()) - .await?; - self.bandwidth_manager().remove_client(client_id).await?; - self.shared_key_manager() - .remove_shared_keys(&client_address.as_base58_string()) - .await?; - Ok(()) - } - - pub async fn insert_shared_keys( + async fn insert_shared_keys( &self, client_address: DestinationAddressBytes, shared_keys: &SharedGatewayKey, @@ -178,7 +191,7 @@ impl GatewayStorage { Ok(client_id) } - pub async fn get_shared_keys( + async fn get_shared_keys( &self, client_address: DestinationAddressBytes, ) -> Result, GatewayStorageError> { @@ -190,7 +203,7 @@ impl GatewayStorage { } #[allow(dead_code)] - pub async fn remove_shared_keys( + async fn remove_shared_keys( &self, client_address: DestinationAddressBytes, ) -> Result<(), GatewayStorageError> { @@ -200,7 +213,7 @@ impl GatewayStorage { Ok(()) } - pub async fn update_last_used_authentication_timestamp( + async fn update_last_used_authentication_timestamp( &self, client_id: i64, last_used_authentication_timestamp: OffsetDateTime, @@ -214,12 +227,15 @@ impl GatewayStorage { Ok(()) } - pub async fn get_client(&self, client_id: i64) -> Result, GatewayStorageError> { + async fn get_client(&self, client_id: i64) -> Result, GatewayStorageError> { let client = self.client_manager.get_client(client_id).await?; Ok(client) } +} - pub async fn store_message( +#[async_trait] +impl InboxGatewayStorage for GatewayStorage { + async fn store_message( &self, client_address: DestinationAddressBytes, message: Vec, @@ -230,7 +246,7 @@ impl GatewayStorage { Ok(()) } - pub async fn retrieve_messages( + async fn retrieve_messages( &self, client_address: DestinationAddressBytes, start_after: Option, @@ -242,19 +258,22 @@ impl GatewayStorage { Ok(messages) } - pub async fn remove_messages(&self, ids: Vec) -> Result<(), GatewayStorageError> { + async fn remove_messages(&self, ids: Vec) -> Result<(), GatewayStorageError> { for id in ids { self.inbox_manager.remove_message(id).await?; } Ok(()) } +} - pub async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> { +#[async_trait] +impl BandwidthGatewayStorage for GatewayStorage { + async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> { self.bandwidth_manager.insert_new_client(client_id).await?; Ok(()) } - pub async fn set_expiration( + async fn set_expiration( &self, client_id: i64, expiration: OffsetDateTime, @@ -265,12 +284,12 @@ impl GatewayStorage { Ok(()) } - pub async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> { + async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> { self.bandwidth_manager.reset_bandwidth(client_id).await?; Ok(()) } - pub async fn get_available_bandwidth( + async fn get_available_bandwidth( &self, client_id: i64, ) -> Result, GatewayStorageError> { @@ -280,7 +299,7 @@ impl GatewayStorage { .await?) } - pub async fn increase_bandwidth( + async fn increase_bandwidth( &self, client_id: i64, amount: i64, @@ -291,7 +310,7 @@ impl GatewayStorage { .await?) } - pub async fn revoke_ticket_bandwidth( + async fn revoke_ticket_bandwidth( &self, ticket_id: i64, amount: i64, @@ -302,7 +321,7 @@ impl GatewayStorage { .await?) } - pub async fn decrease_bandwidth( + async fn decrease_bandwidth( &self, client_id: i64, amount: i64, @@ -313,7 +332,7 @@ impl GatewayStorage { .await?) } - pub async fn insert_epoch_signers( + async fn insert_epoch_signers( &self, epoch_id: i64, signer_ids: Vec, @@ -324,7 +343,7 @@ impl GatewayStorage { Ok(()) } - pub async fn insert_received_ticket( + async fn insert_received_ticket( &self, client_id: i64, received_at: OffsetDateTime, @@ -344,11 +363,11 @@ impl GatewayStorage { Ok(ticket_id) } - pub async fn contains_ticket(&self, serial_number: &[u8]) -> Result { + async fn contains_ticket(&self, serial_number: &[u8]) -> Result { Ok(self.ticket_manager.has_ticket_data(serial_number).await?) } - pub async fn insert_ticket_verification( + async fn insert_ticket_verification( &self, ticket_id: i64, signer_id: i64, @@ -361,7 +380,7 @@ impl GatewayStorage { Ok(()) } - pub async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { + async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { // set the ticket as rejected self.ticket_manager.set_rejected_ticket(ticket_id).await?; @@ -372,7 +391,7 @@ impl GatewayStorage { Ok(()) } - pub async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { + async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { // 1. insert into verified table self.ticket_manager .insert_verified_ticket(ticket_id) @@ -386,7 +405,7 @@ impl GatewayStorage { Ok(()) } - pub async fn remove_verified_ticket_binary_data( + async fn remove_verified_ticket_binary_data( &self, ticket_id: i64, ) -> Result<(), GatewayStorageError> { @@ -396,7 +415,7 @@ impl GatewayStorage { Ok(()) } - pub async fn get_all_verified_tickets_with_sn( + async fn get_all_verified_tickets_with_sn( &self, ) -> Result, GatewayStorageError> { Ok(self @@ -405,7 +424,7 @@ impl GatewayStorage { .await?) } - pub async fn get_all_proposed_tickets_with_sn( + async fn get_all_proposed_tickets_with_sn( &self, proposal_id: u32, ) -> Result, GatewayStorageError> { @@ -415,7 +434,7 @@ impl GatewayStorage { .await?) } - pub async fn insert_redemption_proposal( + async fn insert_redemption_proposal( &self, tickets: &[VerifiedTicket], proposal_id: u32, @@ -438,7 +457,7 @@ impl GatewayStorage { Ok(()) } - pub async fn clear_post_proposal_data( + async fn clear_post_proposal_data( &self, proposal_id: u32, resolved_at: OffsetDateTime, @@ -462,13 +481,11 @@ impl GatewayStorage { Ok(()) } - pub async fn latest_proposal(&self) -> Result, GatewayStorageError> { + async fn latest_proposal(&self) -> Result, GatewayStorageError> { Ok(self.ticket_manager.get_latest_redemption_proposal().await?) } - pub async fn get_all_unverified_tickets( - &self, - ) -> Result, GatewayStorageError> { + async fn get_all_unverified_tickets(&self) -> Result, GatewayStorageError> { self.ticket_manager .get_unverified_tickets() .await? @@ -477,21 +494,21 @@ impl GatewayStorage { .collect() } - pub async fn get_all_unresolved_proposals(&self) -> Result, GatewayStorageError> { + async fn get_all_unresolved_proposals(&self) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_all_unresolved_redemption_proposal_ids() .await?) } - pub async fn get_votes(&self, ticket_id: i64) -> Result, GatewayStorageError> { + async fn get_votes(&self, ticket_id: i64) -> Result, GatewayStorageError> { Ok(self .ticket_manager .get_verification_votes(ticket_id) .await?) } - pub async fn get_signers(&self, epoch_id: i64) -> Result, GatewayStorageError> { + async fn get_signers(&self, epoch_id: i64) -> Result, GatewayStorageError> { Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?) } @@ -500,34 +517,20 @@ impl GatewayStorage { /// # Arguments /// /// * `peer`: wireguard peer data to be stored - /// * `with_client_id`: if the peer should have a corresponding client_id - /// (created with entry wireguard ticket) or live without one (or with an - /// exiting one), for temporary backwards compatibility. - pub async fn insert_wireguard_peer( + async fn insert_wireguard_peer( &self, peer: &defguard_wireguard_rs::host::Peer, - with_client_id: bool, - ) -> Result, GatewayStorageError> { + client_type: ClientType, + ) -> Result { let client_id = match self .wireguard_peer_manager .retrieve_peer(&peer.public_key.to_string()) .await? { Some(peer) => peer.client_id, - _ => { - if with_client_id { - Some( - self.client_manager - .insert_client(ClientType::EntryWireguard) - .await?, - ) - } else { - None - } - } + None => self.client_manager.insert_client(client_type).await?, }; - let mut peer = WireguardPeer::from(peer.clone()); - peer.client_id = client_id; + let peer = WireguardPeer::from_defguard_peer(peer.clone(), client_id)?; self.wireguard_peer_manager.insert_peer(&peer).await?; Ok(client_id) } @@ -537,7 +540,7 @@ impl GatewayStorage { /// # Arguments /// /// * `peer_public_key`: wireguard public key of the peer to be retrieved. - pub async fn get_wireguard_peer( + async fn get_wireguard_peer( &self, peer_public_key: &str, ) -> Result, GatewayStorageError> { @@ -549,7 +552,7 @@ impl GatewayStorage { } /// Retrieves all wireguard peers. - pub async fn get_all_wireguard_peers(&self) -> Result, GatewayStorageError> { + async fn get_all_wireguard_peers(&self) -> Result, GatewayStorageError> { let ret = self.wireguard_peer_manager.retrieve_all_peers().await?; Ok(ret) } @@ -559,7 +562,7 @@ impl GatewayStorage { /// # Arguments /// /// * `peer_public_key`: wireguard public key of the peer to be removed. - pub async fn remove_wireguard_peer( + async fn remove_wireguard_peer( &self, peer_public_key: &str, ) -> Result<(), GatewayStorageError> { diff --git a/common/gateway-storage/src/models.rs b/common/gateway-storage/src/models.rs index 531ed355de..32b12b80a5 100644 --- a/common/gateway-storage/src/models.rs +++ b/common/gateway-storage/src/models.rs @@ -1,9 +1,7 @@ // Copyright 2021-2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use std::time::SystemTime; - -use crate::error::GatewayStorageError; +use crate::{error::GatewayStorageError, make_bincode_serializer}; use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpendingData}; use nym_gateway_requests::shared_key::{LegacySharedKeys, SharedGatewayKey, SharedSymmetricKey}; use sqlx::FromRow; @@ -112,35 +110,24 @@ impl TryFrom for ClientTicket { #[derive(Debug, Clone, FromRow)] pub struct WireguardPeer { pub public_key: String, - pub preshared_key: Option, - pub protocol_version: Option, - pub endpoint: Option, - pub last_handshake: Option, - pub tx_bytes: i64, - pub rx_bytes: i64, - pub persistent_keepalive_interval: Option, pub allowed_ips: Vec, - pub client_id: Option, + pub client_id: i64, } -impl From for WireguardPeer { - fn from(value: defguard_wireguard_rs::host::Peer) -> Self { - WireguardPeer { +impl WireguardPeer { + pub fn from_defguard_peer( + value: defguard_wireguard_rs::host::Peer, + client_id: i64, + ) -> Result { + Ok(WireguardPeer { public_key: value.public_key.to_string(), - preshared_key: value.preshared_key.as_ref().map(|k| k.to_string()), - protocol_version: value.protocol_version.map(|v| v as i64), - endpoint: value.endpoint.map(|e| e.to_string()), - last_handshake: value.last_handshake.map(OffsetDateTime::from), - tx_bytes: value.tx_bytes as i64, - rx_bytes: value.rx_bytes as i64, - persistent_keepalive_interval: value.persistent_keepalive_interval.map(|v| v as i64), - allowed_ips: bincode::Options::serialize( - bincode::DefaultOptions::new(), - &value.allowed_ips, - ) - .unwrap_or_default(), - client_id: None, - } + allowed_ips: bincode::Options::serialize(make_bincode_serializer(), &value.allowed_ips) + .map_err(|source| crate::error::GatewayStorageError::Serialize { + field_key: "allowed_ips", + source, + })?, + client_id, + }) } } @@ -149,49 +136,20 @@ impl TryFrom for defguard_wireguard_rs::host::Peer { fn try_from(value: WireguardPeer) -> Result { Ok(Self { - public_key: value - .public_key - .as_str() - .try_into() - .map_err(|e| Self::Error::TypeConversion(format!("public key {e}")))?, - preshared_key: value - .preshared_key - .as_deref() - .map(TryFrom::try_from) - .transpose() - .map_err(|e| Self::Error::TypeConversion(format!("preshared key {e}")))?, - protocol_version: value - .protocol_version - .map(TryFrom::try_from) - .transpose() - .map_err(|e| Self::Error::TypeConversion(format!("protocol version {e}")))?, - endpoint: value - .endpoint - .as_deref() - .map(|e| e.parse()) - .transpose() - .map_err(|e| Self::Error::TypeConversion(format!("endpoint {e}")))?, - last_handshake: value.last_handshake.map(SystemTime::from), - tx_bytes: value - .tx_bytes - .try_into() - .map_err(|e| Self::Error::TypeConversion(format!("tx bytes {e}")))?, - rx_bytes: value - .rx_bytes - .try_into() - .map_err(|e| Self::Error::TypeConversion(format!("rx bytes {e}")))?, - persistent_keepalive_interval: value - .persistent_keepalive_interval - .map(TryFrom::try_from) - .transpose() - .map_err(|e| { - Self::Error::TypeConversion(format!("persistent keepalive interval {e}")) - })?, + public_key: value.public_key.as_str().try_into().map_err(|_| { + Self::Error::TypeConversion { + field_key: "public_key", + } + })?, allowed_ips: bincode::Options::deserialize( bincode::DefaultOptions::new(), &value.allowed_ips, ) - .map_err(|e| Self::Error::TypeConversion(format!("allowed ips {e}")))?, + .map_err(|source| Self::Error::Deserialize { + field_key: "allowed_ips", + source, + })?, + ..Default::default() }) } } diff --git a/common/gateway-storage/src/traits.rs b/common/gateway-storage/src/traits.rs new file mode 100644 index 0000000000..aa7e01e434 --- /dev/null +++ b/common/gateway-storage/src/traits.rs @@ -0,0 +1,511 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use async_trait::async_trait; +use nym_credentials_interface::ClientTicket; +use nym_gateway_requests::SharedGatewayKey; +use nym_sphinx::DestinationAddressBytes; +use time::OffsetDateTime; + +use crate::{ + clients::ClientType, + models::{ + Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage, + VerifiedTicket, WireguardPeer, + }, + GatewayStorageError, +}; + +#[async_trait] +pub trait SharedKeyGatewayStorage { + async fn get_mixnet_client_id( + &self, + client_address: DestinationAddressBytes, + ) -> Result; + async fn insert_shared_keys( + &self, + client_address: DestinationAddressBytes, + shared_keys: &SharedGatewayKey, + ) -> Result; + async fn get_shared_keys( + &self, + client_address: DestinationAddressBytes, + ) -> Result, GatewayStorageError>; + #[allow(dead_code)] + async fn remove_shared_keys( + &self, + client_address: DestinationAddressBytes, + ) -> Result<(), GatewayStorageError>; + async fn update_last_used_authentication_timestamp( + &self, + client_id: i64, + last_used_authentication_timestamp: OffsetDateTime, + ) -> Result<(), GatewayStorageError>; + async fn get_client(&self, client_id: i64) -> Result, GatewayStorageError>; +} + +#[async_trait] +pub trait InboxGatewayStorage { + async fn store_message( + &self, + client_address: DestinationAddressBytes, + message: Vec, + ) -> Result<(), GatewayStorageError>; + async fn retrieve_messages( + &self, + client_address: DestinationAddressBytes, + start_after: Option, + ) -> Result<(Vec, Option), GatewayStorageError>; + async fn remove_messages(&self, ids: Vec) -> Result<(), GatewayStorageError>; +} + +#[async_trait] +pub trait BandwidthGatewayStorage: dyn_clone::DynClone { + async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError>; + async fn set_expiration( + &self, + client_id: i64, + expiration: OffsetDateTime, + ) -> Result<(), GatewayStorageError>; + async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError>; + async fn get_available_bandwidth( + &self, + client_id: i64, + ) -> Result, GatewayStorageError>; + async fn increase_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result; + async fn revoke_ticket_bandwidth( + &self, + ticket_id: i64, + amount: i64, + ) -> Result<(), GatewayStorageError>; + async fn decrease_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result; + + async fn insert_epoch_signers( + &self, + epoch_id: i64, + signer_ids: Vec, + ) -> Result<(), GatewayStorageError>; + async fn insert_received_ticket( + &self, + client_id: i64, + received_at: OffsetDateTime, + serial_number: Vec, + data: Vec, + ) -> Result; + async fn contains_ticket(&self, serial_number: &[u8]) -> Result; + async fn insert_ticket_verification( + &self, + ticket_id: i64, + signer_id: i64, + verified_at: OffsetDateTime, + accepted: bool, + ) -> Result<(), GatewayStorageError>; + async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError>; + async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError>; + async fn remove_verified_ticket_binary_data( + &self, + ticket_id: i64, + ) -> Result<(), GatewayStorageError>; + async fn get_all_verified_tickets_with_sn( + &self, + ) -> Result, GatewayStorageError>; + async fn get_all_proposed_tickets_with_sn( + &self, + proposal_id: u32, + ) -> Result, GatewayStorageError>; + async fn insert_redemption_proposal( + &self, + tickets: &[VerifiedTicket], + proposal_id: u32, + created_at: OffsetDateTime, + ) -> Result<(), GatewayStorageError>; + async fn clear_post_proposal_data( + &self, + proposal_id: u32, + resolved_at: OffsetDateTime, + rejected: bool, + ) -> Result<(), GatewayStorageError>; + async fn latest_proposal(&self) -> Result, GatewayStorageError>; + async fn get_all_unverified_tickets(&self) -> Result, GatewayStorageError>; + async fn get_all_unresolved_proposals(&self) -> Result, GatewayStorageError>; + async fn get_votes(&self, ticket_id: i64) -> Result, GatewayStorageError>; + async fn get_signers(&self, epoch_id: i64) -> Result, GatewayStorageError>; + + /// Insert a wireguard peer in the storage. + /// + /// # Arguments + /// + /// * `peer`: wireguard peer data to be stored + async fn insert_wireguard_peer( + &self, + peer: &defguard_wireguard_rs::host::Peer, + client_type: ClientType, + ) -> Result; + + /// Tries to retrieve a particular peer with the given public key. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be retrieved. + async fn get_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result, GatewayStorageError>; + + /// Retrieves all wireguard peers. + async fn get_all_wireguard_peers(&self) -> Result, GatewayStorageError>; + + /// Remove a wireguard peer from the storage. + /// + /// # Arguments + /// + /// * `peer_public_key`: wireguard public key of the peer to be removed. + async fn remove_wireguard_peer(&self, peer_public_key: &str) + -> Result<(), GatewayStorageError>; +} + +#[cfg(feature = "mock")] +pub mod mock { + use std::{collections::HashMap, sync::Arc}; + + use tokio::sync::RwLock; + + use super::*; + + struct EcashSigner { + _epoch_id: i64, + _signer_id: i64, + } + + struct ReceivedTicket { + client_id: i64, + _received_at: OffsetDateTime, + rejected: Option, + } + + struct TicketData { + serial_number: Vec, + data: Option>, + } + + struct TicketVerification { + _ticket_id: i64, + _signer_id: i64, + _verified_at: OffsetDateTime, + _accepted: bool, + } + + #[derive(Default)] + pub struct MockGatewayStorage { + available_bandwidth: HashMap, + ecash_signers: Vec, + received_ticket: HashMap, + ticket_data: HashMap, + ticket_verification: HashMap, + verified_tickets: Vec, + wireguard_peers: HashMap, + clients: HashMap, + } + + #[async_trait] + impl BandwidthGatewayStorage for Arc> { + async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> { + self.write().await.available_bandwidth.insert( + client_id, + PersistedBandwidth { + client_id, + available: 0, + expiration: Some(OffsetDateTime::UNIX_EPOCH), + }, + ); + Ok(()) + } + + async fn set_expiration( + &self, + client_id: i64, + expiration: OffsetDateTime, + ) -> Result<(), GatewayStorageError> { + if let Some(bw) = self.write().await.available_bandwidth.get_mut(&client_id) { + bw.expiration = Some(expiration); + } + Ok(()) + } + + async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> { + if let Some(bw) = self.write().await.available_bandwidth.get_mut(&client_id) { + bw.available = 0; + bw.expiration = Some(OffsetDateTime::UNIX_EPOCH); + } + Ok(()) + } + + async fn get_available_bandwidth( + &self, + client_id: i64, + ) -> Result, GatewayStorageError> { + Ok(self + .read() + .await + .available_bandwidth + .get(&client_id) + .cloned()) + } + + async fn increase_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result { + self.write() + .await + .available_bandwidth + .get_mut(&client_id) + .map(|bw| { + bw.available += amount; + bw.available + }) + .ok_or(GatewayStorageError::InternalDatabaseError( + sqlx::Error::RowNotFound, + )) + } + + async fn revoke_ticket_bandwidth( + &self, + ticket_id: i64, + amount: i64, + ) -> Result<(), GatewayStorageError> { + let mut guard = self.write().await; + if let Some(client_id) = guard + .received_ticket + .get(&ticket_id) + .map(|ticket| ticket.client_id) + { + if let Some(bw) = guard.available_bandwidth.get_mut(&client_id) { + bw.available -= amount; + } + } + Ok(()) + } + + async fn decrease_bandwidth( + &self, + client_id: i64, + amount: i64, + ) -> Result { + self.write() + .await + .available_bandwidth + .get_mut(&client_id) + .map(|bw| { + bw.available -= amount; + bw.available + }) + .ok_or(GatewayStorageError::InternalDatabaseError( + sqlx::Error::RowNotFound, + )) + } + + async fn insert_epoch_signers( + &self, + _epoch_id: i64, + signer_ids: Vec, + ) -> Result<(), GatewayStorageError> { + self.write() + .await + .ecash_signers + .extend(signer_ids.iter().map(|signer_id| EcashSigner { + _epoch_id, + _signer_id: *signer_id, + })); + Ok(()) + } + + async fn insert_received_ticket( + &self, + client_id: i64, + _received_at: OffsetDateTime, + serial_number: Vec, + data: Vec, + ) -> Result { + let mut guard = self.write().await; + let ticket_id = guard.received_ticket.len() as i64; + guard.received_ticket.insert( + ticket_id, + ReceivedTicket { + client_id, + _received_at, + rejected: None, + }, + ); + guard.ticket_data.insert( + ticket_id, + TicketData { + serial_number, + data: Some(data), + }, + ); + Ok(ticket_id) + } + + async fn contains_ticket(&self, serial_number: &[u8]) -> Result { + Ok(self + .read() + .await + .ticket_data + .values() + .any(|ticket_data| ticket_data.serial_number == serial_number)) + } + + async fn insert_ticket_verification( + &self, + _ticket_id: i64, + _signer_id: i64, + _verified_at: OffsetDateTime, + _accepted: bool, + ) -> Result<(), GatewayStorageError> { + self.write().await.ticket_verification.insert( + _ticket_id, + TicketVerification { + _ticket_id, + _signer_id, + _verified_at, + _accepted, + }, + ); + Ok(()) + } + + async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { + let mut guard = self.write().await; + if let Some(ticket) = guard.received_ticket.get_mut(&ticket_id) { + ticket.rejected = Some(true); + } + guard.ticket_data.remove(&ticket_id); + Ok(()) + } + + async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> { + let mut guard = self.write().await; + guard.verified_tickets.push(ticket_id); + guard.ticket_verification.remove(&ticket_id); + Ok(()) + } + + async fn remove_verified_ticket_binary_data( + &self, + ticket_id: i64, + ) -> Result<(), GatewayStorageError> { + if let Some(ticket) = self.write().await.ticket_data.get_mut(&ticket_id) { + ticket.data = None; + } + Ok(()) + } + + async fn get_all_verified_tickets_with_sn( + &self, + ) -> Result, GatewayStorageError> { + todo!() + } + + async fn get_all_proposed_tickets_with_sn( + &self, + _proposal_id: u32, + ) -> Result, GatewayStorageError> { + todo!() + } + + async fn insert_redemption_proposal( + &self, + _tickets: &[VerifiedTicket], + _proposal_id: u32, + _created_at: OffsetDateTime, + ) -> Result<(), GatewayStorageError> { + todo!() + } + + async fn clear_post_proposal_data( + &self, + _proposal_id: u32, + _resolved_at: OffsetDateTime, + _rejected: bool, + ) -> Result<(), GatewayStorageError> { + todo!() + } + + async fn latest_proposal(&self) -> Result, GatewayStorageError> { + todo!() + } + + async fn get_all_unverified_tickets( + &self, + ) -> Result, GatewayStorageError> { + todo!() + } + + async fn get_all_unresolved_proposals(&self) -> Result, GatewayStorageError> { + todo!() + } + + async fn get_votes(&self, _ticket_id: i64) -> Result, GatewayStorageError> { + todo!() + } + + async fn get_signers(&self, _epoch_id: i64) -> Result, GatewayStorageError> { + todo!() + } + + async fn insert_wireguard_peer( + &self, + peer: &defguard_wireguard_rs::host::Peer, + client_type: ClientType, + ) -> Result { + let mut guard = self.write().await; + let client_id = + if let Some(peer) = guard.wireguard_peers.get(&peer.public_key.to_string()) { + peer.client_id + } else { + let client_id = guard.clients.len() as i64; + guard.clients.insert(client_id, client_type.to_string()); + client_id + }; + guard.wireguard_peers.insert( + peer.public_key.to_string(), + WireguardPeer::from_defguard_peer(peer.clone(), client_id)?, + ); + Ok(client_id) + } + + async fn get_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result, GatewayStorageError> { + Ok(self + .read() + .await + .wireguard_peers + .get(peer_public_key) + .cloned()) + } + + async fn get_all_wireguard_peers(&self) -> Result, GatewayStorageError> { + todo!() + } + + async fn remove_wireguard_peer( + &self, + peer_public_key: &str, + ) -> Result<(), GatewayStorageError> { + self.write().await.wireguard_peers.remove(peer_public_key); + Ok(()) + } + } +} diff --git a/common/gateway-storage/src/wireguard_peers.rs b/common/gateway-storage/src/wireguard_peers.rs index 00c41483be..22bf178d99 100644 --- a/common/gateway-storage/src/wireguard_peers.rs +++ b/common/gateway-storage/src/wireguard_peers.rs @@ -27,15 +27,18 @@ impl WgPeerManager { pub(crate) async fn insert_peer(&self, peer: &WireguardPeer) -> Result<(), sqlx::Error> { sqlx::query!( r#" - INSERT OR IGNORE INTO wireguard_peer(public_key, preshared_key, protocol_version, endpoint, last_handshake, tx_bytes, rx_bytes, persistent_keepalive_interval, allowed_ips, client_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + INSERT OR IGNORE INTO wireguard_peer(public_key, allowed_ips, client_id) + VALUES (?, ?, ?); UPDATE wireguard_peer - SET preshared_key = ?, protocol_version = ?, endpoint = ?, last_handshake = ?, tx_bytes = ?, rx_bytes = ?, persistent_keepalive_interval = ?, allowed_ips = ?, client_id = ? + SET allowed_ips = ?, client_id = ? WHERE public_key = ? "#, - peer.public_key, peer.preshared_key, peer.protocol_version, peer.endpoint, peer.last_handshake, peer.tx_bytes, peer.rx_bytes, peer.persistent_keepalive_interval, peer.allowed_ips, peer.client_id, - peer.preshared_key, peer.protocol_version, peer.endpoint, peer.last_handshake, peer.tx_bytes, peer.rx_bytes, peer.persistent_keepalive_interval, peer.allowed_ips, peer.client_id, + peer.public_key, + peer.allowed_ips, + peer.client_id, + peer.allowed_ips, + peer.client_id, peer.public_key, ) .execute(&self.connection_pool) diff --git a/common/http-api-client/src/dns.rs b/common/http-api-client/src/dns.rs index eb93f71407..3c68afee1e 100644 --- a/common/http-api-client/src/dns.rs +++ b/common/http-api-client/src/dns.rs @@ -6,9 +6,9 @@ //! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints //! supporting DoH and DoT. //! -//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a +//! This resolver supports a fallback mechanism where, should the DNS-over-TLS resolution fail, a //! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on -//! linux). +//! linux). This is disabled by default and can be enabled using [`enable_system_fallback`]. //! //! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the //! `hickory-resolver` crate @@ -93,14 +93,14 @@ pub struct HickoryDnsResolver { // Tokio Runtime in initialization, so we must delay the actual // construction of the resolver. state: Arc>, - fallback: Arc>, + fallback: Option>>, dont_use_shared: bool, } impl Resolve for HickoryDnsResolver { fn resolve(&self, name: Name) -> Resolving { let resolver = self.state.clone(); - let fallback = self.fallback.clone(); + let maybe_fallback = self.fallback.clone(); let independent = self.dont_use_shared; Box::pin(async move { let resolver = resolver.get_or_try_init(|| { @@ -117,23 +117,30 @@ impl Resolve for HickoryDnsResolver { let lookup = match resolver.lookup_ip(name.as_str()).await { Ok(res) => res, Err(e) => { - // on failure use the fall back system configured DNS resolver - 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 - // lazy-init returns an error it can be handled by the client - if independent { - new_resolver_system() - } else { - Ok(SHARED_RESOLVER - .fallback - .get_or_try_init(new_resolver_system)? - .clone()) + if let Some(ref fallback) = maybe_fallback { + // on failure use the fall back system configured DNS resolver + if !e.is_no_records_found() { + warn!("primary DNS failed w/ error {e}: using system fallback"); } - })?; - resolver.lookup_ip(name.as_str()).await? + let resolver = fallback.get_or_try_init(|| { + // using a closure here is slightly gross, but this makes sure that if the + // lazy-init returns an error it can be handled by the client + if independent { + new_resolver_system() + } else { + Ok(SHARED_RESOLVER + .fallback + .as_ref() + .ok_or(e)? // if the shared resolver has no fallback return the original error + .get_or_try_init(new_resolver_system)? + .clone()) + } + })?; + + resolver.lookup_ip(name.as_str()).await? + } else { + return Err(e.into()); + } } }; @@ -162,14 +169,17 @@ impl HickoryDnsResolver { let lookup = match resolver.lookup_ip(name).await { Ok(res) => res, Err(e) => { - // on failure use the fall back system configured DNS resolver - if !e.is_no_records_found() { - warn!("primary DNS failed w/ error {e}: using system fallback"); + if let Some(ref fallback) = self.fallback { + // on failure use the fall back system configured DNS resolver + if !e.is_no_records_found() { + warn!("primary DNS failed w/ error {e}: using system fallback"); + } + + let resolver = fallback.get_or_try_init(|| self.new_resolver_system())?; + resolver.lookup_ip(name).await? + } else { + return Err(e.into()); } - let resolver = self - .fallback - .get_or_try_init(|| self.new_resolver_system())?; - resolver.lookup_ip(name).await? } }; @@ -193,15 +203,34 @@ impl HickoryDnsResolver { } fn new_resolver_system(&self) -> Result { - if self.dont_use_shared { + if self.dont_use_shared || SHARED_RESOLVER.fallback.is_none() { new_resolver_system() } else { Ok(SHARED_RESOLVER .fallback + .as_ref() + .unwrap() .get_or_try_init(new_resolver_system)? .clone()) } } + + /// Enable fallback to the system default resolver if the primary (DoX) resolver fails + pub fn enable_system_fallback(&mut self) -> Result<(), HickoryDnsError> { + self.fallback = Some(Default::default()); + let _ = self + .fallback + .as_ref() + .unwrap() + .get_or_try_init(new_resolver_system)?; + Ok(()) + } + + /// Disable fallback resolution. If the primary resolver fails the error is + /// returned immediately + pub fn disable_system_fallback(&mut self) { + self.fallback = None; + } } /// Create a new resolver with a custom DoT based configuration. The options are overridden to look diff --git a/common/http-api-client/src/user_agent.rs b/common/http-api-client/src/user_agent.rs index 54fe6ac24e..1543798a6a 100644 --- a/common/http-api-client/src/user_agent.rs +++ b/common/http-api-client/src/user_agent.rs @@ -16,7 +16,7 @@ pub struct UserAgent { pub version: String, /// client platform pub platform: String, - /// source commit version for the calling calling crate / subsystem + /// source commit version for the calling crate / subsystem pub git_commit: String, } diff --git a/common/http-api-common/src/response/mod.rs b/common/http-api-common/src/response/mod.rs index ee1a84a785..2753aaf8f0 100644 --- a/common/http-api-common/src/response/mod.rs +++ b/common/http-api-common/src/response/mod.rs @@ -146,6 +146,12 @@ pub struct OutputParams { pub output: Option, } +impl OutputParams { + pub fn get_output(&self) -> Output { + self.output.unwrap_or_default() + } +} + impl Output { pub fn to_response(self, data: T) -> FormattedResponse { match self { diff --git a/common/socks5-client-core/src/lib.rs b/common/socks5-client-core/src/lib.rs index 17140d39fc..3c7a4b658d 100644 --- a/common/socks5-client-core/src/lib.rs +++ b/common/socks5-client-core/src/lib.rs @@ -233,7 +233,7 @@ where let dkg_query_client = if self.config.base.client.disabled_credentials_mode { None } else { - Some(default_query_dkg_client_from_config(&self.config.base)) + Some(default_query_dkg_client_from_config(&self.config.base)?) }; let mut base_builder = diff --git a/common/task/src/cancellation.rs b/common/task/src/cancellation.rs index 35009cc18e..d45296caca 100644 --- a/common/task/src/cancellation.rs +++ b/common/task/src/cancellation.rs @@ -265,6 +265,7 @@ impl ShutdownManager { } #[must_use] + #[track_caller] pub fn with_shutdown(mut self, shutdown: F) -> Self where F: Future, @@ -281,6 +282,7 @@ impl ShutdownManager { } #[cfg(unix)] + #[track_caller] pub fn with_shutdown_signal(self, signal_kind: SignalKind) -> std::io::Result { let mut sig = signal(signal_kind)?; Ok(self.with_shutdown(async move { @@ -289,6 +291,7 @@ impl ShutdownManager { } #[cfg(not(target_arch = "wasm32"))] + #[track_caller] pub fn with_interrupt_signal(self) -> Self { self.with_shutdown(async move { let _ = tokio::signal::ctrl_c().await; @@ -296,11 +299,13 @@ impl ShutdownManager { } #[cfg(unix)] + #[track_caller] pub fn with_terminate_signal(self) -> std::io::Result { self.with_shutdown_signal(SignalKind::terminate()) } #[cfg(unix)] + #[track_caller] pub fn with_quit_signal(self) -> std::io::Result { self.with_shutdown_signal(SignalKind::quit()) } diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index c999861cd6..cfa82a8bda 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -11,11 +11,13 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-trait = { workspace = true } base64 = { workspace = true } bincode = { workspace = true } chrono = { workspace = true } dashmap = { workspace = true } defguard_wireguard_rs = { workspace = true } +dyn-clone = { workspace = true } futures = { workspace = true } # The latest version on crates.io at the time of writing this (6.0.0) has a # version mismatch with x25519-dalek/curve25519-dalek that is resolved in the @@ -37,3 +39,11 @@ nym-network-defaults = { path = "../network-defaults" } nym-task = { path = "../task" } nym-wireguard-types = { path = "../wireguard-types" } nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } + +[dev-dependencies] +nym-gateway-storage = { path = "../gateway-storage", features = ["mock"] } + +[features] +default = [] +mock = ["nym-gateway-storage/mock"] + diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs index d2fd0e7956..1353fba023 100644 --- a/common/wireguard/src/error.rs +++ b/common/wireguard/src/error.rs @@ -3,18 +3,18 @@ #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("traffic byte data needs to be increasing")] - InconsistentConsumedBytes, - #[error("{0}")] Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError), #[error("internal {0}")] Internal(String), - #[error("storage should have the requested bandwidht entry")] + #[error("storage should have the requested bandwidth entry")] MissingClientBandwidthEntry, + #[error("kernel should have the requested client entry: {0}")] + MissingClientKernelEntry(String), + #[error("{0}")] GatewayStorage(#[from] nym_gateway_storage::error::GatewayStorageError), diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index 6b83c648d2..2706e17982 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -6,16 +6,15 @@ // #![warn(clippy::expect_used)] // #![warn(clippy::unwrap_used)] -use defguard_wireguard_rs::WGApi; +use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi, WireguardInterfaceApi}; use nym_crypto::asymmetric::x25519::KeyPair; +#[cfg(target_os = "linux")] +use nym_gateway_storage::GatewayStorage; use nym_wireguard_types::Config; use peer_controller::PeerControlRequest; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; -#[cfg(target_os = "linux")] -use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask}; - #[cfg(target_os = "linux")] use nym_network_defaults::constants::WG_TUN_BASE_NAME; @@ -28,6 +27,81 @@ pub struct WgApiWrapper { inner: WGApi, } +impl WireguardInterfaceApi for WgApiWrapper { + fn create_interface( + &self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.create_interface() + } + + fn assign_address( + &self, + address: &IpAddrMask, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.assign_address(address) + } + + fn configure_peer_routing( + &self, + peers: &[Peer], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.configure_peer_routing(peers) + } + + #[cfg(not(target_os = "windows"))] + fn configure_interface( + &self, + config: &defguard_wireguard_rs::InterfaceConfiguration, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.configure_interface(config) + } + + #[cfg(target_os = "windows")] + fn configure_interface( + &self, + config: &defguard_wireguard_rs::InterfaceConfiguration, + dns: &[std::net::IpAddr], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.configure_interface(config, dns) + } + + fn remove_interface( + &self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.remove_interface() + } + + fn configure_peer( + &self, + peer: &Peer, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.configure_peer(peer) + } + + fn remove_peer( + &self, + peer_pubkey: &Key, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.remove_peer(peer_pubkey) + } + + fn read_interface_data( + &self, + ) -> Result< + defguard_wireguard_rs::host::Host, + defguard_wireguard_rs::error::WireguardInterfaceError, + > { + self.inner.read_interface_data() + } + + fn configure_dns( + &self, + dns: &[std::net::IpAddr], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.inner.configure_dns(dns) + } +} + impl WgApiWrapper { pub fn new(wg_api: WGApi) -> Self { WgApiWrapper { inner: wg_api } @@ -84,9 +158,9 @@ pub struct WireguardData { /// Start wireguard device #[cfg(target_os = "linux")] pub async fn start_wireguard( - storage: nym_gateway_storage::GatewayStorage, + storage: GatewayStorage, metrics: nym_node_metrics::NymNodeMetrics, - all_peers: Vec, + peers: Vec, task_client: nym_task::TaskClient, wireguard_data: WireguardData, ) -> Result, Box> { @@ -100,29 +174,13 @@ pub async fn start_wireguard( let ifname = String::from(WG_TUN_BASE_NAME); let wg_api = defguard_wireguard_rs::WGApi::new(ifname.clone(), false)?; - let mut peer_bandwidth_managers = HashMap::with_capacity(all_peers.len()); - let peers = all_peers - .into_iter() - .map(Peer::try_from) - .collect::, _>>()? - .into_iter() - .map(|mut peer| { - // since WGApi doesn't set those values on init, let's set them to 0 - peer.rx_bytes = 0; - peer.tx_bytes = 0; - peer - }) - .collect::>(); + let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len()); + for peer in peers.iter() { - let bandwidth_manager = - PeerController::generate_bandwidth_manager(storage.clone(), &peer.public_key) - .await? - .map(|bw_m| Arc::new(RwLock::new(bw_m))); - // Update storage with *x_bytes set to 0, as in kernel peers we can't set those values - // so we need to restart counting. Hopefully the bandwidth was counted in available_bandwidth - storage - .insert_wireguard_peer(peer, bandwidth_manager.is_some()) - .await?; + let bandwidth_manager = Arc::new(RwLock::new( + PeerController::generate_bandwidth_manager(Box::new(storage.clone()), &peer.public_key) + .await?, + )); peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone())); } @@ -175,7 +233,7 @@ pub async fn start_wireguard( let host = wg_api.read_interface_data()?; let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api)); let mut controller = PeerController::new( - storage, + Box::new(storage), metrics, wg_api.clone(), host, diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 0718d233ab..c7b8d5e32b 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -8,14 +8,11 @@ use defguard_wireguard_rs::{ }; use futures::channel::oneshot; use log::info; -use nym_authenticator_requests::latest::registration::{ - RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY, -}; use nym_credential_verification::{ bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, ClientBandwidth, }; -use nym_gateway_storage::GatewayStorage; +use nym_gateway_storage::traits::BandwidthGatewayStorage; use nym_node_metrics::NymNodeMetrics; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; use std::time::{Duration, SystemTime}; @@ -23,14 +20,12 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc, RwLock}; use tokio_stream::{wrappers::IntervalStream, StreamExt}; -use crate::WgApiWrapper; use crate::{error::Error, peer_handle::SharedBandwidthStorageManager}; -use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager}; +use crate::{peer_handle::PeerHandle, peer_storage_manager::CachedPeerManager}; pub enum PeerControlRequest { AddPeer { peer: Peer, - client_id: Option, response_tx: oneshot::Sender, }, RemovePeer { @@ -41,10 +36,6 @@ pub enum PeerControlRequest { key: Key, response_tx: oneshot::Sender, }, - QueryBandwidth { - key: Key, - response_tx: oneshot::Sender, - }, GetClientBandwidth { key: Key, response_tx: oneshot::Sender, @@ -64,17 +55,12 @@ pub struct QueryPeerControlResponse { pub peer: Option, } -pub struct QueryBandwidthControlResponse { - pub success: bool, - pub bandwidth_data: Option, -} - pub struct GetClientBandwidthControlResponse { pub client_bandwidth: Option, } pub struct PeerController { - storage: GatewayStorage, + storage: Box, // we have "all" metrics of a node, but they're behind a single Arc pointer, // so the overhead is minimal @@ -83,9 +69,9 @@ pub struct PeerController { // used to receive commands from individual handles too request_tx: mpsc::Sender, request_rx: mpsc::Receiver, - wg_api: Arc, + wg_api: Arc, host_information: Arc>, - bw_storage_managers: HashMap>, + bw_storage_managers: HashMap, timeout_check_interval: IntervalStream, task_client: nym_task::TaskClient, } @@ -93,11 +79,11 @@ pub struct PeerController { impl PeerController { #[allow(clippy::too_many_arguments)] pub fn new( - storage: GatewayStorage, + storage: Box, metrics: NymNodeMetrics, - wg_api: Arc, + wg_api: Arc, initial_host_information: Host, - bw_storage_managers: HashMap, Peer)>, + bw_storage_managers: HashMap, request_tx: mpsc::Sender, request_rx: mpsc::Receiver, task_client: nym_task::TaskClient, @@ -107,15 +93,11 @@ impl PeerController { ); let host_information = Arc::new(RwLock::new(initial_host_information)); for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() { - let peer_storage_manager = PeerStorageManager::new( - storage.clone(), - peer.clone(), - bandwidth_storage_manager.is_some(), - ); + let cached_peer_manager = CachedPeerManager::new(peer); let mut handle = PeerHandle::new( public_key.clone(), host_information.clone(), - peer_storage_manager, + cached_peer_manager, bandwidth_storage_manager.clone(), request_tx.clone(), &task_client, @@ -144,32 +126,11 @@ impl PeerController { } } - // Function that should be used for peer insertion, to handle both storage and kernel interaction - pub async fn add_peer(&self, peer: &Peer, client_id: Option) -> Result<(), Error> { - if client_id.is_none() { - self.storage.insert_wireguard_peer(peer, false).await?; - } - let ret: Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> = - self.wg_api.inner.configure_peer(peer); - if client_id.is_none() && ret.is_err() { - // Try to revert the insertion in storage - if self - .storage - .remove_wireguard_peer(&peer.public_key.to_string()) - .await - .is_err() - { - log::error!("The storage has been corrupted. Wireguard peer {} will persist in storage indefinitely.", peer.public_key); - } - } - Ok(ret?) - } - // Function that should be used for peer removal, to handle both storage and kernel interaction pub async fn remove_peer(&mut self, key: &Key) -> Result<(), Error> { self.storage.remove_wireguard_peer(&key.to_string()).await?; self.bw_storage_managers.remove(key); - let ret = self.wg_api.inner.remove_peer(key); + let ret = self.wg_api.remove_peer(key); if ret.is_err() { log::error!("Wireguard peer could not be removed from wireguard kernel module. Process should be restarted so that the interface is reset."); } @@ -177,50 +138,43 @@ impl PeerController { } pub async fn generate_bandwidth_manager( - storage: GatewayStorage, + storage: Box, public_key: &Key, - ) -> Result, Error> { - if let Some(client_id) = storage + ) -> Result { + let client_id = storage .get_wireguard_peer(&public_key.to_string()) .await? .ok_or(Error::MissingClientBandwidthEntry)? - .client_id - { - let bandwidth = storage - .get_available_bandwidth(client_id) - .await? - .ok_or(Error::MissingClientBandwidthEntry)?; - Ok(Some(BandwidthStorageManager::new( - storage, - ClientBandwidth::new(bandwidth.into()), - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ))) - } else { - Ok(None) - } + .client_id; + + let bandwidth = storage + .get_available_bandwidth(client_id) + .await? + .ok_or(Error::MissingClientBandwidthEntry)?; + + Ok(BandwidthStorageManager::new( + storage, + ClientBandwidth::new(bandwidth.into()), + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + )) } - async fn handle_add_request( - &mut self, - peer: &Peer, - client_id: Option, - ) -> Result<(), Error> { - self.add_peer(peer, client_id).await?; - let bandwidth_storage_manager = - Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key) - .await? - .map(|bw_m| Arc::new(RwLock::new(bw_m))); - let peer_storage_manager = PeerStorageManager::new( - self.storage.clone(), - peer.clone(), - bandwidth_storage_manager.is_some(), - ); + async fn handle_add_request(&mut self, peer: &Peer) -> Result<(), Error> { + self.wg_api.configure_peer(peer)?; + let bandwidth_storage_manager = Arc::new(RwLock::new( + Self::generate_bandwidth_manager( + dyn_clone::clone_box(&*self.storage), + &peer.public_key, + ) + .await?, + )); + let cached_peer_manager = CachedPeerManager::new(peer); let mut handle = PeerHandle::new( peer.public_key.clone(), self.host_information.clone(), - peer_storage_manager, + cached_peer_manager, bandwidth_storage_manager.clone(), self.request_tx.clone(), &self.task_client, @@ -228,7 +182,7 @@ impl PeerController { self.bw_storage_managers .insert(peer.public_key.clone(), bandwidth_storage_manager); // try to immediately update the host information, to eliminate races - if let Ok(host_information) = self.wg_api.inner.read_interface_data() { + if let Ok(host_information) = self.wg_api.read_interface_data() { *self.host_information.write().await = host_information; } let public_key = peer.public_key.clone(); @@ -248,35 +202,8 @@ impl PeerController { .transpose()?) } - async fn handle_query_bandwidth( - &self, - key: &Key, - ) -> Result, Error> { - let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else { - return Ok(None); - }; - let available_bandwidth = if let Some(bandwidth_storage_manager) = bandwidth_storage_manager - { - bandwidth_storage_manager - .read() - .await - .available_bandwidth() - .await - } else { - let Some(peer) = self.host_information.read().await.peers.get(key).cloned() else { - // host information not updated yet - return Ok(None); - }; - BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes) as i64 - }; - - Ok(Some(RemainingBandwidthData { - available_bandwidth, - })) - } - async fn handle_get_client_bandwidth(&self, key: &Key) -> Option { - if let Some(Some(bandwidth_storage_manager)) = self.bw_storage_managers.get(key) { + if let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) { Some(bandwidth_storage_manager.read().await.client_bandwidth()) } else { None @@ -362,7 +289,7 @@ impl PeerController { loop { tokio::select! { _ = self.timeout_check_interval.next() => { - let Ok(host) = self.wg_api.inner.read_interface_data() else { + let Ok(host) = self.wg_api.read_interface_data() else { log::error!("Can't read wireguard kernel data"); continue; }; @@ -376,8 +303,8 @@ impl PeerController { } msg = self.request_rx.recv() => { match msg { - Some(PeerControlRequest::AddPeer { peer, client_id, response_tx }) => { - let ret = self.handle_add_request(&peer, client_id).await; + Some(PeerControlRequest::AddPeer { peer, response_tx }) => { + let ret = self.handle_add_request(&peer).await; if ret.is_ok() { response_tx.send(AddPeerControlResponse { success: true }).ok(); } else { @@ -396,14 +323,6 @@ impl PeerController { response_tx.send(QueryPeerControlResponse { success: false, peer: None }).ok(); } } - Some(PeerControlRequest::QueryBandwidth { key, response_tx }) => { - let ret = self.handle_query_bandwidth(&key).await; - if let Ok(bandwidth_data) = ret { - response_tx.send(QueryBandwidthControlResponse { success: true, bandwidth_data }).ok(); - } else { - response_tx.send(QueryBandwidthControlResponse { success: false, bandwidth_data: None }).ok(); - } - } Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => { let client_bandwidth = self.handle_get_client_bandwidth(&key).await; response_tx.send(GetClientBandwidthControlResponse { client_bandwidth }).ok(); @@ -419,3 +338,135 @@ impl PeerController { } } } + +#[cfg(feature = "mock")] +#[derive(Default)] +struct MockWgApi { + peers: std::sync::RwLock>, +} + +#[cfg(feature = "mock")] +impl WireguardInterfaceApi for MockWgApi { + fn create_interface( + &self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + fn assign_address( + &self, + _address: &defguard_wireguard_rs::net::IpAddrMask, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + fn configure_peer_routing( + &self, + _peers: &[Peer], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + #[cfg(not(target_os = "windows"))] + fn configure_interface( + &self, + _config: &defguard_wireguard_rs::InterfaceConfiguration, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + #[cfg(target_os = "windows")] + fn configure_interface( + &self, + _config: &defguard_wireguard_rs::InterfaceConfiguration, + _dns: &[std::net::IpAddr], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + fn remove_interface( + &self, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } + + fn configure_peer( + &self, + peer: &Peer, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.peers + .write() + .unwrap() + .insert(peer.public_key.clone(), peer.clone()); + Ok(()) + } + + fn remove_peer( + &self, + peer_pubkey: &Key, + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + self.peers.write().unwrap().remove(peer_pubkey); + Ok(()) + } + + fn read_interface_data( + &self, + ) -> Result { + let mut host = Host::default(); + host.peers = self.peers.read().unwrap().clone(); + Ok(host) + } + + fn configure_dns( + &self, + _dns: &[std::net::IpAddr], + ) -> Result<(), defguard_wireguard_rs::error::WireguardInterfaceError> { + todo!() + } +} + +#[cfg(feature = "mock")] +pub fn start_controller( + request_tx: mpsc::Sender, + request_rx: mpsc::Receiver, +) -> ( + Arc>, + nym_task::TaskManager, +) { + let storage = Arc::new(RwLock::new( + nym_gateway_storage::traits::mock::MockGatewayStorage::default(), + )); + let wg_api = Arc::new(MockWgApi::default()); + let task_manager = nym_task::TaskManager::default(); + let mut peer_controller = PeerController::new( + Box::new(storage.clone()), + Default::default(), + wg_api, + Default::default(), + Default::default(), + request_tx, + request_rx, + task_manager.subscribe(), + ); + tokio::spawn(async move { peer_controller.run().await }); + + (storage, task_manager) +} + +#[cfg(feature = "mock")] +pub async fn stop_controller(mut task_manager: nym_task::TaskManager) { + task_manager.signal_shutdown().unwrap(); + task_manager.wait_for_shutdown().await; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn start_and_stop() { + let (request_tx, request_rx) = mpsc::channel(1); + let (_, task_manager) = start_controller(request_tx.clone(), request_rx); + stop_controller(task_manager).await; + } +} diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index e4de154ee6..f6c4673e21 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -3,13 +3,10 @@ use crate::error::Error; use crate::peer_controller::PeerControlRequest; -use crate::peer_storage_manager::PeerStorageManager; -use defguard_wireguard_rs::host::Peer; +use crate::peer_storage_manager::{CachedPeerManager, PeerInformation}; use defguard_wireguard_rs::{host::Host, key::Key}; use futures::channel::oneshot; -use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY; use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager; -use nym_gateway_storage::models::WireguardPeer; use nym_task::TaskClient; use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK; use std::sync::Arc; @@ -21,8 +18,8 @@ pub(crate) type SharedBandwidthStorageManager = Arc>, - peer_storage_manager: PeerStorageManager, - bandwidth_storage_manager: Option, + cached_peer: CachedPeerManager, + bandwidth_storage_manager: SharedBandwidthStorageManager, request_tx: mpsc::Sender, timeout_check_interval: IntervalStream, task_client: TaskClient, @@ -32,8 +29,8 @@ impl PeerHandle { pub fn new( public_key: Key, host_information: Arc>, - peer_storage_manager: PeerStorageManager, - bandwidth_storage_manager: Option, + cached_peer: CachedPeerManager, + bandwidth_storage_manager: SharedBandwidthStorageManager, request_tx: mpsc::Sender, task_client: &TaskClient, ) -> Self { @@ -45,7 +42,7 @@ impl PeerHandle { PeerHandle { public_key, host_information, - peer_storage_manager, + cached_peer, bandwidth_storage_manager, request_tx, timeout_check_interval, @@ -69,14 +66,10 @@ impl PeerHandle { Ok(success) } - fn compute_spent_bandwidth(kernel_peer: &Peer, storage_peer: &WireguardPeer) -> Option { - let storage_peer_rx_bytes = u64::try_from(storage_peer.rx_bytes) - .inspect_err(|e| tracing::error!("Storage rx bytes could not be converted: {e}")) - .ok()?; - let storage_peer_tx_bytes = u64::try_from(storage_peer.tx_bytes) - .inspect_err(|e| tracing::error!("Storage tx bytes could not be converted: {e}")) - .ok()?; - + fn compute_spent_bandwidth( + kernel_peer: PeerInformation, + cached_peer: PeerInformation, + ) -> Option { let kernel_total = kernel_peer .rx_bytes .checked_add(kernel_peer.tx_bytes) @@ -88,21 +81,26 @@ impl PeerHandle { ); None })?; - let storage_total = storage_peer_rx_bytes - .checked_add(storage_peer_tx_bytes) + let cached_total = cached_peer + .rx_bytes + .checked_add(cached_peer.tx_bytes) .or_else(|| { - tracing::error!("Overflow on storage adding bytes: {storage_peer_rx_bytes} + {storage_peer_tx_bytes}"); + tracing::error!( + "Overflow on cached adding bytes: {} + {}", + cached_peer.rx_bytes, + cached_peer.tx_bytes + ); None })?; - kernel_total.checked_sub(storage_total).or_else(|| { - tracing::error!("Overflow on spent bandwidth subtraction: kernel - storage = {kernel_total} - {storage_total}"); + kernel_total.checked_sub(cached_total).or_else(|| { + tracing::error!("Overflow on spent bandwidth subtraction: kernel - cached = {kernel_total} - {cached_total}"); None }) } - async fn active_peer(&mut self, kernel_peer: &Peer) -> Result { - let Some(storage_peer) = self.peer_storage_manager.get_peer() else { + async fn active_peer(&mut self, kernel_peer: PeerInformation) -> Result { + let Some(cached_peer) = self.cached_peer.get_peer() else { log::debug!( "Peer {:?} not in storage anymore, shutting down handle", self.public_key @@ -110,76 +108,51 @@ impl PeerHandle { return Ok(false); }; - if let Some(bandwidth_manager) = &self.bandwidth_storage_manager { - let spent_bandwidth = Self::compute_spent_bandwidth(kernel_peer, &storage_peer) - .unwrap_or_else(|| { - // if gateway restarted, the kernel values restart from 0 - // and we should restart from 0 in storage as well - if let Some(peer_information) = - self.peer_storage_manager.peer_information.as_mut() - { - peer_information.force_sync = true; - peer_information.peer.rx_bytes = kernel_peer.rx_bytes; - peer_information.peer.tx_bytes = kernel_peer.tx_bytes; - } - 0 - }) - .try_into() - .map_err(|_| Error::InconsistentConsumedBytes)?; - if spent_bandwidth > 0 { - self.peer_storage_manager.update_trx(kernel_peer); - if bandwidth_manager - .write() - .await - .try_use_bandwidth(spent_bandwidth) - .await - .is_err() - { - tracing::debug!( - "Peer {} is out of bandwidth, removing it", - kernel_peer.public_key.to_string() - ); - let success = self.remove_peer().await?; - self.peer_storage_manager.remove_peer(); - return Ok(!success); - } - } - } else { - let spent_bandwidth = kernel_peer.rx_bytes + kernel_peer.tx_bytes; - if spent_bandwidth >= BANDWIDTH_CAP_PER_DAY { - log::debug!( - "Peer {} doesn't have bandwidth anymore, removing it", - self.public_key - ); - let success = self.remove_peer().await?; - return Ok(!success); - } + let spent_bandwidth = Self::compute_spent_bandwidth(kernel_peer, cached_peer) + .unwrap_or_default() + .try_into() + .inspect_err(|err| tracing::error!("Could not convert from u64 to i64: {err:?}")) + .unwrap_or_default(); + + self.cached_peer.update(kernel_peer); + + if spent_bandwidth > 0 + && self + .bandwidth_storage_manager + .write() + .await + .try_use_bandwidth(spent_bandwidth) + .await + .is_err() + { + tracing::debug!( + "Peer {} is out of bandwidth, removing it", + self.public_key.to_string() + ); + let success = self.remove_peer().await?; + self.cached_peer.remove_peer(); + return Ok(!success); } Ok(true) } async fn continue_checking(&mut self) -> Result { - let Some(kernel_peer) = self + let kernel_peer = self .host_information .read() .await .peers .get(&self.public_key) - .cloned() - else { - // the host information hasn't beed updated yet - return Ok(true); - }; - if !self.active_peer(&kernel_peer).await? { + .ok_or(Error::MissingClientKernelEntry(self.public_key.to_string()))? + .into(); + if !self.active_peer(kernel_peer).await? { log::debug!( "Peer {:?} is not active anymore, shutting down handle", self.public_key ); Ok(false) } else { - // Update storage values - self.peer_storage_manager.sync_storage_peer().await?; Ok(true) } } @@ -208,11 +181,10 @@ impl PeerHandle { _ = self.task_client.recv() => { log::trace!("PeerHandle: Received shutdown"); - if let Some(bandwidth_manager) = &self.bandwidth_storage_manager { - if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await { - log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed"); - } + if let Err(e) = self.bandwidth_storage_manager.write().await.sync_storage_bandwidth().await { + log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed"); } + log::trace!("PeerHandle: Finished shutdown"); } } diff --git a/common/wireguard/src/peer_storage_manager.rs b/common/wireguard/src/peer_storage_manager.rs index 97a7463533..1675cf6b2f 100644 --- a/common/wireguard/src/peer_storage_manager.rs +++ b/common/wireguard/src/peer_storage_manager.rs @@ -1,12 +1,8 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::error::Error; use defguard_wireguard_rs::host::Peer; -use nym_gateway_storage::models::WireguardPeer; -use nym_gateway_storage::GatewayStorage; use std::time::Duration; -use time::OffsetDateTime; const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB @@ -29,116 +25,50 @@ impl Default for PeerFlushingBehaviourConfig { } } -pub struct PeerStorageManager { - pub(crate) storage: GatewayStorage, +pub struct CachedPeerManager { pub(crate) peer_information: Option, - pub(crate) cfg: PeerFlushingBehaviourConfig, - pub(crate) with_client_id: bool, } -impl PeerStorageManager { - pub(crate) fn new(storage: GatewayStorage, peer: Peer, with_client_id: bool) -> Self { - let peer_information = Some(PeerInformation::new(peer)); +impl CachedPeerManager { + pub(crate) fn new(peer: &Peer) -> Self { Self { - storage, - peer_information, - cfg: PeerFlushingBehaviourConfig::default(), - with_client_id, + peer_information: Some(peer.into()), } } - pub(crate) fn get_peer(&self) -> Option { + pub(crate) fn get_peer(&self) -> Option { self.peer_information - .as_ref() - .map(|p| p.peer.clone().into()) } pub(crate) fn remove_peer(&mut self) { self.peer_information = None; } - pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) { + pub(crate) fn update(&mut self, kernel_peer: PeerInformation) { if let Some(peer_information) = self.peer_information.as_mut() { - peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes); + peer_information.update_trx_bytes(kernel_peer); } } - - pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> { - let Some(peer_information) = self.peer_information.as_mut() else { - return Ok(()); - }; - if !peer_information.should_sync(self.cfg) { - return Ok(()); - } - if self - .storage - .get_wireguard_peer(&peer_information.peer().public_key.to_string()) - .await? - .is_none() - { - self.peer_information = None; - return Ok(()); - } - self.storage - .insert_wireguard_peer(peer_information.peer(), self.with_client_id) - .await?; - - peer_information.resync_peer_with_storage(); - - Ok(()) - } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub(crate) struct PeerInformation { - pub(crate) peer: Peer, - pub(crate) last_synced: OffsetDateTime, + pub(crate) tx_bytes: u64, + pub(crate) rx_bytes: u64, +} - pub(crate) bytes_delta_since_sync: u64, - pub(crate) force_sync: bool, +impl From<&Peer> for PeerInformation { + fn from(value: &Peer) -> Self { + Self { + tx_bytes: value.tx_bytes, + rx_bytes: value.rx_bytes, + } + } } impl PeerInformation { - pub fn new(peer: Peer) -> PeerInformation { - PeerInformation { - peer, - last_synced: OffsetDateTime::now_utc(), - bytes_delta_since_sync: 0, - force_sync: false, - } - } - - pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool { - if self.force_sync { - return true; - } - if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount { - return true; - } - - if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc() - && self.bytes_delta_since_sync != 0 - { - return true; - } - - false - } - - pub(crate) fn peer(&self) -> &Peer { - &self.peer - } - - pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) { - self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes) - + rx_bytes.saturating_sub(self.peer.rx_bytes); - self.peer.tx_bytes = tx_bytes; - self.peer.rx_bytes = rx_bytes; - } - - pub(crate) fn resync_peer_with_storage(&mut self) { - self.bytes_delta_since_sync = 0; - self.last_synced = OffsetDateTime::now_utc(); - self.force_sync = false; + pub(crate) fn update_trx_bytes(&mut self, peer: PeerInformation) { + self.tx_bytes = peer.tx_bytes; + self.rx_bytes = peer.rx_bytes; } } diff --git a/common/zulip-client/Cargo.toml b/common/zulip-client/Cargo.toml new file mode 100644 index 0000000000..acfc84a540 --- /dev/null +++ b/common/zulip-client/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "zulip-client" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +readme.workspace = true + +[dependencies] +thiserror = { workspace = true } + +itertools = { workspace = true } +url = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +zeroize = { workspace = true } + +nym-bin-common = { path = "../bin-common" } +nym-http-api-client = { path = "../http-api-client" } +reqwest = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +tokio = { workspace = true, features = ["full"] } +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/common/zulip-client/src/client.rs b/common/zulip-client/src/client.rs new file mode 100644 index 0000000000..5de0a18b5c --- /dev/null +++ b/common/zulip-client/src/client.rs @@ -0,0 +1,151 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +//! An incomplete Zulip API Client +//! +//! Currently, it serves a single purpose: to send a message to a server, +//! however, it could very easily be extended with additional functionalities. +//! +//! ## Sending Direct Message +//! +//! ```rust +//! # use zulip_client::{Client, ZulipClientError}; +//! # use zulip_client::message::DirectMessage; +//! # async fn try_send() -> Result<(), ZulipClientError> { +//! let api_key = "your-api-key"; +//! let email = "associated-email-address"; +//! let server = "https://server-address.com"; +//! let client = Client::builder(email, api_key, server)?.build()?; +//! // send to userid 12 +//! client.send_message((12u32, "hello world")).await?; +//! // more concrete typing +//! client.send_message(DirectMessage::new(12, "hello world2")).await?; +//! # Ok(()) +//! # } +//! ``` + +use crate::error::ZulipClientError; +use crate::message::{SendMessageResponse, SendableMessage}; +use nym_bin_common::bin_info; +use nym_http_api_client::UserAgent; +use reqwest::{header, Method, RequestBuilder}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use tracing::trace; +use url::Url; +use zeroize::Zeroizing; + +#[derive(Serialize, Deserialize)] +pub struct ClientConfig { + pub user_email: String, + pub api_key: String, + // TODO: introduce validation + pub user_agent: Option, + pub server_url: Url, +} + +pub struct Client { + server_url: Url, + + api_key: Zeroizing, + user_email: String, + + inner_client: reqwest::Client, +} + +fn default_user_agent() -> String { + UserAgent::from(bin_info!()).to_string() +} + +impl Client { + const MESSAGES_ENDPOINT: &'static str = "/api/v1/messages"; + + pub fn builder( + user_email: impl Into, + api_key: impl Into, + server_url: impl Into, + ) -> Result { + ClientBuilder::new(user_email, api_key, server_url) + } + + pub fn new(config: ClientConfig) -> Result { + let builder = ClientBuilder::new(config.user_email, config.api_key, config.server_url)?; + match config.user_agent { + Some(user_agent) => builder.user_agent(user_agent).build(), + None => builder.build(), + } + } + + pub async fn send_message( + &self, + msg: impl Into, + ) -> Result { + let url = format!("{}{}", self.server_url, Self::MESSAGES_ENDPOINT); + + self.build_request(Method::POST, Self::MESSAGES_ENDPOINT) + .form(&msg.into()) + .send() + .await + .map_err(|source| ZulipClientError::RequestSendingFailure { source, url })? + .json() + .await + .map_err(|source| ZulipClientError::RequestDecodeFailure { source }) + } + + fn build_request(&self, method: Method, endpoint: &'static str) -> RequestBuilder { + let url = format!("{}{endpoint}", self.server_url); + trace!("posting to {url}"); + + self.inner_client + .request(method, url) + .basic_auth(&self.user_email, Some(self.api_key.to_string())) + .header(header::CONTENT_TYPE, "application/x-www-form-urlencoded") + } +} + +pub struct ClientBuilder { + api_key: Zeroizing, + user_email: String, + server_url: Url, + user_agent: Option, +} + +impl ClientBuilder { + pub fn new( + user_email: impl Into, + api_key: impl Into, + server_url: impl Into, + ) -> Result { + let server_url = server_url.into(); + let parsed_url = + Url::from_str(&server_url).map_err(|source| ZulipClientError::MalformedServerUrl { + raw: server_url, + source, + })?; + Ok(ClientBuilder { + api_key: Zeroizing::new(api_key.into()), + user_email: user_email.into(), + server_url: parsed_url, + user_agent: None, + }) + } + + #[must_use] + pub fn user_agent(mut self, user_agent: impl Into) -> Self { + self.user_agent = Some(user_agent.into()); + self + } + + pub fn build(self) -> Result { + let user_agent = self.user_agent.unwrap_or_else(default_user_agent); + Ok(Client { + api_key: self.api_key, + server_url: self.server_url, + user_email: self.user_email, + inner_client: reqwest::ClientBuilder::new() + .user_agent(user_agent) + .build() + .map_err(|source| ZulipClientError::ClientBuildFailure { source })?, + }) + } +} diff --git a/common/zulip-client/src/error.rs b/common/zulip-client/src/error.rs new file mode 100644 index 0000000000..8556833855 --- /dev/null +++ b/common/zulip-client/src/error.rs @@ -0,0 +1,22 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ZulipClientError { + #[error("failed to send request to {url}: {source}")] + RequestSendingFailure { url: String, source: reqwest::Error }, + + #[error("failed to decode received response: {source}")] + RequestDecodeFailure { source: reqwest::Error }, + + #[error("failed to build internal client: {source}")] + ClientBuildFailure { source: reqwest::Error }, + + #[error("provided url ({raw}) is malformed: {source}")] + MalformedServerUrl { + raw: String, + source: url::ParseError, + }, +} diff --git a/common/zulip-client/src/lib.rs b/common/zulip-client/src/lib.rs new file mode 100644 index 0000000000..a7c25f7c52 --- /dev/null +++ b/common/zulip-client/src/lib.rs @@ -0,0 +1,11 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub mod client; +pub mod error; +pub mod message; + +pub type Id = u32; + +pub use client::{Client, ClientBuilder}; +pub use error::ZulipClientError; diff --git a/common/zulip-client/src/message/mod.rs b/common/zulip-client/src/message/mod.rs new file mode 100644 index 0000000000..307ef0222c --- /dev/null +++ b/common/zulip-client/src/message/mod.rs @@ -0,0 +1,215 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::message::to::{ToChannel, ToDirect}; +use serde::{Deserialize, Serialize}; + +pub mod to; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "result")] +#[serde(rename_all = "snake_case")] +pub enum SendMessageResponse { + Success { + id: i64, + automatic_new_visibility_policy: Option, + msg: String, + }, + Error { + code: String, + msg: String, + stream: Option, + }, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum SendableMessageContent { + // old name: 'private' + Direct { + // internally this is a list + to: String, + content: String, + }, + // alternative name: 'channel' + Stream { + to: String, + topic: Option, + content: String, + }, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct SendableMessage { + #[serde(flatten)] + content: SendableMessageContent, + + /// For clients supporting local echo, the event queue ID for the client. + /// If passed, `local_id` is required. If the message is successfully sent, + /// the server will include `local_id` in the message event that the client with this `queue_id` + /// will receive notifying it of the new message via `GET /events`. + /// This lets the client know unambiguously that it should replace the locally echoed message, + /// rather than adding this new message + /// (which would be correct if the user had sent the new message from another device). + /// example: "fb67bf8a-c031-47cc-84cf-ed80accacda8" + queue_id: Option, + + /// For clients supporting local echo, a unique string-format identifier chosen freely by the client; + /// the server will pass it back to the client without inspecting it, as described in the `queue_id` description. + /// example: "100.01" + local_id: Option, + + /// Whether the message should be initially marked read by its sender. + /// If unspecified, the server uses a heuristic based on the client name. + read_by_sender: bool, +} + +impl SendableMessage { + pub fn new(content: impl Into) -> Self { + SendableMessage { + content: content.into(), + queue_id: None, + local_id: None, + read_by_sender: false, + } + } + + #[must_use] + pub fn with_queue(mut self, queue_id: impl Into, local_id: impl Into) -> Self { + self.queue_id = Some(queue_id.into()); + self.local_id = Some(local_id.into()); + self + } + + #[must_use] + pub fn read_by_sender(mut self, read_by_sender: bool) -> Self { + self.read_by_sender = read_by_sender; + self + } +} + +pub type PrivateMessage = DirectMessage; + +pub struct DirectMessage { + to: String, + content: String, +} + +impl DirectMessage { + pub fn new(to: impl Into, content: impl Into) -> Self { + DirectMessage { + to: to.into().to_string(), + content: content.into(), + } + } +} + +pub type ChannelMessage = StreamMessage; +pub struct StreamMessage { + to: String, + topic: Option, + content: String, +} + +impl StreamMessage { + pub fn new( + to: impl Into, + content: impl Into, + topic: Option, + ) -> Self { + StreamMessage { + to: to.into().to_string(), + topic, + content: content.into(), + } + } + + pub fn no_topic(to: impl Into, content: impl Into) -> Self { + Self::new(to, content, None) + } + + #[must_use] + pub fn with_topic(mut self, topic: impl Into) -> Self { + self.topic = Some(topic.into()); + self + } +} + +impl From for SendableMessage { + fn from(content: SendableMessageContent) -> Self { + SendableMessage::new(content) + } +} + +impl From for SendableMessage { + fn from(msg: DirectMessage) -> Self { + SendableMessageContent::from(msg).into() + } +} + +impl From for SendableMessageContent { + fn from(msg: DirectMessage) -> Self { + SendableMessageContent::Direct { + to: msg.to, + content: msg.content, + } + } +} + +impl From<(T, S)> for DirectMessage +where + T: Into, + S: Into, +{ + fn from((to, content): (T, S)) -> Self { + DirectMessage::new(to, content) + } +} + +impl From<(T, S)> for SendableMessage +where + T: Into, + S: Into, +{ + fn from((to, content): (T, S)) -> Self { + DirectMessage::new(to, content).into() + } +} + +impl From for SendableMessage { + fn from(msg: StreamMessage) -> Self { + SendableMessageContent::from(msg).into() + } +} + +impl From for SendableMessageContent { + fn from(msg: StreamMessage) -> Self { + SendableMessageContent::Stream { + to: msg.to, + topic: msg.topic, + content: msg.content, + } + } +} + +impl From<(T, S, Option)> for StreamMessage +where + T: Into, + S: Into, +{ + fn from((to, content, topic): (T, S, Option)) -> Self { + StreamMessage::new(to, content, topic.map(Into::into)) + } +} + +impl From<(T, S, Option)> for SendableMessage +where + T: Into, + S: Into, +{ + fn from(inner: (T, S, Option)) -> Self { + StreamMessage::from(inner).into() + } +} diff --git a/common/zulip-client/src/message/to.rs b/common/zulip-client/src/message/to.rs new file mode 100644 index 0000000000..0bafc3fe11 --- /dev/null +++ b/common/zulip-client/src/message/to.rs @@ -0,0 +1,128 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::Id; +use itertools::Itertools; +use std::fmt::Display; + +// from the docs: +// For channel messages, either the name or integer ID of the channel. +// For direct messages, either a list containing integer user IDs +// or a list containing string Zulip API email addresses. +pub enum ToDirect { + ByIds(Vec), + ByNames(Vec), +} + +impl Display for ToDirect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ToDirect::ByIds(ids) => write!(f, "[{}]", ids.iter().join(",")), + ToDirect::ByNames(names) => { + write!(f, "[{}]", names.join(",")) + } + } + } +} + +impl From> for ToDirect { + fn from(names: Vec) -> Self { + ToDirect::ByNames(names) + } +} + +impl From<&[String]> for ToDirect { + fn from(names: &[String]) -> Self { + names.to_vec().into() + } +} + +impl From<&[&str]> for ToDirect { + fn from(names: &[&str]) -> Self { + names + .iter() + .map(|s| s.to_string()) + .collect::>() + .into() + } +} + +impl From<&[&str; N]> for ToDirect { + fn from(names: &[&str; N]) -> Self { + names.as_slice().into() + } +} + +impl From> for ToDirect { + fn from(names: Vec<&str>) -> Self { + names.as_slice().into() + } +} + +impl From for ToDirect { + fn from(name: String) -> Self { + ToDirect::ByNames(vec![name]) + } +} + +impl From<&str> for ToDirect { + fn from(name: &str) -> Self { + name.to_string().into() + } +} + +impl From for ToDirect { + fn from(id: Id) -> Self { + ToDirect::ByIds(vec![id]) + } +} + +impl From<&[Id]> for ToDirect { + fn from(ids: &[Id]) -> Self { + ids.to_vec().into() + } +} + +impl From<&[Id; N]> for ToDirect { + fn from(ids: &[Id; N]) -> Self { + ids.as_slice().into() + } +} + +impl From> for ToDirect { + fn from(ids: Vec) -> Self { + ToDirect::ByIds(ids) + } +} + +pub enum ToChannel { + ByName(String), + ById(Id), +} + +impl Display for ToChannel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ToChannel::ByName(name) => name.fmt(f), + ToChannel::ById(id) => id.fmt(f), + } + } +} + +impl From for ToChannel { + fn from(name: String) -> Self { + ToChannel::ByName(name) + } +} + +impl From<&str> for ToChannel { + fn from(name: &str) -> Self { + name.to_string().into() + } +} + +impl From for ToChannel { + fn from(id: Id) -> Self { + ToChannel::ById(id) + } +} diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index a434eb8778..11389591a5 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -1091,6 +1091,7 @@ dependencies = [ name = "nym-coconut-dkg" version = "0.1.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers", diff --git a/contracts/coconut-dkg/Cargo.toml b/contracts/coconut-dkg/Cargo.toml index e65539ea41..e77171d8b8 100644 --- a/contracts/coconut-dkg/Cargo.toml +++ b/contracts/coconut-dkg/Cargo.toml @@ -29,6 +29,7 @@ cw4 = { workspace = true } thiserror = { workspace = true } [dev-dependencies] +anyhow = { workspace = true } easy-addr = { path = "../../common/cosmwasm-smart-contracts/easy_addr" } cw-multi-test = { workspace = true } cw4-group = { path = "../multisig/cw4-group" } diff --git a/contracts/coconut-dkg/schema/nym-coconut-dkg.json b/contracts/coconut-dkg/schema/nym-coconut-dkg.json index 456631e501..677dadd012 100644 --- a/contracts/coconut-dkg/schema/nym-coconut-dkg.json +++ b/contracts/coconut-dkg/schema/nym-coconut-dkg.json @@ -361,6 +361,29 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_state_at_height" + ], + "properties": { + "get_epoch_state_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -460,6 +483,80 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_dealers_addresses" + ], + "properties": { + "get_epoch_dealers_addresses": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_epoch_dealers" + ], + "properties": { + "get_epoch_dealers": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -1858,6 +1955,375 @@ } } }, + "get_epoch_dealers": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof", + "ed25519_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + }, + "ed25519_identity": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "get_epoch_dealers_addresses": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerAddressesResponse", + "type": "object", + "required": [ + "dealers" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } + }, + "get_epoch_state_at_height": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Epoch", + "anyOf": [ + { + "$ref": "#/definitions/Epoch" + }, + { + "type": "null" + } + ], + "definitions": { + "Epoch": { + "type": "object", + "required": [ + "epoch_id", + "state", + "state_progress", + "time_configuration" + ], + "properties": { + "deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "state_progress": { + "$ref": "#/definitions/StateProgress" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false + }, + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "waiting_initialisation", + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "StateProgress": { + "type": "object", + "required": [ + "registered_dealers", + "registered_resharing_dealers", + "submitted_dealings", + "submitted_key_shares", + "verified_keys" + ], + "properties": { + "registered_dealers": { + "description": "Counts the number of dealers that have registered in this epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "registered_resharing_dealers": { + "description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_dealings": { + "description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_key_shares": { + "description": "Counts the number of submitted verification key shared from the dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "verified_keys": { + "description": "Counts the number of verified key shares.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, "get_epoch_threshold": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "uint64", diff --git a/contracts/coconut-dkg/schema/raw/query.json b/contracts/coconut-dkg/schema/raw/query.json index 386632565c..65b9e038e2 100644 --- a/contracts/coconut-dkg/schema/raw/query.json +++ b/contracts/coconut-dkg/schema/raw/query.json @@ -28,6 +28,29 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_state_at_height" + ], + "properties": { + "get_epoch_state_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -127,6 +150,80 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "get_epoch_dealers_addresses" + ], + "properties": { + "get_epoch_dealers_addresses": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_epoch_dealers" + ], + "properties": { + "get_epoch_dealers": { + "type": "object", + "required": [ + "epoch_id" + ], + "properties": { + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers.json b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers.json new file mode 100644 index 0000000000..12648de260 --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerResponse", + "type": "object", + "required": [ + "dealers", + "per_page" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/DealerDetails" + } + }, + "per_page": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "DealerDetails": { + "type": "object", + "required": [ + "address", + "announce_address", + "assigned_index", + "bte_public_key_with_proof", + "ed25519_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "announce_address": { + "type": "string" + }, + "assigned_index": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bte_public_key_with_proof": { + "type": "string" + }, + "ed25519_identity": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers_addresses.json b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers_addresses.json new file mode 100644 index 0000000000..529d250a0c --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_dealers_addresses.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PagedDealerAddressesResponse", + "type": "object", + "required": [ + "dealers" + ], + "properties": { + "dealers": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + }, + "start_next_after": { + "description": "Field indicating paging information for the following queries if the caller wishes to get further entries.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json new file mode 100644 index 0000000000..8f6fb68afb --- /dev/null +++ b/contracts/coconut-dkg/schema/raw/response_to_get_epoch_state_at_height.json @@ -0,0 +1,265 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Epoch", + "anyOf": [ + { + "$ref": "#/definitions/Epoch" + }, + { + "type": "null" + } + ], + "definitions": { + "Epoch": { + "type": "object", + "required": [ + "epoch_id", + "state", + "state_progress", + "time_configuration" + ], + "properties": { + "deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "epoch_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "state": { + "$ref": "#/definitions/EpochState" + }, + "state_progress": { + "$ref": "#/definitions/StateProgress" + }, + "time_configuration": { + "$ref": "#/definitions/TimeConfiguration" + } + }, + "additionalProperties": false + }, + "EpochState": { + "oneOf": [ + { + "type": "string", + "enum": [ + "waiting_initialisation", + "in_progress" + ] + }, + { + "type": "object", + "required": [ + "public_key_submission" + ], + "properties": { + "public_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dealing_exchange" + ], + "properties": { + "dealing_exchange": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_submission" + ], + "properties": { + "verification_key_submission": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_validation" + ], + "properties": { + "verification_key_validation": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "verification_key_finalization" + ], + "properties": { + "verification_key_finalization": { + "type": "object", + "required": [ + "resharing" + ], + "properties": { + "resharing": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "StateProgress": { + "type": "object", + "required": [ + "registered_dealers", + "registered_resharing_dealers", + "submitted_dealings", + "submitted_key_shares", + "verified_keys" + ], + "properties": { + "registered_dealers": { + "description": "Counts the number of dealers that have registered in this epoch.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "registered_resharing_dealers": { + "description": "Counts the number of resharing dealers that have registered in this epoch. This field is only populated during a resharing exchange. It is always <= registered_dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_dealings": { + "description": "Counts the number of fully received dealings (i.e. full chunks) from all the allowed dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "submitted_key_shares": { + "description": "Counts the number of submitted verification key shared from the dealers.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "verified_keys": { + "description": "Counts the number of verified key shares.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "TimeConfiguration": { + "type": "object", + "required": [ + "dealing_exchange_time_secs", + "in_progress_time_secs", + "public_key_submission_time_secs", + "verification_key_finalization_time_secs", + "verification_key_submission_time_secs", + "verification_key_validation_time_secs" + ], + "properties": { + "dealing_exchange_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "in_progress_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "public_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_finalization_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_submission_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "verification_key_validation_time_secs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/coconut-dkg/src/contract.rs b/contracts/coconut-dkg/src/contract.rs index 1324abf277..bbd649cea2 100644 --- a/contracts/coconut-dkg/src/contract.rs +++ b/contracts/coconut-dkg/src/contract.rs @@ -3,6 +3,7 @@ use crate::dealers::queries::{ query_current_dealers_paged, query_dealer_details, query_dealers_indices_paged, + query_epoch_dealers_addresses_paged, query_epoch_dealers_paged, query_registered_dealer_details, }; use crate::dealers::transactions::try_add_dealer; @@ -13,13 +14,14 @@ use crate::dealings::queries::{ use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata}; use crate::epoch_state::queries::{ query_can_advance_state, query_current_epoch, query_current_epoch_threshold, - query_epoch_threshold, + query_epoch_at_height, query_epoch_threshold, }; -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::save_epoch; use crate::epoch_state::transactions::{ try_advance_epoch_state, try_initiate_dkg, try_trigger_reset, try_trigger_resharing, }; use crate::error::ContractError; +use crate::queued_migrations::introduce_historical_epochs; use crate::state::queries::query_state; use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE}; use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged}; @@ -67,8 +69,9 @@ pub fn instantiate( }; STATE.save(deps.storage, &state)?; - CURRENT_EPOCH.save( + save_epoch( deps.storage, + env.block.height, &Epoch::new( EpochState::WaitingInitialisation, 0, @@ -100,6 +103,7 @@ pub fn execute( resharing, } => try_add_dealer( deps, + env, info, bte_key_with_proof, identity_key, @@ -118,7 +122,7 @@ pub fn execute( try_commit_verification_key_share(deps, env, info, share, resharing) } ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => { - try_verify_verification_key_share(deps, info, owner, resharing) + try_verify_verification_key_share(deps, env, info, owner, resharing) } ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env), ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info), @@ -131,6 +135,9 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result to_json_binary(&query_state(deps.storage)?)?, QueryMsg::GetCurrentEpochState {} => to_json_binary(&query_current_epoch(deps.storage)?)?, + QueryMsg::GetEpochStateAtHeight { height } => { + to_json_binary(&query_epoch_at_height(deps.storage, height)?)? + } QueryMsg::CanAdvanceState {} => { to_json_binary(&query_can_advance_state(deps.storage, env)?)? } @@ -151,6 +158,26 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { to_json_binary(&query_dealer_details(deps, dealer_address)?)? } + QueryMsg::GetEpochDealersAddresses { + epoch_id, + limit, + start_after, + } => to_json_binary(&query_epoch_dealers_addresses_paged( + deps, + epoch_id, + start_after, + limit, + )?)?, + QueryMsg::GetEpochDealers { + epoch_id, + limit, + start_after, + } => to_json_binary(&query_epoch_dealers_paged( + deps, + epoch_id, + start_after, + limit, + )?)?, QueryMsg::GetCurrentDealers { limit, start_after } => { to_json_binary(&query_current_dealers_paged(deps, start_after, limit)?)? } @@ -221,16 +248,11 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result { set_build_information!(deps.storage)?; cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // MAINNET MIGRATION ASSERTION - let epoch = CURRENT_EPOCH.load(deps.storage)?; - assert_eq!(0, epoch.epoch_id); - - let threshold = THRESHOLD.load(deps.storage)?; - EPOCH_THRESHOLDS.save(deps.storage, 0, &threshold)?; + introduce_historical_epochs(deps, env)?; Ok(Response::new()) } @@ -334,7 +356,7 @@ mod tests { let api = MockApi::default(); const MEMBER_SIZE: usize = 100; let members: [Addr; MEMBER_SIZE] = - std::array::from_fn(|idx| api.addr_make(&format!("member{}", idx))); + std::array::from_fn(|idx| api.addr_make(&format!("member{idx}"))); let mut app = AppBuilder::new().build(|router, _, storage| { router diff --git a/contracts/coconut-dkg/src/dealers/queries.rs b/contracts/coconut-dkg/src/dealers/queries.rs index b092d8c140..4a309405f7 100644 --- a/contracts/coconut-dkg/src/dealers/queries.rs +++ b/contracts/coconut-dkg/src/dealers/queries.rs @@ -5,12 +5,12 @@ use crate::dealers::storage::{ self, get_dealer_details, get_dealer_index, get_registration_details, DEALERS_INDICES, EPOCH_DEALERS_MAP, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; use nym_coconut_dkg_common::dealer::{ - DealerDetailsResponse, DealerType, PagedDealerIndexResponse, PagedDealerResponse, - RegisteredDealerDetails, + DealerDetailsResponse, DealerType, PagedDealerAddressesResponse, PagedDealerIndexResponse, + PagedDealerResponse, RegisteredDealerDetails, }; use nym_coconut_dkg_common::types::{DealerDetails, EpochId}; @@ -23,7 +23,7 @@ pub fn query_registered_dealer_details( let epoch_id = match epoch_id { Some(epoch_id) => epoch_id, - None => CURRENT_EPOCH.load(deps.storage)?.epoch_id, + None => load_current_epoch(deps.storage)?.epoch_id, }; Ok(RegisteredDealerDetails { @@ -36,7 +36,7 @@ pub fn query_dealer_details( dealer_address: String, ) -> StdResult { let addr = deps.api.addr_validate(&dealer_address)?; - let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; + let current_epoch_id = load_current_epoch(deps.storage)?.epoch_id; // if the address has registration data for the current epoch, it means it's an active dealer if let Ok(dealer_details) = get_dealer_details(deps.storage, &addr, current_epoch_id) { @@ -82,8 +82,37 @@ pub fn query_dealers_indices_paged( Ok(PagedDealerIndexResponse::new(dealers, start_next_after)) } -pub fn query_current_dealers_paged( +pub fn query_epoch_dealers_addresses_paged( deps: Deps<'_>, + epoch_id: EpochId, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(storage::DEALERS_ADDRESSES_PAGE_DEFAULT_LIMIT) + .min(storage::DEALERS_ADDRESSES_PAGE_MAX_LIMIT) as usize; + let addr = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + + let start = addr.as_ref().map(Bound::exclusive); + + let dealers = EPOCH_DEALERS_MAP + .prefix(epoch_id) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()?; + let start_next_after = dealers.last().cloned(); + + Ok(PagedDealerAddressesResponse { + dealers, + start_next_after, + }) +} + +pub fn query_epoch_dealers_paged( + deps: Deps<'_>, + epoch_id: EpochId, start_after: Option, limit: Option, ) -> StdResult { @@ -96,10 +125,8 @@ pub fn query_current_dealers_paged( let start = addr.as_ref().map(Bound::exclusive); - let current_epoch_id = CURRENT_EPOCH.load(deps.storage)?.epoch_id; - let dealers = EPOCH_DEALERS_MAP - .prefix(current_epoch_id) + .prefix(epoch_id) .range(deps.storage, start, None, Order::Ascending) .take(limit) .map(|res| { @@ -107,7 +134,7 @@ pub fn query_current_dealers_paged( // SAFETY: if we have DealerRegistrationDetails saved, it means we MUST also have its node index // otherwise some serious invariants have been broken in the contract, and we're in trouble #[allow(clippy::expect_used)] - let assigned_index = get_dealer_index(deps.storage, &address, current_epoch_id) + let assigned_index = get_dealer_index(deps.storage, &address, epoch_id) .expect("could not retrieve dealer index for a registered dealer"); DealerDetails { @@ -125,6 +152,15 @@ pub fn query_current_dealers_paged( Ok(PagedDealerResponse::new(dealers, limit, start_next_after)) } +pub fn query_current_dealers_paged( + deps: Deps<'_>, + start_after: Option, + limit: Option, +) -> StdResult { + let current_epoch_id = load_current_epoch(deps.storage)?.epoch_id; + query_epoch_dealers_paged(deps, current_epoch_id, start_after, limit) +} + #[cfg(test)] pub(crate) mod tests { use super::*; @@ -158,112 +194,330 @@ pub(crate) mod tests { } } - #[test] - fn dealers_empty_on_init() { - let deps = init_contract(); + #[cfg(test)] + mod current_epoch_dealers { + use super::*; - let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap(); - assert_eq!(0, page1.dealers.len() as u32); + #[test] + fn dealers_empty_on_init() { + let deps = init_contract(); + + let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap(); + assert_eq!(0, page1.dealers.len() as u32); + } + + #[test] + fn dealers_paged_retrieval_obeys_limits() { + let mut deps = init_contract(); + let limit = 2; + + fill_dealers(&mut deps, 0, 1000); + + let page1 = + query_current_dealers_paged(deps.as_ref(), None, Option::from(limit)).unwrap(); + assert_eq!(limit, page1.dealers.len() as u32); + + remove_dealers(&mut deps, 0, 1000); + } + + #[test] + fn dealers_paged_retrieval_has_default_limit() { + let mut deps = init_contract(); + + fill_dealers(&mut deps, 0, 1000); + + // query without explicitly setting a limit + let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap(); + + assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32); + + remove_dealers(&mut deps, 0, 1000); + } + + #[test] + fn dealers_paged_retrieval_has_max_limit() { + let mut deps = init_contract(); + + // query with a crazily high limit in an attempt to use too many resources + let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; + + fill_dealers(&mut deps, 0, 1000); + + let page1 = query_current_dealers_paged(deps.as_ref(), None, Option::from(crazy_limit)) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DEALERS_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.dealers.len() as u32); + + remove_dealers(&mut deps, 0, 1000); + } + + #[test] + fn dealers_pagination_works() { + let mut deps = init_contract(); + + let per_page = 2; + + fill_dealers(&mut deps, 0, 1); + let page1 = + query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.dealers.len()); + remove_dealers(&mut deps, 0, 1); + + fill_dealers(&mut deps, 0, 2); + // page1 should have 2 results on it + let page1 = + query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.dealers.len()); + remove_dealers(&mut deps, 0, 2); + + fill_dealers(&mut deps, 0, 3); + // page1 still has 2 results + let page1 = + query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.dealers.len()); + + // retrieving the next page should start after the last key on this page + let start_after = page1.start_next_after.unwrap(); + let page2 = query_current_dealers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.dealers.len()); + remove_dealers(&mut deps, 0, 3); + + fill_dealers(&mut deps, 0, 4); + let page1 = + query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); + let start_after = page1.start_next_after.unwrap(); + let page2 = query_current_dealers_paged( + deps.as_ref(), + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.dealers.len()); + remove_dealers(&mut deps, 0, 4); + } + } + + #[cfg(test)] + mod epoch_dealers { + use super::*; + + #[test] + fn dealers_empty_on_init() { + let deps = init_contract(); + + // check few epochs + for epoch_id in 0..10 { + let page1 = query_epoch_dealers_paged(deps.as_ref(), epoch_id, None, None).unwrap(); + assert_eq!(0, page1.dealers.len() as u32); + } + } + + #[test] + fn theres_no_ovewriting_between_epochs() { + let mut deps = init_contract(); + + fill_dealers(&mut deps, 1, 1000); + + let page1 = query_epoch_dealers_paged(deps.as_ref(), 1, None, None).unwrap(); + assert!(!page1.dealers.is_empty()); + + // nothing for other epochs + let another_epoch = query_epoch_dealers_paged(deps.as_ref(), 2, None, None).unwrap(); + assert!(another_epoch.dealers.is_empty()); + + let another_epoch = query_epoch_dealers_paged(deps.as_ref(), 42, None, None).unwrap(); + assert!(another_epoch.dealers.is_empty()); + } + + #[test] + fn dealers_paged_retrieval_obeys_limits() { + let mut deps = init_contract(); + let limit = 2; + + fill_dealers(&mut deps, 0, 1000); + + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(limit)).unwrap(); + assert_eq!(limit, page1.dealers.len() as u32); + } + + #[test] + fn dealers_paged_retrieval_has_default_limit() { + let mut deps = init_contract(); + + fill_dealers(&mut deps, 0, 1000); + + // query without explicitly setting a limit + let page1 = query_epoch_dealers_paged(deps.as_ref(), 0, None, None).unwrap(); + + assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32); + } + + #[test] + fn dealers_paged_retrieval_has_max_limit() { + let mut deps = init_contract(); + + // query with a crazily high limit in an attempt to use too many resources + let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; + + fill_dealers(&mut deps, 0, 1000); + + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(crazy_limit)) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DEALERS_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.dealers.len() as u32); + } + + #[test] + fn dealers_pagination_works() { + let mut deps = init_contract(); + + let per_page = 2; + + fill_dealers(&mut deps, 0, 1); + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.dealers.len()); + remove_dealers(&mut deps, 0, 1); + + fill_dealers(&mut deps, 0, 2); + // page1 should have 2 results on it + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.dealers.len()); + remove_dealers(&mut deps, 0, 2); + + fill_dealers(&mut deps, 0, 3); + // page1 still has 2 results + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); + assert_eq!(2, page1.dealers.len()); + + // retrieving the next page should start after the last key on this page + let start_after = page1.start_next_after.unwrap(); + let page2 = query_epoch_dealers_paged( + deps.as_ref(), + 0, + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.dealers.len()); + remove_dealers(&mut deps, 0, 3); + + fill_dealers(&mut deps, 0, 4); + let page1 = + query_epoch_dealers_paged(deps.as_ref(), 0, None, Option::from(per_page)).unwrap(); + let start_after = page1.start_next_after.unwrap(); + let page2 = query_epoch_dealers_paged( + deps.as_ref(), + 0, + Option::from(start_after.to_string()), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.dealers.len()); + } } #[test] - fn dealers_paged_retrieval_obeys_limits() { - let mut deps = init_contract(); - let limit = 2; - - fill_dealers(&mut deps, 0, 1000); - - let page1 = query_current_dealers_paged(deps.as_ref(), None, Option::from(limit)).unwrap(); - assert_eq!(limit, page1.dealers.len() as u32); - - remove_dealers(&mut deps, 0, 1000); - } - - #[test] - fn dealers_paged_retrieval_has_default_limit() { + fn epoch_dealers_addresses() { let mut deps = init_contract(); - fill_dealers(&mut deps, 0, 1000); + let mut fixtures = Vec::new(); + for i in 0..100 { + let mut dealer_details = dealer_details_fixture(&deps.api, i); + dealer_details.address = deps.api.addr_make(&format!("dummy-dealer-{i}")); + fixtures.push(dealer_details); + } - // query without explicitly setting a limit - let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap(); + // initially empty for all epochs + for epoch_id in 0..10 { + let page1 = + query_epoch_dealers_addresses_paged(deps.as_ref(), epoch_id, None, None).unwrap(); + assert_eq!(0, page1.dealers.len() as u32); + } - assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32); + // epoch0: dealers 0,1,2,3 + // epoch1: dealers 4,5,6 + // epoch2: dealers: 1,4,6 (some overlap) + // epoch3: dealer 7 + // epoch4: dealers 0..100 (to check limits) + insert_dealer(deps.as_mut(), 0, &fixtures[0]); + insert_dealer(deps.as_mut(), 0, &fixtures[1]); + insert_dealer(deps.as_mut(), 0, &fixtures[2]); + insert_dealer(deps.as_mut(), 0, &fixtures[3]); - remove_dealers(&mut deps, 0, 1000); - } + insert_dealer(deps.as_mut(), 1, &fixtures[4]); + insert_dealer(deps.as_mut(), 1, &fixtures[5]); + insert_dealer(deps.as_mut(), 1, &fixtures[6]); - #[test] - fn dealers_paged_retrieval_has_max_limit() { - let mut deps = init_contract(); + insert_dealer(deps.as_mut(), 2, &fixtures[1]); + insert_dealer(deps.as_mut(), 2, &fixtures[4]); + insert_dealer(deps.as_mut(), 2, &fixtures[6]); - // query with a crazily high limit in an attempt to use too many resources - let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT; + insert_dealer(deps.as_mut(), 3, &fixtures[7]); - fill_dealers(&mut deps, 0, 1000); + for fixture in &fixtures { + insert_dealer(deps.as_mut(), 4, fixture); + } - let page1 = - query_current_dealers_paged(deps.as_ref(), None, Option::from(crazy_limit)).unwrap(); + let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 0, None, None).unwrap(); + assert_eq!(4, res.dealers.len() as u32); + for fixture in &fixtures[0..=3] { + assert!(res.dealers.contains(&fixture.address)) + } - // we default to a decent sized upper bound instead - let expected_limit = DEALERS_PAGE_MAX_LIMIT; - assert_eq!(expected_limit, page1.dealers.len() as u32); + let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 1, None, None).unwrap(); + assert_eq!(3, res.dealers.len() as u32); + for fixture in &fixtures[4..=6] { + assert!(res.dealers.contains(&fixture.address)) + } - remove_dealers(&mut deps, 0, 1000); - } + let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 2, None, None).unwrap(); + assert_eq!(3, res.dealers.len() as u32); + for fixture in &[ + fixtures[1].clone(), + fixtures[4].clone(), + fixtures[6].clone(), + ] { + assert!(res.dealers.contains(&fixture.address)) + } - #[test] - fn dealers_pagination_works() { - let mut deps = init_contract(); + let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 3, None, None).unwrap(); + assert_eq!(vec![fixtures[7].address.clone()], res.dealers); - let per_page = 2; + let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 4, None, None).unwrap(); + assert_eq!( + storage::DEALERS_ADDRESSES_PAGE_DEFAULT_LIMIT, + res.dealers.len() as u32 + ); - fill_dealers(&mut deps, 0, 1); - let page1 = - query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); - - // page should have 1 result on it - assert_eq!(1, page1.dealers.len()); - remove_dealers(&mut deps, 0, 1); - - fill_dealers(&mut deps, 0, 2); - // page1 should have 2 results on it - let page1 = - query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); - assert_eq!(2, page1.dealers.len()); - remove_dealers(&mut deps, 0, 2); - - fill_dealers(&mut deps, 0, 3); - // page1 still has 2 results - let page1 = - query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); - assert_eq!(2, page1.dealers.len()); - - // retrieving the next page should start after the last key on this page - let start_after = page1.start_next_after.unwrap(); - let page2 = query_current_dealers_paged( - deps.as_ref(), - Option::from(start_after.to_string()), - Option::from(per_page), - ) - .unwrap(); - - assert_eq!(1, page2.dealers.len()); - remove_dealers(&mut deps, 0, 3); - - fill_dealers(&mut deps, 0, 4); - let page1 = - query_current_dealers_paged(deps.as_ref(), None, Option::from(per_page)).unwrap(); - let start_after = page1.start_next_after.unwrap(); - let page2 = query_current_dealers_paged( - deps.as_ref(), - Option::from(start_after.to_string()), - Option::from(per_page), - ) - .unwrap(); - - // now we have 2 pages, with 2 results on the second page - assert_eq!(2, page2.dealers.len()); - remove_dealers(&mut deps, 0, 4); + let res = + query_epoch_dealers_addresses_paged(deps.as_ref(), 4, None, Some(1000000)).unwrap(); + assert_eq!( + storage::DEALERS_ADDRESSES_PAGE_MAX_LIMIT, + res.dealers.len() as u32 + ); } } diff --git a/contracts/coconut-dkg/src/dealers/storage.rs b/contracts/coconut-dkg/src/dealers/storage.rs index e713940175..e3edcc95a7 100644 --- a/contracts/coconut-dkg/src/dealers/storage.rs +++ b/contracts/coconut-dkg/src/dealers/storage.rs @@ -13,6 +13,9 @@ pub(crate) const DEALER_INDICES_PAGE_DEFAULT_LIMIT: u32 = 40; pub(crate) const DEALERS_PAGE_MAX_LIMIT: u32 = 25; pub(crate) const DEALERS_PAGE_DEFAULT_LIMIT: u32 = 10; +pub(crate) const DEALERS_ADDRESSES_PAGE_MAX_LIMIT: u32 = 50; +pub(crate) const DEALERS_ADDRESSES_PAGE_DEFAULT_LIMIT: u32 = 25; + pub(crate) const NODE_INDEX_COUNTER: Item = Item::new("node_index_counter"); pub(crate) const DEALERS_INDICES: Map = Map::new("dealer_index"); diff --git a/contracts/coconut-dkg/src/dealers/transactions.rs b/contracts/coconut-dkg/src/dealers/transactions.rs index a7fee91108..ccb6d57c29 100644 --- a/contracts/coconut-dkg/src/dealers/transactions.rs +++ b/contracts/coconut-dkg/src/dealers/transactions.rs @@ -4,12 +4,12 @@ use crate::dealers::storage::{ get_or_assign_index, is_dealer, save_dealer_details_if_not_a_dealer, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::STATE; use crate::Dealer; -use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult}; +use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Response}; use nym_coconut_dkg_common::dealer::DealerRegistrationDetails; use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState}; @@ -28,13 +28,14 @@ fn ensure_group_member(deps: Deps, dealer: Dealer) -> Result<(), ContractError> // for a recurring dealer just let it refresh the keys without having to do all the storage operations pub fn try_add_dealer( deps: DepsMut<'_>, + env: Env, info: MessageInfo, bte_key_with_proof: EncodedBTEPublicKeyWithProof, identity_key: String, announce_address: String, resharing: bool, ) -> Result { - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?; // make sure this potential dealer actually belong to the group @@ -68,16 +69,16 @@ pub fn try_add_dealer( ); // increment the number of registered dealers - CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> { - let mut updated_epoch = epoch; + { + let current_epoch = load_current_epoch(deps.storage)?; + let mut updated_epoch = current_epoch; updated_epoch.state_progress.registered_dealers += 1; if is_resharing_dealer { updated_epoch.state_progress.registered_resharing_dealers += 1; } - - Ok(updated_epoch) - })?; + save_epoch(deps.storage, env.block.height, &updated_epoch)?; + } Ok(Response::new().add_attribute("node_index", node_index.to_string())) } @@ -115,10 +116,11 @@ pub(crate) mod tests { .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs); add_fixture_dealer(deps.as_mut()); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); let ret = try_add_dealer( deps.as_mut(), + env, info, bte_key_with_proof, identity, diff --git a/contracts/coconut-dkg/src/dealings/transactions.rs b/contracts/coconut-dkg/src/dealings/transactions.rs index 73107c352f..186754c3a8 100644 --- a/contracts/coconut-dkg/src/dealings/transactions.rs +++ b/contracts/coconut-dkg/src/dealings/transactions.rs @@ -5,7 +5,7 @@ use crate::dealers::storage::ensure_dealer; use crate::dealings::storage::{ metadata_exists, must_read_metadata, store_metadata, StoredDealing, }; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::STATE; @@ -42,7 +42,7 @@ pub fn try_submit_dealings_metadata( chunks: Vec, resharing: bool, ) -> Result { - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; let state = STATE.load(deps.storage)?; ensure_permission(deps.storage, &info.sender, epoch.epoch_id, resharing)?; @@ -137,7 +137,7 @@ pub fn try_commit_dealings_chunk( // note: checking permissions is implicit as if the metadata exists, // the sender must have been allowed to submit it - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; // read meta let mut metadata = must_read_metadata( @@ -197,7 +197,7 @@ pub fn try_commit_dealings_chunk( // there won't be a lot of them if metadata.is_complete() { epoch.state_progress.submitted_dealings += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; } Ok(Response::new()) @@ -309,12 +309,10 @@ pub(crate) mod tests { ); // same index, but next epoch - CURRENT_EPOCH - .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| { - epoch.epoch_id += 1; - Ok(epoch) - }) - .unwrap(); + let mut epoch = load_current_epoch(&deps.storage).unwrap(); + epoch.epoch_id += 1; + save_epoch(deps.as_mut().storage, epoch.epoch_id, &epoch).unwrap(); + re_register_dealer(deps.as_mut(), &info.sender); try_submit_dealings_metadata( diff --git a/contracts/coconut-dkg/src/epoch_state/queries.rs b/contracts/coconut-dkg/src/epoch_state/queries.rs index 15ba780aec..f107d2e59c 100644 --- a/contracts/coconut-dkg/src/epoch_state/queries.rs +++ b/contracts/coconut-dkg/src/epoch_state/queries.rs @@ -1,17 +1,19 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::{ + load_current_epoch, EPOCH_THRESHOLDS, HISTORICAL_EPOCH, THRESHOLD, +}; use crate::epoch_state::utils::check_state_completion; use crate::error::ContractError; -use cosmwasm_std::{Env, Storage}; +use cosmwasm_std::{Env, StdResult, Storage}; use nym_coconut_dkg_common::types::{Epoch, EpochId, EpochState, StateAdvanceResponse}; pub(crate) fn query_can_advance_state( storage: &dyn Storage, env: Env, ) -> Result { - let epoch = CURRENT_EPOCH.load(storage)?; + let epoch = load_current_epoch(storage)?; if epoch.state == EpochState::WaitingInitialisation { return Ok(StateAdvanceResponse::default()); @@ -34,9 +36,14 @@ pub(crate) fn query_can_advance_state( } pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result { - CURRENT_EPOCH - .load(storage) - .map_err(|_| ContractError::EpochNotInitialised) + load_current_epoch(storage).map_err(|_| ContractError::EpochNotInitialised) +} + +pub(crate) fn query_epoch_at_height( + storage: &dyn Storage, + height: u64, +) -> StdResult> { + HISTORICAL_EPOCH.may_load_at_height(storage, height) } pub(crate) fn query_current_epoch_threshold( diff --git a/contracts/coconut-dkg/src/epoch_state/storage.rs b/contracts/coconut-dkg/src/epoch_state/storage.rs index faed18e431..4318c8cf7b 100644 --- a/contracts/coconut-dkg/src/epoch_state/storage.rs +++ b/contracts/coconut-dkg/src/epoch_state/storage.rs @@ -1,11 +1,225 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use cw_storage_plus::{Item, Map}; +use cosmwasm_std::{StdResult, Storage}; +use cw_storage_plus::{Item, Map, SnapshotItem, Strategy}; use nym_coconut_dkg_common::types::{Epoch, EpochId}; +#[deprecated] +// leave old values in storage for backwards compatibility, but make sure everything in the contract +// uses the new reference pub(crate) const CURRENT_EPOCH: Item = Item::new("current_epoch"); +pub const HISTORICAL_EPOCH: SnapshotItem = SnapshotItem::new( + "historical_epoch", + "historical_epoch__checkpoints", + "historical_epoch__changelog", + Strategy::EveryBlock, +); pub const THRESHOLD: Item = Item::new("threshold"); pub const EPOCH_THRESHOLDS: Map = Map::new("epoch_thresholds"); + +#[allow(deprecated)] +pub fn save_epoch(storage: &mut dyn Storage, height: u64, epoch: &Epoch) -> StdResult<()> { + CURRENT_EPOCH.save(storage, epoch)?; + HISTORICAL_EPOCH.save(storage, epoch, height) +} + +#[allow(deprecated)] +pub fn load_current_epoch(storage: &dyn Storage) -> StdResult { + #[cfg(debug_assertions)] + { + let current = CURRENT_EPOCH.load(storage); + let historical = HISTORICAL_EPOCH.load(storage); + debug_assert_eq!(current, historical); + } + HISTORICAL_EPOCH.load(storage) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::epoch_state::transactions::{try_advance_epoch_state, try_initiate_dkg}; + use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; + use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env}; + use cosmwasm_std::{Addr, Env}; + use nym_coconut_dkg_common::types::EpochState; + use std::ops::{Deref, DerefMut}; + + #[test] + fn full_dkg_correctly_updates_historical_epoch() -> anyhow::Result<()> { + struct EnvWrapper { + env: Env, + } + + impl EnvWrapper { + fn next_block(&mut self) { + self.env.block.height += 1; + self.env.block.time = self.env.block.time.plus_seconds(5); + } + + fn height(&self) -> u64 { + self.block.height + } + } + + impl Deref for EnvWrapper { + type Target = Env; + fn deref(&self) -> &Self::Target { + &self.env + } + } + + impl DerefMut for EnvWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.env + } + } + + let mut empty_deps = mock_dependencies(); + + // before contract is initialised, there's nothing saved + assert!(HISTORICAL_EPOCH + .may_load(empty_deps.as_mut().storage)? + .is_none()); + + let mut deps = init_contract(); + let mut env = EnvWrapper { env: mock_env() }; + + let init_height = env.height(); + // after init it has initial state + assert_eq!(HISTORICAL_EPOCH.load(deps.as_mut().storage)?.epoch_id, 0); + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::WaitingInitialisation + ); + + env.next_block(); + let pub_key_submission_height = env.height(); + try_initiate_dkg( + deps.as_mut(), + (*env).clone(), + message_info(&Addr::unchecked(ADMIN_ADDRESS), &[]), + )?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::PublicKeySubmission { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let dealing_exchange_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::DealingExchange { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_submission_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeySubmission { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_validation_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeyValidation { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let verification_key_finalization_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::VerificationKeyFinalization { resharing: false } + ); + + env.block.time = env.block.time.plus_seconds(100000); + env.next_block(); + let in_progress_height = env.height(); + try_advance_epoch_state(deps.as_mut(), (*env).clone())?; + assert_eq!( + HISTORICAL_EPOCH.load(deps.as_mut().storage)?.state, + EpochState::InProgress {} + ); + + // check old data + assert!(HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, init_height - 1)? + .is_none()); + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, init_height + 1)? + .unwrap() + .state, + EpochState::WaitingInitialisation + ); + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, pub_key_submission_height + 1)? + .unwrap() + .state, + EpochState::PublicKeySubmission { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, dealing_exchange_height + 1)? + .unwrap() + .state, + EpochState::DealingExchange { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_submission_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeySubmission { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_validation_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeyValidation { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height( + deps.as_mut().storage, + verification_key_finalization_height + 1 + )? + .unwrap() + .state, + EpochState::VerificationKeyFinalization { resharing: false } + ); + + assert_eq!( + HISTORICAL_EPOCH + .may_load_at_height(deps.as_mut().storage, in_progress_height + 1)? + .unwrap() + .state, + EpochState::InProgress + ); + + Ok(()) + } +} diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs index cfabdc700d..156459f24c 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/advance_epoch_state.rs @@ -1,7 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD}; +use crate::epoch_state::storage::{load_current_epoch, save_epoch, EPOCH_THRESHOLDS, THRESHOLD}; use crate::epoch_state::transactions::reset_dkg_state; use crate::epoch_state::utils::check_state_completion; use crate::error::ContractError; @@ -39,7 +39,7 @@ fn ensure_can_advance_state( pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result { // TODO: the only case where this can retrigger itself is when insufficient number of parties completed it, i.e. we don't have threshold - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // checks whether the given phase has either completed or reached its deadline ensure_can_advance_state(deps.as_ref(), &env, ¤t_epoch)?; @@ -82,7 +82,7 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result, env: Env) -> Result(storage: &mut dyn Storage, env: &Env, action: A) + where + A: Fn(Epoch) -> Epoch, + { + let current = load_current_epoch(storage).unwrap(); + let updated = action(current); + save_epoch(storage, env.block.height, &updated).unwrap(); + } + #[test] fn short_circuit_advance_state() { fn epoch_in_state(state: EpochState, env: &Env) -> Epoch { Epoch::new(state, 0, Default::default(), env.block.time) } - fn set_epoch(storage: &mut dyn Storage, epoch: Epoch) { - CURRENT_EPOCH.save(storage, &epoch).unwrap(); + fn set_epoch(storage: &mut dyn Storage, env: &Env, epoch: Epoch) { + save_epoch(storage, env.block.height, &epoch).unwrap(); } let mut deps = init_contract(); @@ -114,18 +124,18 @@ mod tests { // it's never possible to short-circuit `WaitingInitialisation` let epoch = epoch_in_state(EpochState::WaitingInitialisation, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); // neither PublicKeySubmission (in either resharing or non-resharing) let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: false }, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); let epoch = epoch_in_state(EpochState::PublicKeySubmission { resharing: true }, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -138,7 +148,7 @@ mod tests { // no dealings let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -146,7 +156,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_dealings = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -154,7 +164,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: false }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_dealings = key_size * 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -167,7 +177,7 @@ mod tests { let mut epoch = epoch_in_state(EpochState::DealingExchange { resharing: true }, &env); epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -176,7 +186,7 @@ mod tests { epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; epoch.state_progress.submitted_dealings = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -185,7 +195,7 @@ mod tests { epoch.state_progress.registered_dealers = 5; epoch.state_progress.registered_resharing_dealers = 4; epoch.state_progress.submitted_dealings = key_size * 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -200,7 +210,7 @@ mod tests { &env, ); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -209,7 +219,7 @@ mod tests { &env, ); epoch.state_progress.registered_dealers = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -219,7 +229,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -229,7 +239,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -239,7 +249,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -254,7 +264,7 @@ mod tests { ); epoch.state_progress.registered_dealers = 5; epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state( @@ -268,7 +278,7 @@ mod tests { EpochState::VerificationKeyValidation { resharing: false }, &env, ); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -276,7 +286,7 @@ mod tests { EpochState::VerificationKeyValidation { resharing: true }, &env, ); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -286,7 +296,7 @@ mod tests { &env, ); epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -295,7 +305,7 @@ mod tests { &env, ); epoch.state_progress.submitted_key_shares = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -305,7 +315,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -315,7 +325,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 4; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); @@ -325,7 +335,7 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap(); @@ -336,14 +346,14 @@ mod tests { ); epoch.state_progress.submitted_key_shares = 5; epoch.state_progress.verified_keys = 5; - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_ok()); check_epoch_state(deps.as_ref().storage, EpochState::InProgress).unwrap(); // it's never possible to short-circuit `InProgress` let epoch = epoch_in_state(EpochState::InProgress, &env); - set_epoch(deps.as_mut().storage, epoch); + set_epoch(deps.as_mut().storage, &env, epoch); let res = try_advance_epoch_state(deps.as_mut(), env.clone()); assert!(res.is_err()); } @@ -366,7 +376,7 @@ mod tests { ) .unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::PublicKeySubmission { resharing: false } @@ -390,19 +400,16 @@ mod tests { env.block.time = env.block.time.plus_seconds(1); // add some dealers to prevent short-circuiting - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.registered_dealers = 42; - Ok(e) - }) - .unwrap(); - + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.registered_dealers = 42; + e + }); env.block.time = env .block .time .plus_seconds(epoch.time_configuration.public_key_submission_time_secs); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::DealingExchange { resharing: false } @@ -425,7 +432,7 @@ mod tests { env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeySubmission { resharing: false } @@ -452,7 +459,7 @@ mod tests { env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeyValidation { resharing: false } @@ -478,16 +485,13 @@ mod tests { ); // add some key shares to prevent short-circuiting - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.submitted_key_shares = 42; - Ok(e) - }) - .unwrap(); - + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.submitted_key_shares = 42; + e + }); env.block.time = env.block.time.plus_seconds(3); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!( epoch.state, EpochState::VerificationKeyFinalization { resharing: false } @@ -512,16 +516,14 @@ mod tests { ); // add some finalized keys to prevent reset - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.verified_keys = 42; - Ok(e) - }) - .unwrap(); + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.verified_keys = 42; + e + }); env.block.time = env.block.time.plus_seconds(1); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let epoch = load_current_epoch(deps.as_mut().storage).unwrap(); assert_eq!(epoch.state, EpochState::InProgress); assert_eq!( epoch.deadline.unwrap(), @@ -547,9 +549,9 @@ mod tests { // Group hasn't changed, so we remain in the same epoch, with updated finish timestamp env.block.time = env.block.time.plus_seconds(100); - let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let prev_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); let mut expected_epoch = Epoch::new( EpochState::InProgress, prev_epoch.epoch_id, @@ -570,11 +572,11 @@ mod tests { // fewer than the threshold epoch.state_progress.verified_keys = 41; - CURRENT_EPOCH.save(deps.as_mut().storage, &epoch).unwrap(); + save_epoch(deps.as_mut().storage, env.block.height, &epoch).unwrap(); env.block.time = env.block.time.plus_seconds(5000000); try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap(); + let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap(); let expected_epoch = Epoch::new( EpochState::PublicKeySubmission { resharing: false }, epoch.epoch_id + 1, @@ -598,12 +600,10 @@ mod tests { assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none()); - CURRENT_EPOCH - .update(deps.as_mut().storage, |mut e| -> StdResult<_> { - e.state_progress.registered_dealers = 100; - Ok(e) - }) - .unwrap(); + update_epoch(deps.as_mut().storage, &env, |mut e| { + e.state_progress.registered_dealers = 100; + e + }); env.block.time = env .block diff --git a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs index 73d548cdeb..e206fcb379 100644 --- a/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs +++ b/contracts/coconut-dkg/src/epoch_state/transactions/mod.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::{CURRENT_EPOCH, THRESHOLD}; +use crate::epoch_state::storage::{load_current_epoch, save_epoch, THRESHOLD}; use crate::error::ContractError; use crate::state::storage::DKG_ADMIN; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, Storage}; @@ -29,7 +29,7 @@ pub(crate) fn try_initiate_dkg( // only the admin is allowed to kick start the process DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let epoch = CURRENT_EPOCH.load(deps.storage)?; + let epoch = load_current_epoch(deps.storage)?; if !matches!(epoch.state, EpochState::WaitingInitialisation) { return Err(ContractError::AlreadyInitialised); } @@ -37,7 +37,7 @@ pub(crate) fn try_initiate_dkg( // the first exchange won't involve resharing let initial_state = EpochState::PublicKeySubmission { resharing: false }; let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time); - CURRENT_EPOCH.save(deps.storage, &initial_epoch)?; + save_epoch(deps.storage, env.block.height, &initial_epoch)?; Ok(Response::default()) } @@ -49,7 +49,7 @@ pub(crate) fn try_trigger_reset( ) -> Result { // only the admin is allowed to trigger DKG reset DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // only allow reset when the DKG exchange isn't in progress if !current_epoch.state.is_in_progress() { @@ -57,7 +57,7 @@ pub(crate) fn try_trigger_reset( } let next_epoch = current_epoch.next_reset(env.block.time); - CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + save_epoch(deps.storage, env.block.height, &next_epoch)?; reset_dkg_state(deps.storage)?; @@ -71,7 +71,7 @@ pub(crate) fn try_trigger_resharing( ) -> Result { // only the admin is allowed to trigger DKG resharing DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?; - let current_epoch = CURRENT_EPOCH.load(deps.storage)?; + let current_epoch = load_current_epoch(deps.storage)?; // only allow resharing when the DKG exchange isn't in progress if !current_epoch.state.is_in_progress() { @@ -79,7 +79,7 @@ pub(crate) fn try_trigger_resharing( } let next_epoch = current_epoch.next_resharing(env.block.time); - CURRENT_EPOCH.save(deps.storage, &next_epoch)?; + save_epoch(deps.storage, env.block.height, &next_epoch)?; reset_dkg_state(deps.storage)?; @@ -89,6 +89,7 @@ pub(crate) fn try_trigger_resharing( #[cfg(test)] pub(crate) mod tests { use super::*; + use crate::epoch_state::storage::load_current_epoch; use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS}; use cosmwasm_std::testing::{message_info, mock_env}; use cosmwasm_std::Addr; @@ -99,7 +100,7 @@ pub(crate) mod tests { let mut deps = init_contract(); let env = mock_env(); - let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap(); + let initial_epoch_info = load_current_epoch(&deps.storage).unwrap(); assert!(initial_epoch_info.deadline.is_none()); let not_admin = deps.api.addr_make("not an admin"); @@ -125,7 +126,7 @@ pub(crate) mod tests { assert_eq!(ContractError::AlreadyInitialised, res); // sets the correct epoch data - let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap(); + let epoch = load_current_epoch(&deps.storage).unwrap(); assert_eq!(epoch.epoch_id, 0); assert_eq!( epoch.state, diff --git a/contracts/coconut-dkg/src/epoch_state/utils.rs b/contracts/coconut-dkg/src/epoch_state/utils.rs index c468e2af8b..31ce9def13 100644 --- a/contracts/coconut-dkg/src/epoch_state/utils.rs +++ b/contracts/coconut-dkg/src/epoch_state/utils.rs @@ -1,7 +1,7 @@ // Copyright 2022-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use crate::error::ContractError; use crate::state::storage::STATE; use cosmwasm_std::Storage; @@ -52,7 +52,7 @@ pub(crate) fn check_epoch_state( storage: &dyn Storage, against: EpochState, ) -> Result<(), ContractError> { - let epoch_state = CURRENT_EPOCH.load(storage)?.state; + let epoch_state = load_current_epoch(storage)?.state; if epoch_state != against { Err(ContractError::IncorrectEpochState { current_state: epoch_state.to_string(), @@ -66,6 +66,7 @@ pub(crate) fn check_epoch_state( #[cfg(test)] pub(crate) mod test { use super::*; + use crate::epoch_state::storage::save_epoch; use crate::support::tests::helpers::init_contract; use cosmwasm_std::testing::mock_env; use cosmwasm_std::Timestamp; @@ -210,12 +211,12 @@ pub(crate) mod test { let env = mock_env(); for fixed_state in EpochState::first().all_until(EpochState::InProgress) { - CURRENT_EPOCH - .save( - deps.as_mut().storage, - &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), - ) - .unwrap(); + save_epoch( + deps.as_mut().storage, + env.block.height, + &Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time), + ) + .unwrap(); for against_state in EpochState::first().all_until(EpochState::InProgress) { let ret = check_epoch_state(deps.as_mut().storage, against_state); if fixed_state == against_state { diff --git a/contracts/coconut-dkg/src/error.rs b/contracts/coconut-dkg/src/error.rs index c867fec2be..45f626fe87 100644 --- a/contracts/coconut-dkg/src/error.rs +++ b/contracts/coconut-dkg/src/error.rs @@ -10,6 +10,9 @@ use thiserror::Error; /// Custom errors for contract failure conditions. #[derive(Error, Debug, PartialEq)] pub enum ContractError { + #[error("could not perform contract migration: {comment}")] + FailedMigration { comment: String }, + #[error(transparent)] Std(#[from] StdError), diff --git a/contracts/coconut-dkg/src/lib.rs b/contracts/coconut-dkg/src/lib.rs index ca3cf4898a..729ba3cede 100644 --- a/contracts/coconut-dkg/src/lib.rs +++ b/contracts/coconut-dkg/src/lib.rs @@ -11,6 +11,7 @@ mod dealers; mod dealings; mod epoch_state; pub mod error; +mod queued_migrations; mod state; mod support; mod verification_key_shares; diff --git a/contracts/coconut-dkg/src/queued_migrations.rs b/contracts/coconut-dkg/src/queued_migrations.rs new file mode 100644 index 0000000000..54c149a518 --- /dev/null +++ b/contracts/coconut-dkg/src/queued_migrations.rs @@ -0,0 +1,21 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::epoch_state::storage::HISTORICAL_EPOCH; +use crate::error::ContractError; +use cosmwasm_std::{DepsMut, Env}; + +pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> { + if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() { + return Err(ContractError::FailedMigration { + comment: "this migration has already been run before".to_string(), + }); + } + + #[allow(deprecated)] + let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?; + // we won't have information on intermediate states prior to now, but that's not the end of the world + HISTORICAL_EPOCH.save(deps.storage, ¤t, env.block.height)?; + + Ok(()) +} diff --git a/contracts/coconut-dkg/src/support/tests/fixtures.rs b/contracts/coconut-dkg/src/support/tests/fixtures.rs index 04dde90358..87a59af53b 100644 --- a/contracts/coconut-dkg/src/support/tests/fixtures.rs +++ b/contracts/coconut-dkg/src/support/tests/fixtures.rs @@ -12,8 +12,8 @@ pub const TEST_MIX_DENOM: &str = "unym"; pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare { ContractVKShare { - share: format!("share{}", index), - announce_address: format!("localhost:{}", index), + share: format!("share{index}"), + announce_address: format!("localhost:{index}"), node_index: index, owner: Addr::unchecked(owner), epoch_id: index, @@ -43,7 +43,7 @@ pub fn dealing_metadata_fixture() -> Vec { pub fn dealer_details_fixture(api: &MockApi, assigned_index: u64) -> DealerDetails { DealerDetails { - address: api.addr_make(&format!("owner{}", assigned_index)), + address: api.addr_make(&format!("owner{assigned_index}")), bte_public_key_with_proof: "".to_string(), ed25519_identity: "".to_string(), announce_address: "".to_string(), diff --git a/contracts/coconut-dkg/src/support/tests/helpers.rs b/contracts/coconut-dkg/src/support/tests/helpers.rs index d567888577..5ef8eea22f 100644 --- a/contracts/coconut-dkg/src/support/tests/helpers.rs +++ b/contracts/coconut-dkg/src/support/tests/helpers.rs @@ -3,7 +3,7 @@ use crate::contract::instantiate; use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP}; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::load_current_epoch; use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env, MockApi, MockQuerier}; use cosmwasm_std::{ from_json, to_json_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps, @@ -27,7 +27,7 @@ pub const MULTISIG_CONTRACT: &str = addr!("multisig contract address"); pub(crate) static GROUP_MEMBERS: Mutex> = Mutex::new(Vec::new()); pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) { - let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id; + let epoch_id = load_current_epoch(deps.storage).unwrap().epoch_id; let previous = epoch_id - 1; let details = EPOCH_DEALERS_MAP .load(deps.storage, (previous, dealer)) @@ -38,7 +38,7 @@ pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) { } pub fn add_current_dealer(deps: DepsMut<'_>, details: &DealerDetails) { - let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id; + let epoch_id = load_current_epoch(deps.storage).unwrap().epoch_id; insert_dealer(deps, epoch_id, details) } diff --git a/contracts/coconut-dkg/src/verification_key_shares/queries.rs b/contracts/coconut-dkg/src/verification_key_shares/queries.rs index e0254eebdd..f337f07a22 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/queries.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/queries.rs @@ -99,7 +99,7 @@ pub(crate) mod tests { let mut deps = init_contract(); let limit = 2; for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() @@ -115,7 +115,7 @@ pub(crate) mod tests { fn vk_shares_paged_retrieval_has_default_limit() { let mut deps = init_contract(); for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() @@ -136,7 +136,7 @@ pub(crate) mod tests { fn vk_shares_paged_retrieval_has_max_limit() { let mut deps = init_contract(); for n in 0..1000 { - let owner = format!("owner{}", n); + let owner = format!("owner{n}"); let vk_share = vk_share_fixture(&owner, 0); let sender = Addr::unchecked(owner); vk_shares() diff --git a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs index cff0c88232..5c01b12b1a 100644 --- a/contracts/coconut-dkg/src/verification_key_shares/transactions.rs +++ b/contracts/coconut-dkg/src/verification_key_shares/transactions.rs @@ -3,7 +3,7 @@ use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS; use crate::dealers::storage::get_dealer_details; -use crate::epoch_state::storage::CURRENT_EPOCH; +use crate::epoch_state::storage::{load_current_epoch, save_epoch}; use crate::epoch_state::utils::check_epoch_state; use crate::error::ContractError; use crate::state::storage::{MULTISIG, STATE}; @@ -25,7 +25,7 @@ pub fn try_commit_verification_key_share( deps.storage, EpochState::VerificationKeySubmission { resharing }, )?; - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; let epoch_id = epoch.epoch_id; let details = get_dealer_details(deps.storage, &info.sender, epoch_id)?; @@ -43,7 +43,7 @@ pub fn try_commit_verification_key_share( node_index: details.assigned_index, announce_address: details.announce_address, owner: info.sender.clone(), - epoch_id: CURRENT_EPOCH.load(deps.storage)?.epoch_id, + epoch_id: load_current_epoch(deps.storage)?.epoch_id, verified: false, }; vk_shares().save(deps.storage, (&info.sender, epoch_id), &data)?; @@ -60,13 +60,14 @@ pub fn try_commit_verification_key_share( )?; epoch.state_progress.submitted_key_shares += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; Ok(Response::new().add_message(msg)) } pub fn try_verify_verification_key_share( deps: DepsMut<'_>, + env: Env, info: MessageInfo, owner: String, resharing: bool, @@ -77,7 +78,7 @@ pub fn try_verify_verification_key_share( deps.storage, EpochState::VerificationKeyFinalization { resharing }, )?; - let mut epoch = CURRENT_EPOCH.load(deps.storage)?; + let mut epoch = load_current_epoch(deps.storage)?; let epoch_id = epoch.epoch_id; MULTISIG.assert_admin(deps.as_ref(), &info.sender)?; @@ -93,7 +94,7 @@ pub fn try_verify_verification_key_share( })?; epoch.state_progress.verified_keys += 1; - CURRENT_EPOCH.save(deps.storage, &epoch)?; + save_epoch(deps.storage, env.block.height, &epoch)?; Ok(Response::default()) } @@ -259,9 +260,14 @@ mod tests { let owner = deps.api.addr_make("owner").to_string(); let multisig_info = message_info(&Addr::unchecked(MULTISIG_CONTRACT), &[]); - let ret = - try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + info.clone(), + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!( ret, ContractError::IncorrectEpochState { @@ -291,15 +297,26 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + info, + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {})); - let ret = - try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false) - .unwrap_err(); + let ret = try_verify_verification_key_share( + deps.as_mut(), + env.clone(), + multisig_info, + owner.clone(), + false, + ) + .unwrap_err(); assert_eq!( ret, ContractError::NoCommitForOwner { @@ -356,9 +373,15 @@ mod tests { .block .time .plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs); - try_advance_epoch_state(deps.as_mut(), env).unwrap(); + try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap(); - try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.to_string(), false) - .unwrap(); + try_verify_verification_key_share( + deps.as_mut(), + env, + multisig_info, + owner.to_string(), + false, + ) + .unwrap(); } } diff --git a/deny.toml b/deny.toml index 5bd402997e..7a3323ce1b 100644 --- a/deny.toml +++ b/deny.toml @@ -104,6 +104,7 @@ allow = [ "Unicode-3.0", "OpenSSL", "Zlib", + "CDLA-Permissive-2.0", ] # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-install-output.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-install-output.mdx index 487e4027bd..ae026dc659 100644 --- a/documentation/docs/components/operators/snippets/wg-exit-policy-install-output.mdx +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-install-output.mdx @@ -7,20 +7,32 @@ net.ipv6.conf.all.forwarding = 1 net.ipv4.ip_forward = 1 IP forwarding configured successfully. Creating Nym exit policy chain... -Creating chain NYM-EXIT... -Creating chain NYM-EXIT in ip6tables... -Linking NYM-EXIT to FORWARD chain... -Linking NYM-EXIT to IPv6 FORWARD chain... +Chain NYM-EXIT already exists. Flushing it... +Chain NYM-EXIT already exists in ip6tables. Flushing it... +NYM-EXIT all opt -- in * out nymwg 0.0.0.0/0 -> 0.0.0.0/0 +NYM-EXIT all opt in * out nymwg ::/0 -> ::/0 Setting up NAT rules... +MASQUERADE all opt -- in * out ens3 0.0.0.0/0 -> 0.0.0.0/0 IPv4 NAT rule already exists. +MASQUERADE all opt in * out ens3 ::/0 -> ::/0 IPv6 NAT rule already exists. +ACCEPT all opt -- in nymwg out ens3 0.0.0.0/0 -> 0.0.0.0/0 +ACCEPT all opt -- in ens3 out nymwg 0.0.0.0/0 -> 0.0.0.0/0 state RELATED,ESTABLISHED +ACCEPT all opt in nymwg out ens3 ::/0 -> ::/0 +ACCEPT all opt in ens3 out nymwg ::/0 -> ::/0 state RELATED,ESTABLISHED Configuring DNS and ICMP rules... -Added IPv6 ICMP rule (allow ping6). -Added IPv6 DNS rule (UDP). -Added IPv6 DNS rule (TCP). +ACCEPT icmp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 icmptype 8 +ACCEPT icmp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 icmptype 0 +ACCEPT icmpv6 opt in * out * ::/0 -> ::/0 +ACCEPT udp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 udp dpt:53 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:53 +ACCEPT udp opt in * out * ::/0 -> ::/0 udp dpt:53 +ACCEPT tcp opt in * out * ::/0 -> ::/0 tcp dpt:53 Applying Spamhaus blocklist... Downloading exit policy from https://nymtech.net/.wellknown/network-requester/exit-policy.txt Processing 429 blocklist rules... +REJECT all opt -- in * out * 0.0.0.0/0 -> 205.189.71.0/24 reject-with icmp-port-unreachable +REJECT all opt -- in * out * 0.0.0.0/0 -> 205.189.72.0/23 reject-with icmp-port-unreachable Blocklist applied successfully. Applying allowed ports... Adding rules for SILC (Port: 706) @@ -103,6 +115,11 @@ Adding rules for POP3OverTLS (Port: 995) Added: NYM-EXIT tcp port 995 Added: NYM-EXIT udp port 995 Added: NYM-EXIT udp port 995 +Adding rules for DarkFiTor (Port: 25551) + Added: NYM-EXIT tcp port 25551 + Added: NYM-EXIT tcp port 25551 + Added: NYM-EXIT udp port 25551 + Added: NYM-EXIT udp port 25551 Adding rules for MMCC (Port: 5050) Added: NYM-EXIT tcp port 5050 Added: NYM-EXIT tcp port 5050 @@ -268,6 +285,11 @@ Adding rules for Mumble (Port: 64738) Added: NYM-EXIT tcp port 64738 Added: NYM-EXIT udp port 64738 Added: NYM-EXIT udp port 64738 +Adding rules for DarkFi (Port: 26661) + Added: NYM-EXIT tcp port 26661 + Added: NYM-EXIT tcp port 26661 + Added: NYM-EXIT udp port 26661 + Added: NYM-EXIT udp port 26661 Adding rules for PPTP (Port: 1723) Added: NYM-EXIT tcp port 1723 Added: NYM-EXIT tcp port 1723 @@ -333,6 +355,11 @@ Adding rules for Kpasswd (Port: 464) Added: NYM-EXIT tcp port 464 Added: NYM-EXIT udp port 464 Added: NYM-EXIT udp port 464 +Adding rules for MoneroRPC (Port: 18089) + Added: NYM-EXIT tcp port 18089 + Added: NYM-EXIT tcp port 18089 + Added: NYM-EXIT udp port 18089 + Added: NYM-EXIT udp port 18089 Adding rules for RemoteHTTPS (Port: 981) Added: NYM-EXIT tcp port 981 Added: NYM-EXIT tcp port 981 @@ -398,6 +425,11 @@ Adding rules for GroupWise (Port: 1677) Added: NYM-EXIT tcp port 1677 Added: NYM-EXIT udp port 1677 Added: NYM-EXIT udp port 1677 +Adding rules for Monero (Port: 18080-18081) + Added: NYM-EXIT tcp port range 18080:18081 + Added: NYM-EXIT tcp port range 18080:18081 + Added: NYM-EXIT udp port range 18080:18081 + Added: NYM-EXIT udp port range 18080:18081 Adding rules for EnsimControlPanel (Port: 19638) Added: NYM-EXIT tcp port 19638 Added: NYM-EXIT tcp port 19638 diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-status-output.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-status-output.mdx index 7a1eaa5acf..8e8293a865 100644 --- a/documentation/docs/components/operators/snippets/wg-exit-policy-status-output.mdx +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-status-output.mdx @@ -1,807 +1,795 @@ ```console -ESC[0;33mNym Exit Policy Status:ESC[0m -ESC[0;33m----------------------ESC[0m -ESC[0;32mNetwork Device:ESC[0m ens3 -ESC[0;32mWireguard Interface:ESC[0m nymwg +Nym Exit Policy Status: +---------------------- +Network Device: eth0 +Wireguard Interface: nymwg -ESC[0;33mInterface Details:ESC[0m -12: nymwg: mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 - link/none +Interface Details: +87: nymwg: mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 + link/none -ESC[0;33mIP Addresses:ESC[0m -12: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 +IP Addresses: +87: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 inet 10.1.0.1/32 brd 10.1.0.1 scope global nymwg valid_lft forever preferred_lft forever -12: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 - inet6 fc01::1/112 scope global +87: nymwg: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 + inet6 fc01::1/112 scope global valid_lft forever preferred_lft forever -ESC[0;33mIptables Chains:ESC[0m +Iptables Chains: IPv4 Chain: Chain NYM-EXIT (1 references) - pkts bytes target prot opt in out source destination - 0 0 REJECT 0 -- * * 0.0.0.0/0 5.188.10.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 5.188.11.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 31.132.36.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 31.184.237.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 37.9.42.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 43.229.52.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 45.9.148.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 45.43.128.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 45.142.120.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.112.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.120.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.148.127.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.173.208.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 79.110.22.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.121.39.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.193.75.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.12.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.81.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.82.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.83.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.164.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.216.3.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.220.163.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.200.248.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.90.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.91.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.243.93.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.234.99.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 103.99.0.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 103.215.80.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 103.239.28.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.166.96.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.207.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.233.0.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.239.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.243.192.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.247.96.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.250.192.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.250.224.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 107.182.112.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 107.190.160.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 141.136.22.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 150.129.40.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 159.174.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 162.222.128.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 162.249.20.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 163.53.247.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 166.117.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 167.74.0.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 167.160.96.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 168.64.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 168.76.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 168.129.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 169.239.152.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 170.114.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 172.98.0.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 174.136.192.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 176.121.14.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.97.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.100.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.159.107.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.192.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.193.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.14.195.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.21.8.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.39.8.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.71.0.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.77.248.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.116.172.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.116.175.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.124.56.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.129.8.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.130.36.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.130.40.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.140.53.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.220.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.222.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.143.223.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.146.168.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.165.153.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.193.90.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.29.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.30.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.244.31.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 188.247.230.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.26.25.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.31.212.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.175.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.176.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.43.184.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.161.80.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.251.231.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 193.228.91.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.97.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.98.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 194.5.99.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 195.182.57.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 196.45.120.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 196.61.192.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 196.196.8.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 196.199.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 197.231.208.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.20.16.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.45.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.56.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.151.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.151.152.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.178.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.183.32.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.186.25.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.187.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.200.0.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.200.8.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 198.206.140.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.5.152.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.34.128.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.84.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.89.16.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.120.163.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.166.200.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.185.192.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.196.192.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.198.160.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.212.96.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.223.0.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.241.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.249.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.253.224.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 199.254.32.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.19.38.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.44.224.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.52.96.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.87.199.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.107.208.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.126.244.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.130.16.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.147.64.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.232.0.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.144.0.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.148.192.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.151.128.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.159.45.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.172.244.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.189.71.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.189.72.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.203.0.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.233.224.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 205.236.189.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.124.104.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.195.224.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.197.165.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.209.80.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.224.160.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.226.0.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.226.32.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 206.227.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 207.22.192.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 207.45.224.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 207.110.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 207.110.128.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 209.66.128.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 216.179.128.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 217.8.116.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 217.8.117.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 223.169.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 223.254.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 42.4.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 68.119.232.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 68.215.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 69.244.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 70.111.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 70.126.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 112.78.2.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 195.20.40.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 14.160.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 27.2.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 27.106.108.128/25 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 37.236.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.100.21.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 39.32.0.0/11 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 41.190.2.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 41.190.30.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 45.116.232.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.118.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.161.9.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 60.184.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 62.44.134.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 78.85.40.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 79.11.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 79.108.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 81.93.93.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 83.24.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 83.149.19.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 84.18.126.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.198.140.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.116.176.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.227.224.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.241.88.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 89.114.108.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.196.250.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 93.122.192.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 95.0.60.160/27 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 95.110.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 89.189.152.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 103.26.246.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 106.51.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.124.0.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.124.16.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.126.128.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.175.6.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 111.125.108.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 112.101.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.80.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.128.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 114.96.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 114.115.128.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 115.72.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 118.20.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.24.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 125.167.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 139.5.157.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 146.185.223.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 154.68.4.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 177.55.154.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 177.125.30.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 177.224.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.135.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 179.5.103.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 181.67.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 181.174.101.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 182.69.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 182.160.100.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 182.184.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 182.253.162.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 183.82.128.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 183.128.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.36.88.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.150.15.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 185.172.86.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 186.179.100.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 189.216.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 190.235.110.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 190.239.190.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.64.121.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 193.34.141.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 193.188.254.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 197.229.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 201.148.126.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 202.136.88.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 200.121.192.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 220.164.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 221.228.192.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 24.0.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 27.184.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.0.0.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 58.53.128.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 59.92.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 60.52.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 60.176.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 60.215.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 61.163.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 62.194.131.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 64.175.32.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 67.116.236.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 67.121.120.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 67.124.36.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 68.62.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 69.112.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 71.56.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 76.112.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 78.97.32.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 80.108.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 81.240.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 82.72.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 82.169.28.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 84.127.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 84.144.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 84.220.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.48.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.54.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.85.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.86.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.176.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 89.217.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.176.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.182.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 92.0.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 92.112.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 92.128.0.0/12 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.128.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 110.212.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 111.85.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 112.224.0.0/11 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.70.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.89.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.111.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.224.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 113.240.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 114.246.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 114.248.80.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 115.60.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 115.213.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 116.238.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 117.22.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 117.136.0.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 118.80.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 118.168.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 120.0.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 120.68.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 121.29.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 122.169.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 122.173.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.67.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.101.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.112.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.134.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.161.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.174.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.188.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 123.244.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 124.89.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 124.128.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 124.134.0.0/17 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 124.94.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 125.93.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 125.125.176.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 125.224.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 150.70.75.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 166.204.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.191.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.125.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 183.91.2.0/23 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 183.184.0.0/13 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 188.23.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 188.98.0.0/15 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 189.64.0.0/14 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 201.53.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 201.82.64.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 212.56.64.0/18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 218.202.219.0/24 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 220.152.128.0/22 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 220.178.0.0/19 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 221.11.32.0/20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 222.183.0.0/16 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 222.240.216.0/21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 82.165.159.132/31 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 91.208.144.164 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 209.182.193.155 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 213.205.38.29 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 5.79.71.205 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 5.79.71.225 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.129.41 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.129.213 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.144.42 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.147.11 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.151.95 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.153.71 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.153.115 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.168.194 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.169.101 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.170.84 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.174.35 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.179.9 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.182.164 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.184.75 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.186.110 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.186.114 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.188.186 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 38.229.191.189 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 46.244.21.4 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 50.21.181.152 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 50.63.202.35 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 52.5.245.208 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 64.71.166.50 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 64.71.188.178 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 67.215.255.139 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 74.200.48.169 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.153.9 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.164.166 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 74.208.64.191 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.17.31.122 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 85.17.31.82 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.146 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.149.145 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.149.153 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.112 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.18.141 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.153 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.154 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.190.157 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.20.192 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.24.200 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.253.18 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 87.106.26.9 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 95.211.230.75 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.42.225.122 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 104.244.14.252 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 109.70.26.37 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 144.217.74.156 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 146.148.124.166 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 148.81.111.111 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 151.80.148.103 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 176.58.104.168 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.202 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.211 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.203.226 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 178.162.217.107 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 184.105.76.250 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 184.105.192.2 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.0.72.20 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.0.72.21 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.169.69.25 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.42.116.41 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 192.42.119.41 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 193.166.255.170 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 193.166.255.171 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 204.11.56.48 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 208.91.197.46 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.93 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.116 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 212.227.20.164 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 213.165.83.176 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.135.114 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.185.162 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 216.218.208.114 reject-with icmp-port-unreachable - 0 0 REJECT 0 -- * * 0.0.0.0/0 216.66.15.109 reject-with icmp-port-unreachable - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:706 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:706 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5432 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5432 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2082:2083 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2082:2083 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8232:8233 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8232:8233 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1500 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1500 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:123 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:123 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1293 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1293 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:11371 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:11371 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:443 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:110 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:110 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1194 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1194 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3074 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3074 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1521 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1521 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:2049 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:2049 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:88 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:88 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:995 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:995 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5050 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5050 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:43 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:43 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:991 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:991 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:143 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:143 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5228 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5228 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:445 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:445 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1755 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1755 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:993 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:993 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9001 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9001 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5222:5223 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5222:5223 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:20:21 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:20:21 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:60000:61000 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:60000:61000 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2102:2104 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2102:2104 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:873 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:873 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:27000:27050 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:27000:27050 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9418 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9418 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1863 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1863 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8087:8088 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8087:8088 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9030 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9030 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4643 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4643 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9339 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9339 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:902:904 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:902:904 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1533 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1533 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2095:2096 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2095:2096 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5190 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5190 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:749 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:749 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4321 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4321 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10000 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:10000 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:53 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19294 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19294 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:220 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:220 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8332:8333 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8332:8333 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:64738 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:64738 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1723 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1723 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8443 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8443 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8888 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8888 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2086:2087 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2086:2087 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9735 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9735 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:554 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:554 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:853 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:22 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8082 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8082 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:992 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:992 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:25565 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:25565 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3690 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3690 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:464 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:464 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:981 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:981 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9053 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9053 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:50002 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:50002 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9443 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9443 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:389 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:389 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:80:81 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:80:81 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:27017 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:27017 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5000:5005 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5000:5005 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1433 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1433 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8883 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8883 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3306 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8767 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8767 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1677 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1677 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19638 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19638 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1220 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1220 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:79 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:79 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:989:990 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:989:990 - 0 0 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:636 - 0 0 ACCEPT 17 -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:636 - 0 0 REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable + pkts bytes target prot opt in out source destination + 0 0 REJECT all -- * * 0.0.0.0/0 5.188.10.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 5.188.11.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 31.132.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 31.184.237.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 37.9.42.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 43.229.52.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 45.9.148.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 45.43.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 45.142.120.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.148.112.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.148.120.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.148.127.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.173.208.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 79.110.22.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.121.39.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.193.75.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.12.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.81.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.82.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.83.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.164.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.216.3.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.220.163.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.200.248.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.243.90.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.243.91.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.243.93.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.234.99.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 103.99.0.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 103.215.80.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 103.239.28.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.166.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.207.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.233.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.239.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.243.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.247.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.250.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.250.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 107.182.112.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 107.190.160.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 141.136.22.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 150.129.40.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 159.174.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 162.222.128.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 162.249.20.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 163.53.247.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 166.117.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 167.74.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 167.160.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 168.64.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 168.76.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 168.129.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 169.239.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 170.114.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 172.98.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 174.136.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 176.121.14.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.159.97.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.159.100.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.159.107.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.14.192.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.14.193.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.14.195.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.21.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.39.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.71.0.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.77.248.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.116.172.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.116.175.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.124.56.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.129.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.130.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.130.40.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.140.53.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.143.220.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.143.222.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.143.223.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.146.168.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.165.153.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.193.90.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.244.29.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.244.30.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.244.31.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 188.247.230.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.26.25.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.31.212.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.43.175.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.43.176.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.43.184.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.161.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.251.231.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 193.228.91.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 194.5.97.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 194.5.98.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 194.5.99.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 195.182.57.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 196.45.120.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 196.61.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 196.196.8.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 196.199.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 197.231.208.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.20.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.45.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.56.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.151.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.151.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.178.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.183.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.186.25.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.187.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.200.0.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.200.8.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 198.206.140.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.5.152.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.34.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.84.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.89.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.120.163.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.166.200.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.185.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.196.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.198.160.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.212.96.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.223.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.241.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.249.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.253.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 199.254.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.19.38.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.44.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.52.96.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.87.199.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.107.208.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.126.244.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.130.16.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.147.64.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.232.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.144.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.148.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.151.128.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.159.45.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.172.244.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.189.71.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.189.72.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.203.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.233.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 205.236.189.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.124.104.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.195.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.197.165.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.209.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.224.160.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.226.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.226.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 206.227.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 207.22.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 207.45.224.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 207.110.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 207.110.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 209.66.128.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 216.179.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 217.8.116.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 217.8.117.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 223.169.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 223.254.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 42.4.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 68.119.232.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 68.215.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 69.244.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 70.111.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 70.126.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 112.78.2.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 195.20.40.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 14.160.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 27.2.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 27.106.108.128/25 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 37.236.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.100.21.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 39.32.0.0/11 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 41.190.2.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 41.190.30.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 45.116.232.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.118.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.161.9.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 60.184.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 62.44.134.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 78.85.40.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 79.11.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 79.108.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 81.93.93.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 83.24.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 83.149.19.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 84.18.126.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.198.140.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.116.176.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.227.224.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.241.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 89.114.108.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.196.250.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 93.122.192.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 95.0.60.160/27 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 95.110.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 89.189.152.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 103.26.246.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 106.51.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.124.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.124.16.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.126.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.175.6.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 111.125.108.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 112.101.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.80.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.128.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 114.96.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 114.115.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 115.72.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 118.20.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.24.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 125.167.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 139.5.157.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 146.185.223.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 154.68.4.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 177.55.154.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 177.125.30.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 177.224.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.135.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 179.5.103.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 181.67.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 181.174.101.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 182.69.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 182.160.100.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 182.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 182.253.162.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 183.82.128.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 183.128.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.36.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.150.15.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 185.172.86.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 186.179.100.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 189.216.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 190.235.110.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 190.239.190.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.64.121.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 193.34.141.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 193.188.254.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 197.229.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 201.148.126.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 202.136.88.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 200.121.192.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 220.164.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 221.228.192.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 24.0.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 27.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.0.0.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 58.53.128.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 59.92.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 60.52.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 60.176.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 60.215.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 61.163.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 62.194.131.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 64.175.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 67.116.236.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 67.121.120.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 67.124.36.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 68.62.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 69.112.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 71.56.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 76.112.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 78.97.32.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 80.108.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 81.240.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 82.72.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 82.169.28.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 84.127.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 84.144.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 84.220.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.48.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.54.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.85.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.86.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.176.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 89.217.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.176.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.182.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 92.0.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 92.112.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 92.128.0.0/12 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.128.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 110.212.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 111.85.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 112.224.0.0/11 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.70.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.89.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.111.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.224.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 113.240.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 114.246.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 114.248.80.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 115.60.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 115.213.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 116.238.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 117.22.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 117.136.0.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 118.80.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 118.168.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 120.0.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 120.68.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 121.29.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 122.169.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 122.173.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.67.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.101.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.112.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.134.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.161.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.174.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.188.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 123.244.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 124.89.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 124.128.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 124.134.0.0/17 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 124.94.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 125.93.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 125.125.176.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 125.224.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 150.70.75.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 166.204.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.191.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.125.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 183.91.2.0/23 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 183.184.0.0/13 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 188.23.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 188.98.0.0/15 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 189.64.0.0/14 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 201.53.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 201.82.64.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 212.56.64.0/18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 218.202.219.0/24 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 220.152.128.0/22 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 220.178.0.0/19 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 221.11.32.0/20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 222.183.0.0/16 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 222.240.216.0/21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 82.165.159.132/31 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 91.208.144.164 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 209.182.193.155 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 213.205.38.29 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 5.79.71.205 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 5.79.71.225 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.129.41 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.129.213 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.144.42 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.147.11 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.151.95 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.153.71 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.153.115 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.168.194 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.169.101 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.170.84 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.174.35 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.179.9 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.182.164 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.184.75 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.186.110 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.186.114 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.188.186 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 38.229.191.189 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 46.244.21.4 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 50.21.181.152 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 50.63.202.35 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 52.5.245.208 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 64.71.166.50 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 64.71.188.178 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 67.215.255.139 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 74.200.48.169 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 74.208.153.9 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 74.208.164.166 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 74.208.64.191 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.17.31.122 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 85.17.31.82 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.18.146 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.149.145 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.149.153 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.18.112 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.18.141 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.190.153 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.190.154 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.190.157 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.20.192 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.24.200 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.253.18 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 87.106.26.9 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 95.211.230.75 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.42.225.122 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 104.244.14.252 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 109.70.26.37 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 144.217.74.156 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 146.148.124.166 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 148.81.111.111 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 151.80.148.103 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 176.58.104.168 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.162.203.202 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.162.203.211 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.162.203.226 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 178.162.217.107 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 184.105.76.250 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 184.105.192.2 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.0.72.20 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.0.72.21 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.169.69.25 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.42.116.41 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 192.42.119.41 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 193.166.255.170 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 193.166.255.171 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 204.11.56.48 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 208.91.197.46 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 212.227.20.93 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 212.227.20.116 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 212.227.20.164 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 213.165.83.176 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 216.218.135.114 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 216.218.185.162 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 216.218.208.114 reject-with icmp-port-unreachable + 0 0 REJECT all -- * * 0.0.0.0/0 216.66.15.109 reject-with icmp-port-unreachable + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:706 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:706 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5432 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5432 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2082:2083 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2082:2083 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8232:8233 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8232:8233 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1500 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1500 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:123 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:123 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1293 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1293 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:11371 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:11371 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:443 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:110 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:110 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1194 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1194 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3074 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3074 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1521 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1521 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:2049 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:2049 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:88 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:88 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:995 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:995 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:25551 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:25551 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5050 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5050 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:43 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:43 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:991 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:991 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:143 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:143 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5228 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5228 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:445 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:445 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1755 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1755 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:993 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:993 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9001 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9001 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5222:5223 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5222:5223 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:20:21 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:20:21 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:60000:61000 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:60000:61000 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2102:2104 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2102:2104 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:873 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:873 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:27000:27050 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:27000:27050 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9418 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9418 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1863 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1863 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8087:8088 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8087:8088 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9030 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9030 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4643 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4643 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9339 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9339 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:902:904 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:902:904 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1533 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1533 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2095:2096 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2095:2096 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5190 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:5190 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:749 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:749 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:4321 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:4321 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10000 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:10000 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:53 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:53 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19294 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19294 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:220 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:220 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:8332:8333 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:8332:8333 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:64738 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:64738 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:26661 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:26661 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1723 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1723 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8443 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8443 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8888 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8888 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:2086:2087 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:2086:2087 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9735 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9735 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:554 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:554 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:853 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:853 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:22 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8082 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8082 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:992 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:992 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:25565 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:25565 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3690 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3690 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:464 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:464 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:18089 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:18089 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:981 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:981 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9053 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9053 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:50002 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:50002 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9443 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:9443 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:389 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:389 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:80:81 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:80:81 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:27017 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:27017 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:5000:5005 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:5000:5005 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1433 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1433 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8883 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8883 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:3306 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8767 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:8767 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1677 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1677 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:18080:18081 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:18080:18081 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:19638 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:19638 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:1220 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:1220 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:79 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:79 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpts:989:990 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpts:989:990 + 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:636 + 0 0 ACCEPT udp -- * * 0.0.0.0/0 0.0.0.0/0 udp dpt:636 + 0 0 REJECT all -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-port-unreachable IPv6 Chain: Chain NYM-EXIT (1 references) - pkts bytes target prot opt in out source destination - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:706 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:706 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5432 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5432 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2082:2083 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2082:2083 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8232:8233 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8232:8233 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1500 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1500 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:123 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:123 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1293 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1293 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:11371 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:11371 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:443 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:443 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:110 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:110 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1194 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1194 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3074 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3074 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1521 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1521 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:2049 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:2049 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:88 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:88 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:995 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:995 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5050 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5050 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:43 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:43 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:991 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:991 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:143 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:143 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5228 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5228 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:445 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:445 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1755 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1755 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:993 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:993 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9001 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9001 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5222:5223 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5222:5223 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:20:21 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:20:21 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:60000:61000 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:60000:61000 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2102:2104 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2102:2104 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:873 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:873 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:27000:27050 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:27000:27050 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9418 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9418 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1863 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1863 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8087:8088 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8087:8088 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9030 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9030 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4643 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4643 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9339 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9339 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:902:904 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:902:904 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1533 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1533 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5222:5223 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5222:5223 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:20:21 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:20:21 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:60000:61000 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:60000:61000 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2102:2104 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2102:2104 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:873 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:873 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:27000:27050 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:27000:27050 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9418 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9418 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1863 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1863 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8087:8088 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8087:8088 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9030 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9030 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4643 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4643 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9339 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9339 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:902:904 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:902:904 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1533 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1533 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2095:2096 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2095:2096 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:5190 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:5190 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:749 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:749 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:4321 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:4321 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:10000 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:10000 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:53 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:53 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:19294 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:19294 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:220 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:220 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:8332:8333 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:8332:8333 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:64738 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:64738 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1723 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1723 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8443 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8443 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8888 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8888 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:2086:2087 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:2086:2087 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9735 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9735 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:554 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:554 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:853 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:853 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:22 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:22 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8082 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8082 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:992 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:992 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:25565 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:25565 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3690 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3690 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:464 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:464 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:981 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:981 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9053 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9053 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:50002 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:50002 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:9443 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:9443 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:389 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:389 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:80:81 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:80:81 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:27017 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:27017 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:5000:5005 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:5000:5005 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1433 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1433 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8883 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8883 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:3306 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:3306 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:8767 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:8767 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1677 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1677 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:19638 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:19638 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:1220 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:1220 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:79 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:79 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpts:989:990 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpts:989:990 - 0 0 ACCEPT 6 -- * * ::/0 ::/0 tcp dpt:636 - 0 0 ACCEPT 17 -- * * ::/0 ::/0 udp dpt:636 - 0 0 REJECT 0 -- * * ::/0 ::/0 reject-with icmp6-port-unreachable + pkts bytes target prot opt in out source destination + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:706 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:706 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:5432 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:5432 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:2082:2083 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:2082:2083 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:8232:8233 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:8232:8233 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1500 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1500 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:123 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:123 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1293 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1293 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:11371 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:11371 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:443 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:443 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:110 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:110 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1194 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1194 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:3074 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:3074 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1521 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1521 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:2049 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:2049 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:88 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:88 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:995 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:995 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:25551 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:25551 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:5050 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:5050 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:43 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:43 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:991 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:991 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:143 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:143 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:5228 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:5228 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:445 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:445 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1755 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1755 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:993 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:993 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9001 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9001 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:5222:5223 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:5222:5223 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:20:21 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:20:21 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:60000:61000 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:60000:61000 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:2102:2104 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:2102:2104 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:873 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:873 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:27000:27050 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:27000:27050 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9418 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9418 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1863 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1863 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:8087:8088 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:8087:8088 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9030 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9030 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:4643 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:4643 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9339 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9339 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:902:904 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:902:904 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1533 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1533 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:2095:2096 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:2095:2096 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:5190 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:5190 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:749 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:749 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:4321 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:4321 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:10000 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:10000 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:53 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:53 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:19294 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:19294 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:220 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:220 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:8332:8333 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:8332:8333 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:64738 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:64738 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:26661 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:26661 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1723 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1723 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:8443 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:8443 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:8888 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:8888 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:2086:2087 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:2086:2087 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9735 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9735 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:554 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:554 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:853 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:853 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:22 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:22 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:8082 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:8082 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:992 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:992 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:25565 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:25565 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:3690 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:3690 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:464 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:464 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:18089 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:18089 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:981 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:981 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9053 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9053 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:50002 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:50002 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:9443 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:9443 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:389 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:389 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:80:81 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:80:81 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:27017 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:27017 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:5000:5005 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:5000:5005 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1433 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1433 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:8883 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:8883 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:3306 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:3306 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:8767 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:8767 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1677 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1677 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:18080:18081 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:18080:18081 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:19638 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:19638 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:1220 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:1220 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:79 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:79 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpts:989:990 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpts:989:990 + 0 0 ACCEPT tcp * * ::/0 ::/0 tcp dpt:636 + 0 0 ACCEPT udp * * ::/0 ::/0 udp dpt:636 + 0 0 REJECT all * * ::/0 ::/0 reject-with icmp6-port-unreachable -ESC[0;33mIP Forwarding:ESC[0m +IP Forwarding: IPv4: 1 IPv6: 1 ``` diff --git a/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx b/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx index c7ee424647..184f858c83 100644 --- a/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx +++ b/documentation/docs/components/operators/snippets/wg-exit-policy-test-output.mdx @@ -2,41 +2,61 @@ Running Nym Exit Policy Verification Tests... Testing Port Range Rules... Testing FTP tcp port range 20-21 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:20:21 ✓ Rule exists: NYM-EXIT tcp port range 20:21 Testing HTTP tcp port range 80-81 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:80:81 ✓ Rule exists: NYM-EXIT tcp port range 80:81 Testing CPanel tcp port range 2082-2083 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:2082:2083 ✓ Rule exists: NYM-EXIT tcp port range 2082:2083 Testing XMPP tcp port range 5222-5223 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:5222:5223 ✓ Rule exists: NYM-EXIT tcp port range 5222:5223 Testing Steam (sampling) tcp port range 27000-27050 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:27000:27050 ✓ Rule exists: NYM-EXIT tcp port range 27000:27050 Testing FTP over TLS tcp port range 989-990 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:989:990 ✓ Rule exists: NYM-EXIT tcp port range 989:990 Testing RTP/VoIP tcp port range 5000-5005 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:5000:5005 ✓ Rule exists: NYM-EXIT tcp port range 5000:5005 Testing Simplify Media tcp port range 8087-8088 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:8087:8088 ✓ Rule exists: NYM-EXIT tcp port range 8087:8088 Testing Zcash tcp port range 8232-8233 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:8232:8233 ✓ Rule exists: NYM-EXIT tcp port range 8232:8233 Testing Bitcoin tcp port range 8332-8333 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:8332:8333 ✓ Rule exists: NYM-EXIT tcp port range 8332:8333 +Testing Monero tcp port range 18080-18081 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpts:18080:18081 +✓ Rule exists: NYM-EXIT tcp port range 18080:18081 Test test_port_range_rules PASSED Testing Critical Service Rules... +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:22 ✓ Rule exists: NYM-EXIT tcp port 22 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:53 ✓ Rule exists: NYM-EXIT tcp port 53 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:443 ✓ Rule exists: NYM-EXIT tcp port 443 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:853 ✓ Rule exists: NYM-EXIT tcp port 853 +ACCEPT tcp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 tcp dpt:1194 ✓ Rule exists: NYM-EXIT tcp port 1194 +ACCEPT udp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 udp dpt:53 ✓ Rule exists: NYM-EXIT udp port 53 +ACCEPT udp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 udp dpt:123 ✓ Rule exists: NYM-EXIT udp port 123 +ACCEPT udp opt -- in * out * 0.0.0.0/0 -> 0.0.0.0/0 udp dpt:1194 ✓ Rule exists: NYM-EXIT udp port 1194 Relevant existing rules for HTTP (port 80): Test test_critical_services PASSED This test takes some time, do not quit the process Testing Default Reject Rule... - ✓ Default REJECT rule exists Test test_default_reject_rule PASSED diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md index fe3aa06e04..3dec107d5c 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md @@ -1 +1 @@ -0.69% +0.73% diff --git a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md index 961a5f20a4..322660801f 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md @@ -1 +1 @@ -41.235 +39.437 diff --git a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md index 9a18ad5a74..7a436498af 100644 --- a/documentation/docs/components/outputs/api-scraping-outputs/time-now.md +++ b/documentation/docs/components/outputs/api-scraping-outputs/time-now.md @@ -1 +1 @@ -Friday, July 4th 2025, 14:45:02 UTC +Wednesday, July 30th 2025, 09:32:50 UTC \ No newline at end of file diff --git a/documentation/docs/components/outputs/command-outputs/nym-node-help.md b/documentation/docs/components/outputs/command-outputs/nym-node-help.md index 62836e167d..f1af88721d 100644 --- a/documentation/docs/components/outputs/command-outputs/nym-node-help.md +++ b/documentation/docs/components/outputs/command-outputs/nym-node-help.md @@ -2,13 +2,14 @@ Usage: nym-node [OPTIONS] Commands: - build-info Show build information of this binary - bonding-information Show bonding information of this node depending on its currently selected mode - node-details Show details of this node - migrate Attempt to migrate an existing mixnode or gateway into a nym-node - run Start this nym-node - sign Use identity key of this node to sign provided message - help Print this message or the help of the given subcommand(s) + build-info Show build information of this binary + bonding-information Show bonding information of this node depending on its currently selected mode + node-details Show details of this node + migrate Attempt to migrate an existing mixnode or gateway into a nym-node + run Start this nym-node + sign Use identity key of this node to sign provided message + unsafe-reset-sphinx-keys UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network state + help Print this message or the help of the given subcommand(s) Options: -c, --config-env-file diff --git a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md index b1d4cec752..e5b10cc72b 100644 --- a/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md +++ b/documentation/docs/components/outputs/csv2md-outputs/isp-sheet.md @@ -1,21 +1,44 @@ -| **ISP** | **Locations** | **Public IPv6** | **Crypto Payments** | **Comments** | **Last Updated** | -|:------------------------------------------------|:---------------------------------------------------------------------------------------------------------|:-------------------------------------|:---------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------| -| [AlexHost](https://alexhost.com) | Moldova, Bulgaria, Sweden, Netherlands | Yes, on by default | Yes | They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR) | 07/2024 | -| [BitLaunch](https://bitlaunch.io) | Canada, USA, UK | No | Yes | Expensive. Digial Ocean through BitLanch has IPv6 | 05/2024 | -| [Cherry Servers](https://www.cherryservers.com) | Lithuania, Netherlands, USA, Singapore | No | Yes | Issued IP doesn’t match the location offered by the provider. | 05/2024 | -| [Flokinet](https://flokinet.is) | Netherlands, Iceland, Romania,France | Yes, needs a ticket and custom setup | yes, including XMR | Very slow customer support | 05/2024 | -| [HostSailor](https://hostsailor.com) | USA | Yes, based on ticket | Yes | The IPv6 setup needs custom research and is not documented | 05/2024 | -| [Hostiko](https://hostiko.com.ua) | Ukraine, Germany | Yes, on by default | Yes | Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. | 07/2024 | -| [Hostinger](https://hostinger.com) | France, Lithuania, India, USA, Brazil | Yes, out of the box | Yes | Crypto payments must be done per each server monthly or annually. | 05/2024 | -| [Hostslick](https://hostslick.com) | Netherlands, Germany | Yes, on by default | Yes | Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node | 07/2024 | -| [Incognet](https://incognet.io) | Netherlands and USA | Yes, on by default | Yes | They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits | 07/2024 | -| [IsHosting](https://ishosting.com/en) | Brazil, Netherlands | Yes, based on ticket | Yes | Expensive | 05/2024 | -| [Linode](https://linode.com) | USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy | Yes out of the box | No, only through [BitLAunch](https://bitlaunch.io) | IPv6 sometimes need to be re-added in Networking tab, no reboot needed | 05/2024 | -| [LiteServer](https://liteserver.nl) | Netherlands | Yes, on by default | Yes | Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal | 07/2024 | -| [Mevspace](https://mevspace.com) | Poland | Yes, on by default | Yes | Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff | 07/2024 | -| [Misaka](https://www.misaka.io/) | South Africa | Yes, native support | No | Very Expensive | 05/2024 | -| [Njalla](https://nja.la) | Sweden | Yes | Yes | Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. | 05/2024 | -| [RDP](https://rdp.sh) | Netherlands, USA, Poland | Yes, on by default | Yes | German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node. | 07/2024 | -| [TerraHost](https://terrahost.no) | Norway | Yes, on by default | Yes | Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser | 07/2024 | -| [iHostArt](https://ihostart.com) | Romania | Yes, on by default | Yes | Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware. | 07/2024 | -| [vSys Host](https://vsys.host) | Ukraine, Netherlands, USA | Yes, on by default | Yes | Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check. | 07/2024 | +| **ISP** | **Locations** | **Public IPv6** | **Crypto Payments** | **Comments** | **Last Updated** | +|:---------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------|:---------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------| +| [AlexHost](https://alexhost.com) | Moldova, Bulgaria, Sweden, Netherlands | Yes, on by default | Yes | They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR) | 07/2024 | +| [AmeriNoc](https://www.amerinoc.com) | USA | Yes | nan | nan | 07/2025 | +| [BitLaunch](https://bitlaunch.io) | Canada, USA, UK | No | Yes | Expensive. Digial Ocean through BitLanch has IPv6 | 05/2024 | +| [Cherry Servers](https://www.cherryservers.com) | Lithuania, Netherlands, USA, Singapore | No | Yes | Issued IP doesn’t match the location offered by the provider. | 05/2024 | +| [Colocall](https://www.colocall.net/) | Ukraine | Yes | nan | 07/2025 | nan | +| [DataPacket](https://www.datapacket.com/pricing) | NL, GR, SK, BE, RO, HU, DK, IE, DE, UA, PT, GB, ES, FR, IT, NO, CZ, BG, SE, AT, PL, HR, CH, USA, CO, AR, PE, MX, CL, TR, ZA, NG, IL, HK, AU, SG, JP | Yes | nan | nan | 07/2025 | +| [Dataclub](https://www.dataclub.eu/) | Latvia, Sweden, Netherlands | Yes | nan | nan | 07/2027 | +| [Flokinet](https://flokinet.is) | Netherlands, Iceland, Romania,France | Yes, needs a ticket and custom setup | yes, including XMR | Very slow customer support | 05/2024 | +| [FranTech](https://my.frantech.ca) | USA | Yes | nan | nan | 07/2025 | +| [Fsit](https://www.fsit.com/server/vps-vserver-kvm) | Swiss | Yes | Yes | nan | 07/2025 | +| [HostSailor](https://hostsailor.com) | USA | Yes, based on ticket | Yes | The IPv6 setup needs custom research and is not documented | 05/2024 | +| [Hostiko](https://hostiko.com.ua) | Ukraine, Germany | Yes, on by default | Yes | Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. | 07/2024 | +| [Hostinger](https://hostinger.com) | France, Lithuania, India, USA, Brazil | Yes, out of the box | Yes | Not fast enough, Crypto payments must be done per each server monthly or annually. | 07/2025 | +| [Hostroyale](https://hostroyale.com/hosting/dedicated-server/) | Various countries with different pricing | nan | Yes | nan | 07/2025 | +| [Hostslick](https://hostslick.com) | Netherlands, Germany | Yes, on by default | Yes | Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node | 07/2024 | +| [Incognet](https://incognet.io) | Netherlands and USA | Yes, on by default | Yes | They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits | 07/2024 | +| [Incognet](https://incognet.io/kansas-city-dedicated-servers) | USA, Netherlands | Yes | nan | nan | 07/2025 | +| [Ionos](https://www.ionos.com/servers/amd-servers) | US, DE, UK, ESP, FR | nan | No | nan | 07/2025 | +| [IsHosting](https://ishosting.com/en) | Brazil, Netherlands | Yes, based on ticket | Yes | Expensive | 05/2024 | +| [Leaseweb](https://www.leaseweb.com/en/configure/vc/product/entityKey/DEDSER02_NEW_ORDER_BUSINESS_R740XD-24SFF-6134) | US, NL, DE, UK, CA, SG, JP, AUS, HK | nan | No | KYC mandatory | 07/2025 | +| [Linode](https://linode.com) | USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy | Yes out of the box | No, only through [BitLAunch](https://bitlaunch.io) | IPv6 sometimes need to be re-added in Networking tab, no reboot needed | 05/2024 | +| [LiteServer](https://liteserver.nl) | Netherlands | Yes, on by default | Yes | Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal | 07/2024 | +| [Lowendbox](https://lowendbox.com/category/dedicated-servers) | | | | Just an aggregator with good offers | 07/2025 | +| [M247](https://m247.com/eu/services/host/dedicated-servers/) | UK, Austria, Br, Sw, Jp, Poland, Fr, USA, Netherlands | Yes | No | nan | 07/2025 | +| [Mebilcom](https://www.melbicom.net/dedicatedserver/) | NL, US, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL | nan | No | nan | 07/2025 | +| [Mevspace](https://mevspace.com) | Poland | Yes, on by default | Yes | Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff | 07/2024 | +| [Misaka](https://www.misaka.io/) | South Africa | Yes, native support | No | Very Expensive | 05/2024 | +| [NiceVPS](https://nicevps.net/) | Netherlands | Yes | nan | nan | 07/2025 | +| [Njalla](https://nja.la) | Sweden | Yes | Yes | Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market. | 05/2024 | +| [OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/) | USA, DE, FR, UK, PL, CA | | No | Not all locations always available | 07/2025 | +| [Oneprovider](https://oneprovider.com/en/dedicated-servers/ipv6) | PL, FR, NL, UA, US, BG, RO, DK, ESP, NO, CZ, RS, IE, IT, UK, HU, CH, SK, AT, BE, BA, HK, JP, SG, LU, AU, SWE, UAE, BR, CR, MX, GR, CL, MA, AR | Yes | No | nan | 07/2025 | +| [PrivateLayer](https://privatelayer.com) | Swiss | Yes | Yes | Slow customer response | 07/2025 | +| [Privex](https://www.privex.io/tor-exit-policy/) | USA, Germany, Sweden | Yes | Yes | nan | 07/2025 | +| [Psychz](https://www.psychz.net) | US, UK, Brazil, Japan, Russia, South Africa and many more | Yes | nan | nan | 07/2025 | +| [RDP](https://rdp.sh) | Netherlands, USA, Poland | Yes, on by default | Yes | German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node. | 07/2024 | +| [Servermania](https://www.servermania.com/dedicated-servers-hosting.htm) | USA, Canada | nan | No | nan | 07/2025 | +| [Svea](https://svea.net/vps) | Sweden | Yes | nan | nan | 07/2025 | +| [TerraHost](https://terrahost.no) | Norway | Yes, on by default | Yes | Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser | 07/2024 | +| [Thundervm](https://thundervm.com/en/hosting/dedicated-server) | USA, UK, France, Italy, Switzerland, Netherlands | nan | Yes | | 07/2025 | +| [Zenlayer](https://www.zenlayer.com/bare-metal/) | [advertised over 50 locations](50+ https://www.zenlayer.com/global-network) | nan | nan | nan | 07/2025 | +| [iHostArt](https://ihostart.com) | Romania | Yes, on by default | Yes | Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware. | 07/2024 | +| [vSys Host](https://vsys.host) | Ukraine, Netherlands, USA | Yes, on by default | Yes | Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check. | 07/2024 | diff --git a/documentation/docs/data/csv/isp-sheet.csv b/documentation/docs/data/csv/isp-sheet.csv index f72973f924..9a39041c20 100644 --- a/documentation/docs/data/csv/isp-sheet.csv +++ b/documentation/docs/data/csv/isp-sheet.csv @@ -1,26 +1,43 @@ **ISP**,**Locations**,**Public IPv6**,**Crypto Payments**,**Comments**,**Last Updated** -[Flokinet](https://flokinet.is),"Netherlands, Iceland, Romania,France","Yes, needs a ticket and custom setup","yes, including XMR","Very slow customer support","05/2024" -[BitLaunch](https://bitlaunch.io),"Canada, USA, UK","No","Yes","Expensive. Digial Ocean through BitLanch has IPv6","05/2024" -[Hostinger](https://hostinger.com),"France, Lithuania, India, USA, Brazil","Yes, out of the box","Yes","Crypto payments must be done per each server monthly or annually.","05/2024" -[Linode](https://linode.com),"USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy","Yes out of the box","No, only through [BitLAunch](https://bitlaunch.io)","IPv6 sometimes need to be re-added in Networking tab, no reboot needed","05/2024" -[Cherry Servers](https://www.cherryservers.com),"Lithuania, Netherlands, USA, Singapore","No","Yes","Issued IP doesn’t match the location offered by the provider.","05/2024" -[Njalla](https://nja.la),"Sweden","Yes","Yes","Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market.","05/2024" -[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024" -[Misaka](https://www.misaka.io/),"South Africa","Yes, native support","No","Very Expensive","05/2024" -[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket","Yes","Expensive","05/2024" -[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default","Yes","They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)","07/2024" -[iHostArt](https://ihostart.com),"Romania","Yes, on by default","Yes","Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.","07/2024" -[Incognet](https://incognet.io),"Netherlands and USA","Yes, on by default","Yes","They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits","07/2024" -[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default","Yes","Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.","07/2024" -[LiteServer](https://liteserver.nl),"Netherlands","Yes, on by default","Yes","Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal","07/2024" -[TerraHost](https://terrahost.no),"Norway","Yes, on by default","Yes","Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser","07/2024" -[Mevspace](https://mevspace.com),"Poland","Yes, on by default","Yes","Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff","07/2024" -[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default","Yes","Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.","07/2024" -[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default","Yes","Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node","07/2024" -[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default","Yes","German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.","07/2024" - - - - - - +[Flokinet](https://flokinet.is),"Netherlands, Iceland, Romania,France","Yes, needs a ticket and custom setup","yes, including XMR",Very slow customer support,05/2024 +[BitLaunch](https://bitlaunch.io),"Canada, USA, UK",No,Yes,Expensive. Digial Ocean through BitLanch has IPv6,05/2024 +[Hostinger](https://hostinger.com),"France, Lithuania, India, USA, Brazil","Yes, out of the box",Yes,"Not fast enough, Crypto payments must be done per each server monthly or annually.",07/2025 +[Linode](https://linode.com),"USA, Canada, Japan, India, Indonesia, Sweden, Netherlands, Germany, Brazil, France, UK, Australia, Italy",Yes out of the box,"No, only through [BitLAunch](https://bitlaunch.io)","IPv6 sometimes need to be re-added in Networking tab, no reboot needed",05/2024 +[Cherry Servers](https://www.cherryservers.com),"Lithuania, Netherlands, USA, Singapore",No,Yes,Issued IP doesn’t match the location offered by the provider.,05/2024 +[Njalla](https://nja.la),Sweden,Yes,Yes,"Privacy vandguards! The biggest VPS 45 is 3 cores only, but it works better than many “larger” servers on the market.",05/2024 +[HostSailor](https://hostsailor.com),USA,"Yes, based on ticket",Yes,The IPv6 setup needs custom research and is not documented,05/2024 +[Misaka](https://www.misaka.io/),South Africa,"Yes, native support",No,Very Expensive,05/2024 +[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket",Yes,Expensive,05/2024 +[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default",Yes,"They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)",07/2024 +[iHostArt](https://ihostart.com),Romania,"Yes, on by default",Yes,"Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.",07/2024 +[Incognet](https://incognet.io),Netherlands and USA,"Yes, on by default",Yes,They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits,07/2024 +[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default",Yes,"Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.",07/2024 +[LiteServer](https://liteserver.nl),Netherlands,"Yes, on by default",Yes,Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal,07/2024 +[TerraHost](https://terrahost.no),Norway,"Yes, on by default",Yes,Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser,07/2024 +[Mevspace](https://mevspace.com),Poland,"Yes, on by default",Yes,"Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff",07/2024 +[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default",Yes,"Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.",07/2024 +[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default",Yes,Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node,07/2024 +[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default",Yes,"German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.",07/2024 +[Lowendbox](https://lowendbox.com/category/dedicated-servers), , , ,Just an aggregator with good offers,07/2025 +[Thundervm](https://thundervm.com/en/hosting/dedicated-server),"USA, UK, France, Italy, Switzerland, Netherlands",,Yes, ,07/2025 +[OVH](https://us.ovhcloud.com/bare-metal/rise/rise-3/),"USA, DE, FR, UK, PL, CA", ,No,Not all locations always available,07/2025 +[Mebilcom](https://www.melbicom.net/dedicatedserver/),"NL, US, DE, UAE, NG, ESP, IN, IT, FR, LT, SG, BG, LV, PL",,No,,07/2025 +[Servermania](https://www.servermania.com/dedicated-servers-hosting.htm),"USA, Canada",,No,,07/2025 +[Oneprovider](https://oneprovider.com/en/dedicated-servers/ipv6),"PL, FR, NL, UA, US, BG, RO, DK, ESP, NO, CZ, RS, IE, IT, UK, HU, CH, SK, AT, BE, BA, HK, JP, SG, LU, AU, SWE, UAE, BR, CR, MX, GR, CL, MA, AR",Yes,No,,07/2025 +[Ionos](https://www.ionos.com/servers/amd-servers),"US, DE, UK, ESP, FR",,No,,07/2025 +[Leaseweb](https://www.leaseweb.com/en/configure/vc/product/entityKey/DEDSER02_NEW_ORDER_BUSINESS_R740XD-24SFF-6134),"US, NL, DE, UK, CA, SG, JP, AUS, HK",,No,KYC mandatory,07/2025 +[M247](https://m247.com/eu/services/host/dedicated-servers/),"UK, Austria, Br, Sw, Jp, Poland, Fr, USA, Netherlands",Yes,No,,07/2025 +[Hostroyale](https://hostroyale.com/hosting/dedicated-server/),Various countries with different pricing,, Yes,,07/2025 +[DataPacket](https://www.datapacket.com/pricing),"NL, GR, SK, BE, RO, HU, DK, IE, DE, UA, PT, GB, ES, FR, IT, NO, CZ, BG, SE, AT, PL, HR, CH, USA, CO, AR, PE, MX, CL, TR, ZA, NG, IL, HK, AU, SG, JP",Yes,,,07/2025 +[Zenlayer](https://www.zenlayer.com/bare-metal/), [advertised over 50 locations](50+ https://www.zenlayer.com/global-network),,,,07/2025 +[PrivateLayer](https://privatelayer.com),Swiss,Yes,Yes,Slow customer response,07/2025 +[AmeriNoc](https://www.amerinoc.com),USA,Yes,,,07/2025 +[Colocall](https://www.colocall.net/),Ukraine,Yes,,07/2025, +[Incognet](https://incognet.io/kansas-city-dedicated-servers),"USA, Netherlands",Yes,,,07/2025 +[FranTech](https://my.frantech.ca),USA,Yes,,,07/2025 +[Psychz](https://www.psychz.net),"US, UK, Brazil, Japan, Russia, South Africa and many more",Yes,,,07/2025 +[Fsit](https://www.fsit.com/server/vps-vserver-kvm),Swiss,Yes,Yes,,07/2025 +[NiceVPS](https://nicevps.net/),Netherlands,Yes,,,07/2025 +[Dataclub](https://www.dataclub.eu/),"Latvia, Sweden, Netherlands",Yes,,,07/2027 +[Privex](https://www.privex.io/tor-exit-policy/),"USA, Germany, Sweden",Yes,Yes,,07/2025 +[Svea](https://svea.net/vps),Sweden,Yes,,,07/2025 diff --git a/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx b/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx index 9d787172d3..4df6a92c8d 100644 --- a/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx +++ b/documentation/docs/pages/apis/ns-api/ns-api-run-deploy.mdx @@ -306,6 +306,7 @@ export RUST_LOG="info" export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" export NODE_STATUS_AGENT_SERVER_PORT="8000" export NODE_STATUS_AGENT_AUTH_KEY= +export NODE_STATUS_AGENT_PROBE_MNEMONIC= export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" workers=${1:-1} diff --git a/documentation/docs/pages/operators/changelog.mdx b/documentation/docs/pages/operators/changelog.mdx index d72f01bccd..1289f1f2c3 100644 --- a/documentation/docs/pages/operators/changelog.mdx +++ b/documentation/docs/pages/operators/changelog.mdx @@ -47,6 +47,83 @@ This page displays a full list of all the changes during our release cycle from + + +## `v2025.13-emmental` + +- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.13-emmental) +- [`nym-node`](nodes/nym-node.mdx) version `1.15.0` + +```sh +nym-node +Binary Name: nym-node +Build Timestamp: 2025-07-22T09:24:35.790560275Z +Build Version: 1.15.0 +Commit SHA: 578c9b0567656d86812aa21eb0b4c93b5a7235bd +Commit Date: 2025-07-22T11:09:35.000000000+02:00 +Commit Branch: HEAD +rustc Version: 1.86.0 +rustc Channel: stable +cargo Profile: release +``` + +### Operators Updates & Tools + +- [**NIP-3: Nym Exit Policy Update**](https://forum.nym.com/t/nip-3-nym-exit-policy-update/1462/2) is out - **VOTE [HERE](https://governator.nym.com/proposal/prop-d0c0d398-43bd-4a6f-b008-1921b64ae4ed)!** + +- `nym-node` can only run one `--mode` at a time. Multiple mode nodes will fail to start. + +### Features + +- [Initial performance contract](https://github.com/nymtech/nym/pull/5833): Introduces the foundational structure of the performance contract. Not yet integrated into any system. + +- [Basic performance contract integration within Nym API](https://github.com/nymtech/nym/pull/5871): Integrates the performance contract into `nym-api`, enabling node score source selection from the contract (disabled by default). + +- [Forbid running `mixnode` + `entry-gateway` on the same node](https://github.com/nymtech/nym/pull/5878): Prevents configuration of `mixnode` and `entry-gateway` node on the same host. + +- [HTTP Discovery objects & network defaults](https://github.com/nymtech/nym/pull/5814): Adds config options for expanded API URLs via discovery environment objects. + +- [Add build info endpoints](https://github.com/nymtech/nym/pull/5857) + +- [Batch SQL writes for packet stats](https://github.com/nymtech/nym/pull/5874) + +- [Update push-node-status-agent.yaml](https://github.com/nymtech/nym/pull/5882): Adds input for setting release tag and prefixes image tag with `golden-`. + +- [Make Mix hops optional for Mixnet Client SURBs](https://github.com/nymtech/nym/pull/5861): Allows SURBs to be configured to only use gateways in the mixnet return path. + +- [Check gateway supported versions](https://github.com/nymtech/nym/pull/5860) + +- [Clear out screaming logs](https://github.com/nymtech/nym/pull/5856): Removes unnecessary alarming log messages. + +- [Use display when printing paths](https://github.com/nymtech/nym/pull/5853): Uses Rust’s `display()` method for better path output. + +- [Remove old explorer references](https://github.com/nymtech/nym/pull/5846) + +- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet + +### Bugfixes + +- [Don't allow mixnode running in exit mode](https://github.com/nymtech/nym/pull/5898) + +- [Fix contract build process in Makefile](https://github.com/nymtech/nym/pull/5892) + +- [Ignore 'Send' responses when claiming bandwidth](https://github.com/nymtech/nym/pull/5884) + +- [Fix the broken link](https://github.com/nymtech/nym/pull/5873) + +- [Scraper bugfix: ignore precommits from missing validators](https://github.com/nymtech/nym/pull/5867) + +- [Fix removal of qa env](https://github.com/nymtech/nym/pull/5855) + +- [Return true remaining](https://github.com/nymtech/nym/pull/5866) + +### Refactors & Maintenance + +- [chore: 1.88 clippy](https://github.com/nymtech/nym/pull/5877): Applies clippy fixes based on recent compiler version update. + +- [Security patches for the `dkg` crate](https://github.com/nymtech/nym/pull/5828): Addresses overflows, unsafe RNGs, and integrity checks as per Cryspen audit. + + ## `v2025.12-dolcelatte` - [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.12-dolcelatte) diff --git a/documentation/docs/pages/operators/community-counsel/legal.mdx b/documentation/docs/pages/operators/community-counsel/legal.mdx index c916a899d6..f2f0d114d9 100644 --- a/documentation/docs/pages/operators/community-counsel/legal.mdx +++ b/documentation/docs/pages/operators/community-counsel/legal.mdx @@ -24,25 +24,23 @@ Write a message to your provider telling them about your intention to run a `nym #### Join Operators Legal Forum -This [Matrix channel]((https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu)) is the best place to ask questions and share your experience with others. You can share screen prints of abuse reports and ask for support. +This [Matrix channel]((https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu)) is the best place to ask questions and share your experience with others. You can share screen prints of abuse reports and ask for support. #### Join Operators Legal Clinic Do you have any questions directed for lawyers? Come and chat with Nym COO Alexis Roussel, every Wednesday 14:30 UTC for 60min in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu). #### Use a friendly provider Nym operators community shares their experience with different ISPs on [this page](isp-list). At the same time, consider to move away from these provides: - + - Servinga / VPS2day (AS39378) - Frantech / Ponynet / BuyVM (AS53667) -- OVH SAS / OVHcloud (AS16276) - Online S.A.S. / Scaleway (AS12876) - Hetzner Online GmbH (AS24940) -- IONOS SE (AS8560) -- Psychz Networks (AS40676) - 1337 Services GmbH / RDP.sh (AS210558) +- Stark Industries Solutions Ltd. / PQ.Hosting / The.Hosting / UFO-AS (AS44477 / ASN 33993) #### Backup your nodes -Your only way to restore your node is when you have an access to `.nym` directory locally. Follow [node](../nodes/maintenance#backup-a-node) and [proxy configuration](../nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](../nodes/maintenance#restoring-a-node) on another machine later on, without losing your delegation. +Your only way to restore your node is when you have an access to `.nym` directory locally. Follow [node](../nodes/maintenance#backup-a-node) and [proxy configuration](../nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](../nodes/maintenance#restoring-a-node) on another machine later on, without losing your delegation. #### Use `nym-exit` prefix on your landing page URL We would like to ask operators to use [reverse proxy](../nodes/nym-node/configuration/proxy-configuration) with a [landing page](landing-pages). When assigning a domain please use a common convention with `nym-exit` in the beginning of the the page URL as this will create a reputation and reference. The entire address should have this new format: @@ -69,10 +67,10 @@ nym-exit.mysquad.org **The `NYM-EXIT` part in the beginning is what's important.** #### Chose the right TLD -When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report. +When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report. #### Respond to abuse reports -Make sure to read notifications from your account provider and if you receive an abuse report, respond to it in time. Here is a template explaining the essential legal background of Nym Node Exit Gateway. Don't forget to adjust the variables. +Make sure to read notifications from your account provider and if you receive an abuse report, respond to it in time. Here is a template explaining the essential legal background of Nym Node Exit Gateway. Don't forget to adjust the variables.
@@ -81,4 +79,3 @@ Make sure to read notifications from your account provider and if you receive an #### Help us to improve these pages Add your findings by opening a [Pull Request](add-content). - diff --git a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx index ab425f8e4e..81044db705 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/configuration.mdx @@ -454,7 +454,7 @@ The exit policy is same for all NRs, the content is shaped by an offchain govern There is a caveat though. NR is only routing TCP streams and therefore any other type of routing is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through wireguard and don't act as open proxies, the operators have to set up these rules via IP tables rules. -**Follow these steps, using a [setup script]i(https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:** +**Follow these steps, using a [setup script](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:** diff --git a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx index 760b8e5efa..2a53917cb0 100644 --- a/documentation/docs/pages/operators/nodes/nym-node/setup.mdx +++ b/documentation/docs/pages/operators/nodes/nym-node/setup.mdx @@ -20,10 +20,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](. ```sh nym-node Binary Name: nym-node -Build Timestamp: 2025-07-09T12:50:32.123212812Z -Build Version: 1.14.0 -Commit SHA: 089c47cce70ef8ea5ecfe3d00b52e510d006e120 -Commit Date: 2025-07-07T15:44:15.000000000+02:00 +Build Timestamp: 2025-07-22T09:24:35.790560275Z +Build Version: 1.15.0 +Commit SHA: 578c9b0567656d86812aa21eb0b4c93b5a7235bd +Commit Date: 2025-07-22T11:09:35.000000000+02:00 Commit Branch: HEAD rustc Version: 1.86.0 rustc Channel: stable diff --git a/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx b/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx index d5a3c6fe53..8b1b8f38f2 100644 --- a/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx +++ b/documentation/docs/pages/operators/nodes/performance-and-testing/gateway-probe.mdx @@ -9,7 +9,7 @@ Nym Node operators running Gateway functionality are already familiar with the m ## Preparation -We recommend to have install all [the prerequisites](../binaries/building-nym.md#prerequisites) needed to build `nym-node` from source including latest [Rust Toolchain](https://www.rust-lang.org/tools/install). +We recommend to have installed all [the prerequisites](../binaries/building-nym.md#prerequisites) needed to build `nym-node` from source including latest [Rust Toolchain](https://www.rust-lang.org/tools/install), **and** make sure to have [Go](https://go.dev/doc/install) installed. Go is necessary as the probe uses the `rust2go` FFI library to use `netstack` when making requests. ## Installation @@ -34,27 +34,65 @@ cargo build --release -p nym-gateway-probe To list all commands and options run the binary with `--help` command: ```sh -./target/release/nym-gateway-probe --help +./target/release/nym-gateway-probe -h ``` - Output: ```sh -Usage: nym-gateway-probe [OPTIONS] +Usage: nym-gateway-probe [OPTIONS] --mnemonic Options: - -c, --config-env-file Path pointing to an env file describing the network - -g, --gateway - -n, --no-log - -h, --help Print help - -V, --version Print version - + -c, --config-env-file + Path pointing to an env file describing the network + -g, --entry-gateway + The specific gateway specified by ID + -n, --node + Identity of the node to test + --min-gateway-mixnet-performance + + --min-gateway-vpn-performance + + --only-wireguard + + -i, --ignore-egress-epoch-role + Disable logging during probe + --no-log + + -a, --amnezia-args + Arguments to be appended to the wireguard config enabling amnezia-wg configuration + --netstack-download-timeout-sec + [default: 180] + --netstack-v4-dns + [default: 1.1.1.1] + --netstack-v6-dns + [default: 2606:4700:4700::1111] + --netstack-num-ping + [default: 5] + --netstack-send-timeout-sec + [default: 3] + --netstack-recv-timeout-sec + [default: 3] + --netstack-ping-hosts-v4 + [default: nymtech.net] + --netstack-ping-ips-v4 + [default: 1.1.1.1] + --netstack-ping-hosts-v6 + [default: ipv6.google.com] + --netstack-ping-ips-v6 + [default: 2001:4860:4860::8888 2606:4700:4700::1111 2620:fe::fe] + --mnemonic + + -h, --help + Print help + -V, --version + Print version ``` -To run the client, simply add a flag `--gateway` with a targeted gateway identity key. +To run the client, simply add `-n` flag followed by the ID key of the node you wish to test, as well as the mnemonic of a funded Nyx account; this is required to test the ticketbook generation. ```sh -./target/release/nym-gateway-probe --gateway +./target/release/nym-gateway-probe -n --mnemonic ``` For any `nym-node --mode exit-gateway` the aim is to have this outcome: @@ -87,4 +125,4 @@ For any `nym-node --mode exit-gateway` the aim is to have this outcome: **If your Gateway is blacklisted, the probe will not work.** -If you don't provide a `--gateway` flag it will pick a random one to test. +If you don't provide a `-n` flag it will pick a random node to test. diff --git a/documentation/docs/pages/operators/nodes/validator-setup.mdx b/documentation/docs/pages/operators/nodes/validator-setup.mdx index 21612c377d..8b7c3a8ac3 100644 --- a/documentation/docs/pages/operators/nodes/validator-setup.mdx +++ b/documentation/docs/pages/operators/nodes/validator-setup.mdx @@ -9,12 +9,12 @@ import { AccordionTemplate } from 'components/accordion-template.tsx'; > Nym has two main codebases: > - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code except for the validators. -> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go & maintained as a no-modification fork of [wasmd](https://github.com/CosmWasm/wasmd) +> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go & maintained as fork of [wasmd](https://github.com/CosmWasm/wasmd) The validator is a Go application which implements it's functionalities using [Cosmos SDK](https://v1.cosmos.network/sdk). The underlying state-replication engine is powered by [CometBFT](https://cometbft.com/), where the consensus mechanism is based on the [Tendermint Consensus Algorithm](https://arxiv.org/abs/1807.04938). Finally, a [CosmWasm](https://cosmwasm.com) smart contract module controls crucial mixnet functionalities like decentralised directory service, node bonding, and delegated mixnet staking. -We are currently running mainnet with a closed set of reputable validators. To ensure decentralisation of Nyx chain, we are working on a mechanism to onboard new validators to the network. To join the waitlist, please drop an email to `validators [at] nymtech.net` with details of your setup, experience and any other relevent information +At present, our mainnet operates with a select group of reputed validators. We are not accepting new validators at this time. Any updates or changes to this policy will be promptly announced. ## Building your validator @@ -52,7 +52,7 @@ pacman -S git gcc jq - First remove any existing old Go installation and extract the archive you just downloaded into /usr/local: ```sh -rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.10.linux-amd64.tar.gz +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.11.linux-amd64.tar.gz ``` - Then add /usr/local/go/bin to the PATH environment variable @@ -69,14 +69,14 @@ go version - Should return something like: ```sh -go version go1.20.10 linux/amd64 +go version go1.23.11 linux/amd64 ``` ### Download a precompiled validator binary -You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). +You can find pre-compiled binaries for Ubuntu `22.04` and `24.04` [here](https://github.com/nymtech/nyxd/releases). ### Manually compiling your validator binary @@ -109,8 +109,9 @@ Usage: nyxd [command] Available Commands: + comet CometBFT subcommands completion Generate the autocompletion script for the specified shell - config Create or query an application CLI configuration file + config Utilities for managing application configuration debug Tool for helping with debugging your application export Export state to JSON genesis Application's genesis-related subcommands @@ -119,20 +120,20 @@ Available Commands: keys Manage your application's keys prune Prune app history states by keeping the recent heights and deleting old heights query Querying subcommands - rollback rollback cosmos-sdk and tendermint state by one height - rosetta spin up a rosetta server + rollback rollback Cosmos SDK and CometBFT state by one height + snapshots Manage local snapshots start Run the full node status Query remote node for status - tendermint Tendermint subcommands testnet subcommands for starting or configuring local testnets tx Transactions subcommands version Print the application binary version information Flags: -h, --help help for nyxd - --home string directory for config and data + --home string directory for config and data (default "/Users/neo/.nyxd") --log_format string The logging format (json|plain) (default "plain") - --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:,:') (default "info") + --log_no_color Disable colored logs --trace print out full stack trace on errors Use "nyxd [command] --help" for more information about a command. @@ -180,8 +181,9 @@ Usage: nyxd [command] Available Commands: + comet CometBFT subcommands completion Generate the autocompletion script for the specified shell - config Create or query an application CLI configuration file + config Utilities for managing application configuration debug Tool for helping with debugging your application export Export state to JSON genesis Application's genesis-related subcommands @@ -190,20 +192,20 @@ Available Commands: keys Manage your application's keys prune Prune app history states by keeping the recent heights and deleting old heights query Querying subcommands - rollback rollback cosmos-sdk and tendermint state by one height - rosetta spin up a rosetta server + rollback rollback Cosmos SDK and CometBFT state by one height + snapshots Manage local snapshots start Run the full node status Query remote node for status - tendermint Tendermint subcommands testnet subcommands for starting or configuring local testnets tx Transactions subcommands version Print the application binary version information Flags: -h, --help help for nyxd - --home string directory for config and data + --home string directory for config and data (default "/Users/neo/.nyxd") --log_format string The logging format (json|plain) (default "plain") - --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") + --log_level string The logging level (trace|debug|info|warn|error|fatal|panic|disabled or '*:,:') (default "info") + --log_no_color Disable colored logs --trace print out full stack trace on errors Use "nyxd [command] --help" for more information about a command. @@ -246,7 +248,7 @@ You can use the following command to download them for the correct network: wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json # Sandbox testnet -curl https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json +curl https://rpc.sandbox.nymtech.net/genesis | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json ``` ### `config.toml` configuration @@ -512,7 +514,7 @@ nyxd tx slashing unjail --from="KEYRING_NAME" --chain-id=nyx --gas=auto - --gas-adjustment=1.4 + --gas-adjustment=1.5 --gas-prices=0.025unyx ``` @@ -523,7 +525,7 @@ nyxd tx slashing unjail --from="KEYRING_NAME" --chain-id=sandbox --gas=auto - --gas-adjustment=1.4 + --gas-adjustment=1.5 --gas-prices=0.025unyx ``` diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index e3b560ec94..a152092ef7 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -24,6 +24,8 @@ use nym_gateway_requests::{ SimpleGatewayRequestsError, }; use nym_gateway_storage::error::GatewayStorageError; +use nym_gateway_storage::traits::BandwidthGatewayStorage; +use nym_gateway_storage::traits::SharedKeyGatewayStorage; use nym_node_metrics::events::MetricsEvent; use nym_sphinx::forwarding::packet::MixPacket; use nym_statistics_common::{gateways::GatewaySessionEvent, types::SessionType}; @@ -190,7 +192,7 @@ impl AuthenticatedHandler { let handler = AuthenticatedHandler { bandwidth_storage_manager: BandwidthStorageManager::new( - fresh.shared_state.storage.clone(), + Box::new(fresh.shared_state.storage.clone()), ClientBandwidth::new(bandwidth.into()), client.id, fresh.shared_state.cfg.bandwidth, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index bd66ee26f5..2edbf16b4c 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -27,6 +27,9 @@ use nym_gateway_requests::{ INITIAL_PROTOCOL_VERSION, }; use nym_gateway_storage::error::GatewayStorageError; +use nym_gateway_storage::traits::BandwidthGatewayStorage; +use nym_gateway_storage::traits::InboxGatewayStorage; +use nym_gateway_storage::traits::SharedKeyGatewayStorage; use nym_node_metrics::events::MetricsEvent; use nym_sphinx::DestinationAddressBytes; use nym_task::TaskClient; diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index c13df9f241..08c5bc886b 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -13,7 +13,6 @@ use nym_credential_verification::ecash::{ credential_sender::CredentialHandlerConfig, EcashManager, }; use nym_crypto::asymmetric::ed25519; -use nym_gateway_storage::models::WireguardPeer; use nym_ip_packet_router::IpPacketRouter; use nym_mixnet_client::forwarder::MixForwardingSender; use nym_network_defaults::NymNetworkDetails; @@ -38,7 +37,11 @@ mod stale_data_cleaner; use crate::node::stale_data_cleaner::StaleMessagesCleaner; pub use client_handling::active_clients::ActiveClientsStore; pub use nym_gateway_stats_storage::PersistentStatsStorage; -pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage}; +pub use nym_gateway_storage::{ + error::GatewayStorageError, + traits::{BandwidthGatewayStorage, InboxGatewayStorage}, + GatewayStorage, +}; use nym_node_metrics::NymNodeMetrics; pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent}; @@ -93,7 +96,7 @@ pub struct GatewayTasksBuilder { // populated and cached as necessary ecash_manager: Option>, - wireguard_peers: Option>, + wireguard_peers: Option>, wireguard_networks: Option>, } @@ -357,12 +360,12 @@ impl GatewayTasksBuilder { async fn build_wireguard_peers_and_networks( &self, - ) -> Result<(Vec, Vec), GatewayError> { + ) -> Result<(Vec, Vec), GatewayError> { let mut used_private_network_ips = vec![]; let mut all_peers = vec![]; for wireguard_peer in self.storage.get_all_wireguard_peers().await?.into_iter() { let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?; - let Some(peer) = peer.allowed_ips.pop() else { + let Some(allowed_ip) = peer.allowed_ips.pop() else { let peer_identity = &peer.public_key; warn!("Peer {peer_identity} has empty allowed ips. It will be removed",); self.storage @@ -370,8 +373,8 @@ impl GatewayTasksBuilder { .await?; continue; }; - used_private_network_ips.push(peer.ip); - all_peers.push(wireguard_peer); + used_private_network_ips.push(allowed_ip.ip); + all_peers.push(peer); } Ok((all_peers, used_private_network_ips)) @@ -379,7 +382,9 @@ impl GatewayTasksBuilder { // only used under linux #[allow(dead_code)] - async fn get_wireguard_peers(&mut self) -> Result, GatewayError> { + async fn get_wireguard_peers( + &mut self, + ) -> Result, GatewayError> { if let Some(cached) = self.wireguard_peers.take() { return Ok(cached); } @@ -432,8 +437,8 @@ impl GatewayTasksBuilder { opts.config.clone(), wireguard_data.inner.clone(), used_private_network_ips, + ecash_manager, ) - .with_ecash_verifier(ecash_manager) .with_custom_gateway_transceiver(transceiver) .with_shutdown(self.shutdown.fork("authenticator_sp")) .with_wait_for_gateway(true) diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 7ca23637cb..464a3ea6e2 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-api" license = "GPL-3.0" -version = "1.1.62" +version = "1.1.63" authors.workspace = true edition = "2021" rust-version.workspace = true @@ -16,17 +16,12 @@ async-trait = { workspace = true } bs58 = { workspace = true } bip39 = { workspace = true } bincode.workspace = true +console-subscriber = { workspace = true, optional = true } # nym-api needs to be built with RUSTFLAGS="--cfg tokio_unstable" cfg-if = { workspace = true } clap = { workspace = true, features = ["cargo", "derive", "env"] } -console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable" dashmap = { workspace = true } -dirs = { workspace = true } futures = { workspace = true } -itertools = { workspace = true } humantime-serde = { workspace = true } -k256 = { workspace = true, features = [ - "ecdsa-core", -] } # needed for the Verifier trait; pull whatever version is used by other dependencies moka = { workspace = true } pin-project = { workspace = true } rand = { workspace = true } @@ -51,7 +46,6 @@ tendermint = { workspace = true } ts-rs = { workspace = true, optional = true } anyhow = { workspace = true } -getset = { workspace = true } sqlx = { workspace = true, features = [ "runtime-tokio-rustls", @@ -66,30 +60,19 @@ zeroize = { workspace = true } # for axum server axum = { workspace = true, features = ["tokio"] } -axum-extra = { workspace = true, features = ["typed-header"] } tower-http = { workspace = true, features = ["cors", "trace", "compression-br", "compression-deflate", "compression-gzip", "compression-zstd"] } utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipauto = { workspace = true } utoipa-swagger-ui = { workspace = true, features = ["axum"] } tracing = { workspace = true } -## ephemera-specific -#actix-web = "4" -#array-bytes = "6.0.0" -#chrono = { version = "0.4.24", default-features = false, features = ["clock"] } -#futures-util = "0.3.25" -#serde_derive = "1.0.149" -#uuid = { version = "1.3.0", features = ["serde", "v4"] } - ## internal -#ephemera = { path = "../ephemera" } nym-bandwidth-controller = { path = "../common/bandwidth-controller" } nym-ecash-contract-common = { path = "../common/cosmwasm-smart-contracts/ecash-contract" } nym-ecash-time = { path = "../common/ecash-time", features = ["expiration"] } nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" } nym-compact-ecash = { path = "../common/nym_offline_compact_ecash" } nym-credentials-interface = { path = "../common/credentials-interface" } -#nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" } nym-config = { path = "../common/config" } cosmwasm-std = { workspace = true } nym-credential-storage = { path = "../common/credential-storage", features = [ @@ -102,11 +85,8 @@ cw3 = { workspace = true } cw4 = { workspace = true } nym-dkg = { path = "../common/dkg", features = ["cw-types"] } nym-gateway-client = { path = "../common/client-libs/gateway-client" } -nym-inclusion-probability = { path = "../common/inclusion-probability" } nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } -nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" } nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float", "utoipa"] } -nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nym-sphinx = { path = "../common/nymsphinx" } nym-pemstore = { path = "../common/pemstore" } nym-task = { path = "../common/task" } @@ -121,7 +101,8 @@ nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa" nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] } nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" } nym-statistics-common = { path = "../common/statistics" } -chrono.workspace = true +nym-ecash-signer-check = { path = "../common/ecash-signer-check" } + [features] no-reward = [] diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index c0eb51d4d0..267cb731f7 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true bs58 = { workspace = true } cosmrs = { workspace = true } cosmwasm-std = { workspace = true } -getset = { workspace = true } schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } humantime-serde = { workspace = true } @@ -37,10 +36,13 @@ nym-ecash-time = { path = "../../common/ecash-time" } nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" } nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] } nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", features = ["utoipa"] } +nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" } nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false, features = ["openapi"] } -nym-noise-keys = { path = "../../common/nymnoise/keys"} +nym-noise-keys = { path = "../../common/nymnoise/keys" } nym-network-defaults = { path = "../../common/network-defaults" } nym-ticketbooks-merkle = { path = "../../common/ticketbooks-merkle" } +nym-ecash-signer-check-types = { path = "../../common/ecash-signer-check-types" } + [dev-dependencies] rand_chacha = { workspace = true } diff --git a/nym-api/nym-api-requests/src/ecash/models.rs b/nym-api/nym-api-requests/src/ecash/models.rs index 3779d27e17..a69d0c1d33 100644 --- a/nym-api/nym-api-requests/src/ecash/models.rs +++ b/nym-api/nym-api-requests/src/ecash/models.rs @@ -1,7 +1,9 @@ // Copyright 2023-2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::signable::SignedMessage; use cosmrs::AccountId; +use nym_coconut_dkg_common::types::EpochId; use nym_compact_ecash::scheme::coin_indices_signatures::AnnotatedCoinIndexSignature; use nym_compact_ecash::scheme::expiration_date_signatures::AnnotatedExpirationDateSignature; use nym_compact_ecash::utils::try_deserialize_g1_projective; @@ -12,14 +14,13 @@ use nym_credentials_interface::{ VerificationKeyAuth, WithdrawalRequest, }; use nym_crypto::asymmetric::ed25519; -use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_signature; use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof}; use serde::{Deserialize, Serialize}; use sha2::Digest; use std::collections::BTreeMap; use std::ops::Deref; use thiserror::Error; -use time::Date; +use time::{Date, OffsetDateTime}; use utoipa::ToSchema; #[derive(Serialize, Deserialize, Clone, ToSchema)] @@ -541,74 +542,6 @@ pub struct CommitedDeposit { pub merkle_index: usize, } -// -// - -// make sure only our types can implement this trait (to ensure infallible serialisation) -mod private { - use crate::ecash::models::{ - IssuedTicketbooksChallengeCommitmentRequestBody, - IssuedTicketbooksChallengeCommitmentResponseBody, IssuedTicketbooksDataRequestBody, - IssuedTicketbooksDataResponseBody, IssuedTicketbooksForResponseBody, - }; - - pub trait Sealed {} - - // requests - impl Sealed for IssuedTicketbooksChallengeCommitmentRequestBody {} - impl Sealed for IssuedTicketbooksDataRequestBody {} - - // responses - impl Sealed for IssuedTicketbooksChallengeCommitmentResponseBody {} - impl Sealed for IssuedTicketbooksForResponseBody {} - impl Sealed for IssuedTicketbooksDataResponseBody {} -} - -// the trait is not public as it's only defined on types that are guaranteed to not panic when serialised -pub trait SignableMessageBody: Serialize + private::Sealed { - fn sign(self, key: &ed25519::PrivateKey) -> SignedMessage - where - Self: Sized, - { - let signature = key.sign(self.plaintext()); - SignedMessage { - body: self, - signature, - } - } - - fn plaintext(&self) -> Vec { - #[allow(clippy::unwrap_used)] - // SAFETY: all types that implement this trait have valid serialisations - serde_json::to_vec(&self).unwrap() - } -} - -impl SignableMessageBody for T where T: Serialize + private::Sealed {} - -#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct SignedMessage { - pub body: T, - #[schema(value_type = String)] - #[serde(with = "bs58_ed25519_signature")] - pub signature: ed25519::Signature, -} - -impl SignedMessage { - pub fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool - where - T: SignableMessageBody, - { - let plaintext = self.body.plaintext(); - if plaintext.is_empty() { - return false; - } - - pub_key.verify(&plaintext, &self.signature).is_ok() - } -} - pub type IssuedTicketbooksDataRequest = SignedMessage; pub type IssuedTicketbooksChallengeCommitmentRequest = SignedMessage; @@ -826,6 +759,31 @@ pub struct IssuedTicketbooksForCount { pub count: u32, } +pub type EcashSignerStatusResponse = SignedMessage; + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +// includes all pre-requisites for successful (assuming valid request) `/blind-sign` +pub struct EcashSignerStatusResponseBody { + #[serde(with = "time::serde::rfc3339")] + #[schema(value_type = String)] + pub current_time: OffsetDateTime, + + /// Current, perceived, dkg epoch id + pub dkg_ecash_epoch_id: EpochId, + + /// Flag indicating whether the operator has explicitly disabled signer functionalities in the api + pub signer_disabled: bool, + + /// Flag indicating whether this api thinks it's a valid ecash signer for the current epoch + pub is_ecash_signer: bool, + + /// Flag indicating whether this api thinks it has valid signing keys. + /// It might be a valid signer that's not disabled, but the keys might have accidentally been + /// removed due to invalid data migration. + pub has_signing_keys: bool, +} + #[cfg(test)] mod tests { use super::*; diff --git a/nym-api/nym-api-requests/src/lib.rs b/nym-api/nym-api-requests/src/lib.rs index 681f1c6b0f..371ab9ba56 100644 --- a/nym-api/nym-api-requests/src/lib.rs +++ b/nym-api/nym-api-requests/src/lib.rs @@ -11,6 +11,7 @@ pub mod legacy; pub mod models; pub mod nym_nodes; pub mod pagination; +pub mod signable; // The response type we fetch from the network details endpoint. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs deleted file mode 100644 index 3d4447dfff..0000000000 --- a/nym-api/nym-api-requests/src/models.rs +++ /dev/null @@ -1,2107 +0,0 @@ -// Copyright 2022-2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -#![allow(deprecated)] - -use crate::helpers::unix_epoch; -use crate::helpers::PlaceholderJsonSchemaImpl; -use crate::legacy::{ - LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer, -}; -use crate::nym_nodes::SemiSkimmedNode; -use crate::nym_nodes::{BasicEntryInformation, NodeRole, SkimmedNode}; -use crate::pagination::PaginatedResponse; -use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; -use nym_contracts_common::NaiveFloat; -use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey}; -use nym_crypto::asymmetric::x25519::{self, serde_helpers::bs58_x25519_pubkey}; -use nym_mixnet_contract_common::nym_node::Role; -use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams}; -use nym_mixnet_contract_common::rewarding::RewardEstimate; -use nym_mixnet_contract_common::{GatewayBond, IdentityKey, Interval, MixNode, NodeId, Percent}; -use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; -use nym_node_requests::api::v1::authenticator::models::Authenticator; -use nym_node_requests::api::v1::gateway::models::Wireguard; -use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; -use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles}; -use nym_noise_keys::VersionedNoiseKey; -use schemars::gen::SchemaGenerator; -use schemars::schema::{InstanceType, Schema, SchemaObject}; -use schemars::JsonSchema; -use serde::{Deserialize, Deserializer, Serialize}; -use std::cmp::Ordering; -use std::collections::BTreeMap; -use std::fmt::{Debug, Display, Formatter}; -use std::net::IpAddr; -use std::ops::{Deref, DerefMut}; -use std::{fmt, time::Duration}; -use thiserror::Error; -use time::{Date, OffsetDateTime}; -use tracing::{error, warn}; -use utoipa::{IntoParams, ToResponse, ToSchema}; - -pub use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState}; -pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -pub struct RequestError { - message: String, -} - -impl RequestError { - pub fn new>(msg: S) -> Self { - RequestError { - message: msg.into(), - } - } - - pub fn message(&self) -> &str { - &self.message - } - - pub fn empty() -> Self { - Self { - message: String::new(), - } - } -} - -impl Display for RequestError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.message, f) - } -} - -#[derive( - Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, ToSchema, Default, -)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/MixnodeStatus.ts" - ) -)] -#[serde(rename_all = "snake_case")] -pub enum MixnodeStatus { - Active, // in both the active set and the rewarded set - Standby, // only in the rewarded set - Inactive, // in neither the rewarded set nor the active set, but is bonded - #[default] - NotFound, // doesn't even exist in the bonded set -} -impl MixnodeStatus { - pub fn is_active(&self) -> bool { - *self == MixnodeStatus::Active - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts" - ) -)] -pub struct MixnodeCoreStatusResponse { - pub mix_id: NodeId, - pub count: i64, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts" - ) -)] -pub struct GatewayCoreStatusResponse { - pub identity: String, - pub count: i64, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/MixnodeStatusResponse.ts" - ) -)] -pub struct MixnodeStatusResponse { - pub status: MixnodeStatus, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct NodePerformance { - #[schema(value_type = String)] - pub most_recent: Performance, - #[schema(value_type = String)] - pub last_hour: Performance, - #[schema(value_type = String)] - pub last_24h: Performance, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, JsonSchema, ToSchema)] -#[serde(rename_all = "camelCase")] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts(export, export_to = "ts-packages/types/src/types/rust/DisplayRole.ts") -)] -pub enum DisplayRole { - EntryGateway, - Layer1, - Layer2, - Layer3, - ExitGateway, - Standby, -} - -impl From for DisplayRole { - fn from(role: Role) -> Self { - match role { - Role::EntryGateway => DisplayRole::EntryGateway, - Role::Layer1 => DisplayRole::Layer1, - Role::Layer2 => DisplayRole::Layer2, - Role::Layer3 => DisplayRole::Layer3, - Role::ExitGateway => DisplayRole::ExitGateway, - Role::Standby => DisplayRole::Standby, - } - } -} - -impl From for Role { - fn from(role: DisplayRole) -> Self { - match role { - DisplayRole::EntryGateway => Role::EntryGateway, - DisplayRole::Layer1 => Role::Layer1, - DisplayRole::Layer2 => Role::Layer2, - DisplayRole::Layer3 => Role::Layer3, - DisplayRole::ExitGateway => Role::ExitGateway, - DisplayRole::Standby => Role::Standby, - } - } -} - -// imo for now there's no point in exposing more than that, -// nym-api shouldn't be calculating apy or stake saturation for you. -// it should just return its own metrics (performance) and then you can do with it as you wish -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/NodeAnnotation.ts" - ) -)] -pub struct NodeAnnotation { - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - // legacy - #[schema(value_type = String)] - pub last_24h_performance: Performance, - pub current_role: Option, - - pub detailed_performance: DetailedNodePerformance, -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DetailedNodePerformance.ts" - ) -)] -#[non_exhaustive] -pub struct DetailedNodePerformance { - /// routing_score * config_score - pub performance_score: f64, - - pub routing_score: RoutingScore, - pub config_score: ConfigScore, -} - -impl DetailedNodePerformance { - pub fn new( - performance_score: f64, - routing_score: RoutingScore, - config_score: ConfigScore, - ) -> DetailedNodePerformance { - Self { - performance_score, - routing_score, - config_score, - } - } - - pub fn to_rewarding_performance(&self) -> Performance { - Performance::naive_try_from_f64(self.performance_score).unwrap_or_default() - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts(export, export_to = "ts-packages/types/src/types/rust/RoutingScore.ts") -)] -#[non_exhaustive] -pub struct RoutingScore { - /// Total score after taking all the criteria into consideration - pub score: f64, -} - -impl RoutingScore { - pub fn new(score: f64) -> RoutingScore { - Self { score } - } - - pub const fn zero() -> RoutingScore { - RoutingScore { score: 0.0 } - } - - pub fn legacy_performance(&self) -> Performance { - Performance::naive_try_from_f64(self.score).unwrap_or_default() - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts(export, export_to = "ts-packages/types/src/types/rust/ConfigScore.ts") -)] -#[non_exhaustive] -pub struct ConfigScore { - /// Total score after taking all the criteria into consideration - pub score: f64, - - pub versions_behind: Option, - pub self_described_api_available: bool, - pub accepted_terms_and_conditions: bool, - pub runs_nym_node_binary: bool, -} - -impl ConfigScore { - pub fn new( - score: f64, - versions_behind: u32, - accepted_terms_and_conditions: bool, - runs_nym_node_binary: bool, - ) -> ConfigScore { - Self { - score, - versions_behind: Some(versions_behind), - self_described_api_available: true, - accepted_terms_and_conditions, - runs_nym_node_binary, - } - } - - pub fn bad_semver() -> ConfigScore { - ConfigScore { - score: 0.0, - versions_behind: None, - self_described_api_available: true, - accepted_terms_and_conditions: false, - runs_nym_node_binary: false, - } - } - - pub fn unavailable() -> ConfigScore { - ConfigScore { - score: 0.0, - versions_behind: None, - self_described_api_available: false, - accepted_terms_and_conditions: false, - runs_nym_node_binary: false, - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/AnnotationResponse.ts" - ) -)] -pub struct AnnotationResponse { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub annotation: Option, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/NodePerformanceResponse.ts" - ) -)] -pub struct NodePerformanceResponse { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub performance: Option, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts" - ) -)] -pub struct NodeDatePerformanceResponse { - #[schema(value_type = u32)] - pub node_id: NodeId, - #[schema(value_type = String, example = "1970-01-01")] - #[schemars(with = "String")] - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - pub date: Date, - pub performance: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[schema(title = "LegacyMixNodeDetailsWithLayer")] -pub struct LegacyMixNodeDetailsWithLayerSchema { - /// Basic bond information of this mixnode, such as owner address, original pledge, etc. - #[schema(example = "unimplemented schema")] - pub bond_information: String, - - /// Details used for computation of rewarding related data. - #[schema(example = "unimplemented schema")] - pub rewarding_details: String, - - /// Adjustments to the mixnode that are ought to happen during future epoch transitions. - #[schema(example = "unimplemented schema")] - pub pending_changes: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct MixNodeBondAnnotated { - #[schema(value_type = LegacyMixNodeDetailsWithLayerSchema)] - pub mixnode_details: LegacyMixNodeDetailsWithLayer, - #[schema(value_type = String)] - pub stake_saturation: StakeSaturation, - #[schema(value_type = String)] - pub uncapped_stake_saturation: StakeSaturation, - // NOTE: the performance field is deprecated in favour of node_performance - #[schema(value_type = String)] - pub performance: Performance, - pub node_performance: NodePerformance, - #[schema(value_type = String)] - pub estimated_operator_apy: Decimal, - #[schema(value_type = String)] - pub estimated_delegators_apy: Decimal, - pub blacklisted: bool, - - // a rather temporary thing until we query self-described endpoints of mixnodes - #[serde(default)] - #[schema(value_type = Vec)] - pub ip_addresses: Vec, -} - -#[derive(Debug, Error)] -pub enum MalformedNodeBond { - #[error("the associated ed25519 identity key is malformed")] - InvalidEd25519Key, - - #[error("the associated x25519 sphinx key is malformed")] - InvalidX25519Key, -} - -impl MixNodeBondAnnotated { - pub fn mix_node(&self) -> &MixNode { - &self.mixnode_details.bond_information.mix_node - } - - pub fn mix_id(&self) -> NodeId { - self.mixnode_details.mix_id() - } - - pub fn identity_key(&self) -> &str { - self.mixnode_details.bond_information.identity() - } - - pub fn owner(&self) -> &Addr { - self.mixnode_details.bond_information.owner() - } - - pub fn version(&self) -> &str { - &self.mixnode_details.bond_information.mix_node.version - } - - pub fn try_to_skimmed_node(&self, role: NodeRole) -> Result { - Ok(SkimmedNode { - node_id: self.mix_id(), - ed25519_identity_pubkey: self - .identity_key() - .parse() - .map_err(|_| MalformedNodeBond::InvalidEd25519Key)?, - ip_addresses: self.ip_addresses.clone(), - mix_port: self.mix_node().mix_port, - x25519_sphinx_pubkey: self - .mix_node() - .sphinx_key - .parse() - .map_err(|_| MalformedNodeBond::InvalidX25519Key)?, - role, - supported_roles: DeclaredRoles { - mixnode: true, - entry: false, - exit_nr: false, - exit_ipr: false, - }, - entry: None, - performance: self.node_performance.last_24h, - }) - } - - pub fn try_to_semi_skimmed_node( - &self, - role: NodeRole, - ) -> Result { - let skimmed_node = self.try_to_skimmed_node(role)?; - Ok(SemiSkimmedNode { - basic: skimmed_node, - x25519_noise_versioned_key: None, // legacy node won't ever support Noise - }) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct GatewayBondAnnotated { - pub gateway_bond: LegacyGatewayBondWithId, - - #[serde(default)] - pub self_described: Option, - - // NOTE: the performance field is deprecated in favour of node_performance - #[schema(value_type = String)] - pub performance: Performance, - pub node_performance: NodePerformance, - pub blacklisted: bool, - - #[serde(default)] - #[schema(value_type = Vec)] - pub ip_addresses: Vec, -} - -impl GatewayBondAnnotated { - pub fn version(&self) -> &str { - &self.gateway_bond.gateway.version - } - - pub fn identity(&self) -> &String { - self.gateway_bond.bond.identity() - } - - pub fn owner(&self) -> &Addr { - self.gateway_bond.bond.owner() - } - - pub fn try_to_skimmed_node(&self, role: NodeRole) -> Result { - Ok(SkimmedNode { - node_id: self.gateway_bond.node_id, - ip_addresses: self.ip_addresses.clone(), - ed25519_identity_pubkey: self - .gateway_bond - .gateway - .identity_key - .parse() - .map_err(|_| MalformedNodeBond::InvalidEd25519Key)?, - mix_port: self.gateway_bond.bond.gateway.mix_port, - x25519_sphinx_pubkey: self - .gateway_bond - .gateway - .sphinx_key - .parse() - .map_err(|_| MalformedNodeBond::InvalidX25519Key)?, - role, - supported_roles: DeclaredRoles { - mixnode: false, - entry: true, - exit_nr: false, - exit_ipr: false, - }, - entry: Some(BasicEntryInformation { - hostname: None, - ws_port: self.gateway_bond.bond.gateway.clients_port, - wss_port: None, - }), - performance: self.node_performance.last_24h, - }) - } - - pub fn try_to_semi_skimmed_node( - &self, - role: NodeRole, - ) -> Result { - let skimmed_node = self.try_to_skimmed_node(role)?; - Ok(SemiSkimmedNode { - basic: skimmed_node, - x25519_noise_versioned_key: None, // legacy node won't ever support Noise - }) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct GatewayDescription { - // for now only expose what we need. this struct will evolve in the future (or be incorporated into nym-node properly) -} - -#[derive(Debug, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams)] -pub struct ComputeRewardEstParam { - #[schema(value_type = Option)] - #[param(value_type = Option)] - pub performance: Option, - pub active_in_rewarded_set: Option, - pub pledge_amount: Option, - pub total_delegation: Option, - #[schema(value_type = Option)] - #[param(value_type = Option)] - pub interval_operating_cost: Option, - #[schema(value_type = Option)] - #[param(value_type = Option)] - pub profit_margin_percent: Option, -} - -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/RewardEstimationResponse.ts" - ) -)] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct RewardEstimationResponse { - pub estimation: RewardEstimate, - pub reward_params: RewardingParams, - pub epoch: Interval, - #[cfg_attr(feature = "generate-ts", ts(type = "number"))] - pub as_at: i64, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct UptimeResponse { - #[schema(value_type = u32)] - pub mix_id: NodeId, - // The same as node_performance.last_24h. Legacy - pub avg_uptime: u8, - pub node_performance: NodePerformance, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct GatewayUptimeResponse { - pub identity: String, - // The same as node_performance.last_24h. Legacy - pub avg_uptime: u8, - pub node_performance: NodePerformance, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/StakeSaturationResponse.ts" - ) -)] -pub struct StakeSaturationResponse { - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - #[schema(value_type = String)] - pub saturation: StakeSaturation, - - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - #[schema(value_type = String)] - pub uncapped_saturation: StakeSaturation, - pub as_at: i64, -} - -pub type StakeSaturation = Decimal; - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/SelectionChance.ts" - ) -)] -#[deprecated] -pub enum SelectionChance { - High, - Good, - Low, -} - -impl From for SelectionChance { - fn from(p: f64) -> SelectionChance { - match p { - p if p >= 0.7 => SelectionChance::High, - p if p >= 0.3 => SelectionChance::Good, - _ => SelectionChance::Low, - } - } -} - -impl From for SelectionChance { - fn from(p: Decimal) -> Self { - match p { - p if p >= Decimal::from_ratio(70u32, 100u32) => SelectionChance::High, - p if p >= Decimal::from_ratio(30u32, 100u32) => SelectionChance::Good, - _ => SelectionChance::Low, - } - } -} - -impl fmt::Display for SelectionChance { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SelectionChance::High => write!(f, "High"), - SelectionChance::Good => write!(f, "Good"), - SelectionChance::Low => write!(f, "Low"), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts" - ) -)] -#[deprecated] -pub struct InclusionProbabilityResponse { - pub in_active: SelectionChance, - pub in_reserve: SelectionChance, -} - -impl fmt::Display for InclusionProbabilityResponse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "in_active: {}, in_reserve: {}", - self.in_active, self.in_reserve - ) - } -} - -#[deprecated] -#[derive(Clone, Serialize, schemars::JsonSchema, ToSchema)] -pub struct AllInclusionProbabilitiesResponse { - pub inclusion_probabilities: Vec, - pub samples: u64, - pub elapsed: Duration, - pub delta_max: f64, - pub delta_l2: f64, - pub as_at: i64, -} - -#[deprecated] -#[derive(Clone, Serialize, schemars::JsonSchema, ToSchema)] -pub struct InclusionProbability { - #[schema(value_type = u32)] - pub mix_id: NodeId, - pub in_active: f64, - pub in_reserve: f64, -} - -type Uptime = u8; - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct MixnodeStatusReportResponse { - pub mix_id: NodeId, - pub identity: IdentityKey, - pub owner: String, - #[schema(value_type = u8)] - pub most_recent: Uptime, - #[schema(value_type = u8)] - pub last_hour: Uptime, - #[schema(value_type = u8)] - pub last_day: Uptime, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct GatewayStatusReportResponse { - pub identity: String, - pub owner: String, - #[schema(value_type = u8)] - pub most_recent: Uptime, - #[schema(value_type = u8)] - pub last_hour: Uptime, - #[schema(value_type = u8)] - pub last_day: Uptime, -} - -#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts" - ) -)] -pub struct PerformanceHistoryResponse { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub history: PaginatedResponse, -} - -#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/UptimeHistoryResponse.ts" - ) -)] -pub struct UptimeHistoryResponse { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub history: PaginatedResponse, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts" - ) -)] -pub struct HistoricalUptimeResponse { - #[schema(value_type = String, example = "1970-01-01")] - #[schemars(with = "String")] - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - pub date: Date, - - pub uptime: Uptime, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts" - ) -)] -pub struct HistoricalPerformanceResponse { - #[schema(value_type = String, example = "1970-01-01")] - #[schemars(with = "String")] - #[cfg_attr(feature = "generate-ts", ts(type = "string"))] - pub date: Date, - - pub performance: f64, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct OldHistoricalUptimeResponse { - pub date: String, - #[schema(value_type = u8)] - pub uptime: Uptime, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct MixnodeUptimeHistoryResponse { - pub mix_id: NodeId, - pub identity: String, - pub owner: String, - pub history: Vec, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct GatewayUptimeHistoryResponse { - pub identity: String, - pub owner: String, - pub history: Vec, -} - -#[derive(ToSchema)] -#[schema(title = "Coin")] -pub struct CoinSchema { - pub denom: String, - #[schema(value_type = String)] - pub amount: Uint128, -} - -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema, ToResponse)] -pub struct CirculatingSupplyResponse { - #[schema(value_type = CoinSchema)] - pub total_supply: Coin, - #[schema(value_type = CoinSchema)] - pub mixmining_reserve: Coin, - #[schema(value_type = CoinSchema)] - pub vesting_tokens: Coin, - #[schema(value_type = CoinSchema)] - pub circulating_supply: Coin, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostInformation { - #[schema(value_type = Vec)] - pub ip_address: Vec, - pub hostname: Option, - pub keys: HostKeys, -} - -impl From for HostInformation { - fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { - HostInformation { - ip_address: value.ip_address, - hostname: value.hostname, - keys: value.keys.into(), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct HostKeys { - #[serde(with = "bs58_ed25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub ed25519: ed25519::PublicKey, - - #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub x25519: x25519::PublicKey, - - pub current_x25519_sphinx_key: SphinxKey, - - #[serde(default)] - pub pre_announced_x25519_sphinx_key: Option, - - #[serde(default)] - pub x25519_versioned_noise: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct SphinxKey { - pub rotation_id: u32, - - #[serde(with = "bs58_x25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub public_key: x25519::PublicKey, -} - -impl From for SphinxKey { - fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { - SphinxKey { - rotation_id: value.rotation_id, - public_key: value.public_key, - } - } -} - -impl From for HostKeys { - fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { - HostKeys { - ed25519: value.ed25519_identity, - x25519: value.x25519_sphinx, - current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), - pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), - x25519_versioned_noise: value.x25519_versioned_noise, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WebSockets { - pub ws_port: u16, - - pub wss_port: Option, -} - -impl From for WebSockets { - fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { - WebSockets { - ws_port: value.ws_port, - wss_port: value.wss_port, - } - } -} - -pub fn de_rfc3339_or_default<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - Ok(time::serde::rfc3339::deserialize(deserializer).unwrap_or_else(|_| unix_epoch())) -} - -// for all intents and purposes it's just OffsetDateTime, but we need JsonSchema... -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, ToSchema)] -pub struct OffsetDateTimeJsonSchemaWrapper( - #[serde( - default = "unix_epoch", - with = "crate::helpers::overengineered_offset_date_time_serde" - )] - #[schema(inline)] - pub OffsetDateTime, -); - -impl Default for OffsetDateTimeJsonSchemaWrapper { - fn default() -> Self { - OffsetDateTimeJsonSchemaWrapper(unix_epoch()) - } -} - -impl Display for OffsetDateTimeJsonSchemaWrapper { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.0, f) - } -} - -impl From for OffsetDateTime { - fn from(value: OffsetDateTimeJsonSchemaWrapper) -> Self { - value.0 - } -} - -impl From for OffsetDateTimeJsonSchemaWrapper { - fn from(value: OffsetDateTime) -> Self { - OffsetDateTimeJsonSchemaWrapper(value) - } -} - -impl Deref for OffsetDateTimeJsonSchemaWrapper { - type Target = OffsetDateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for OffsetDateTimeJsonSchemaWrapper { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -// implementation taken from: https://github.com/GREsau/schemars/pull/207 -impl JsonSchema for OffsetDateTimeJsonSchemaWrapper { - fn is_referenceable() -> bool { - false - } - - fn schema_name() -> String { - "DateTime".into() - } - - fn json_schema(_: &mut SchemaGenerator) -> Schema { - SchemaObject { - instance_type: Some(InstanceType::String.into()), - format: Some("date-time".into()), - ..Default::default() - } - .into() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeDescription { - #[schema(value_type = u32)] - pub node_id: NodeId, - pub contract_node_type: DescribedNodeType, - pub description: NymNodeData, -} - -impl NymNodeDescription { - pub fn version(&self) -> &str { - &self.description.build_information.build_version - } - - pub fn entry_information(&self) -> BasicEntryInformation { - BasicEntryInformation { - hostname: self.description.host_information.hostname.clone(), - ws_port: self.description.mixnet_websockets.ws_port, - wss_port: self.description.mixnet_websockets.wss_port, - } - } - - pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { - self.description.host_information.keys.ed25519 - } - - pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { - let keys = &self.description.host_information.keys; - - if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { - // legacy case (i.e. node doesn't support rotation) - return keys.current_x25519_sphinx_key.public_key; - } - - if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { - // it's the 'current' key - return keys.current_x25519_sphinx_key.public_key; - } - - if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { - if pre_announced.rotation_id == current_rotation_id { - return pre_announced.public_key; - } - } - - warn!( - "unexpected key rotation {current_rotation_id} for node {}", - self.node_id - ); - // this should never be reached, but just in case, return the fallback option - keys.current_x25519_sphinx_key.public_key - } - - pub fn to_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SkimmedNode { - let keys = &self.description.host_information.keys; - let entry = if self.description.declared_role.entry { - Some(self.entry_information()) - } else { - None - }; - - SkimmedNode { - node_id: self.node_id, - ed25519_identity_pubkey: keys.ed25519, - ip_addresses: self.description.host_information.ip_address.clone(), - mix_port: self.description.mix_port(), - x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), - // we can't use the declared roles, we have to take whatever was provided in the contract. - // why? say this node COULD operate as an exit, but it might be the case the contract decided - // to assign it an ENTRY role only. we have to use that one instead. - role, - supported_roles: self.description.declared_role, - entry, - performance, - } - } - - pub fn to_semi_skimmed_node( - &self, - current_rotation_id: u32, - role: NodeRole, - performance: Performance, - ) -> SemiSkimmedNode { - let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); - - SemiSkimmedNode { - basic: skimmed_node, - x25519_noise_versioned_key: self - .description - .host_information - .keys - .x25519_versioned_noise, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" - ) -)] -pub enum DescribedNodeType { - LegacyMixnode, - LegacyGateway, - NymNode, -} - -impl DescribedNodeType { - pub fn is_nym_node(&self) -> bool { - matches!(self, DescribedNodeType::NymNode) - } -} - -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] -#[cfg_attr( - feature = "generate-ts", - ts( - export, - export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" - ) -)] -pub struct DeclaredRoles { - pub mixnode: bool, - pub entry: bool, - pub exit_nr: bool, - pub exit_ipr: bool, -} - -impl DeclaredRoles { - pub fn can_operate_exit_gateway(&self) -> bool { - self.exit_ipr && self.exit_nr - } -} - -impl From for DeclaredRoles { - fn from(value: NodeRoles) -> Self { - DeclaredRoles { - mixnode: value.mixnode_enabled, - entry: value.gateway_enabled, - exit_nr: value.gateway_enabled && value.network_requester_enabled, - exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, - } - } -} - -// this struct is getting quite bloated... -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NymNodeData { - #[serde(default)] - pub last_polled: OffsetDateTimeJsonSchemaWrapper, - - pub host_information: HostInformation, - - #[serde(default)] - pub declared_role: DeclaredRoles, - - #[serde(default)] - pub auxiliary_details: AuxiliaryDetails, - - // TODO: do we really care about ALL build info or just the version? - pub build_information: BinaryBuildInformationOwned, - - #[serde(default)] - pub network_requester: Option, - - #[serde(default)] - pub ip_packet_router: Option, - - #[serde(default)] - pub authenticator: Option, - - #[serde(default)] - pub wireguard: Option, - - // for now we only care about their ws/wss situation, nothing more - pub mixnet_websockets: WebSockets, -} - -impl NymNodeData { - pub fn mix_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .mix_port - .unwrap_or(DEFAULT_MIX_LISTENING_PORT) - } - - pub fn verloc_port(&self) -> u16 { - self.auxiliary_details - .announce_ports - .verloc_port - .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct LegacyDescribedGateway { - pub bond: GatewayBond, - pub self_described: Option, -} - -impl From for LegacyDescribedGateway { - fn from(bond: LegacyGatewayBondWithId) -> Self { - LegacyDescribedGateway { - bond: bond.bond, - self_described: None, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct LegacyDescribedMixNode { - pub bond: LegacyMixNodeBondWithLayer, - pub self_described: Option, -} - -impl From for LegacyDescribedMixNode { - fn from(bond: LegacyMixNodeBondWithLayer) -> Self { - LegacyDescribedMixNode { - bond, - self_described: None, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NetworkRequesterDetails { - /// address of the embedded network requester - pub address: String, - - /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list - pub uses_exit_policy: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct IpPacketRouterDetails { - /// address of the embedded ip packet router - pub address: String, -} - -// works for current simple case. -impl From for IpPacketRouterDetails { - fn from(value: IpPacketRouter) -> Self { - IpPacketRouterDetails { - address: value.address, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct AuthenticatorDetails { - /// address of the embedded authenticator - pub address: String, -} - -// works for current simple case. -impl From for AuthenticatorDetails { - fn from(value: Authenticator) -> Self { - AuthenticatorDetails { - address: value.address, - } - } -} -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct WireguardDetails { - pub port: u16, - pub public_key: String, -} - -// works for current simple case. -impl From for WireguardDetails { - fn from(value: Wireguard) -> Self { - WireguardDetails { - port: value.port, - public_key: value.public_key, - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct ApiHealthResponse { - pub status: ApiStatus, - #[serde(default)] - pub chain_status: ChainStatus, - pub uptime: u64, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -#[serde(rename_all = "lowercase")] -pub enum ApiStatus { - Up, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, schemars::JsonSchema, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum ChainStatus { - Synced, - #[default] - Unknown, - Stalled { - #[serde( - serialize_with = "humantime_serde::serialize", - deserialize_with = "humantime_serde::deserialize" - )] - approximate_amount: Duration, - }, -} - -impl ApiHealthResponse { - pub fn new_healthy(uptime: Duration) -> Self { - ApiHealthResponse { - status: ApiStatus::Up, - chain_status: ChainStatus::Synced, - uptime: uptime.as_secs(), - } - } -} - -impl ApiStatus { - pub fn is_up(&self) -> bool { - matches!(self, ApiStatus::Up) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct SignerInformationResponse { - pub cosmos_address: String, - - pub identity: String, - - pub announce_address: String, - - pub verification_key: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, Default, ToSchema)] -pub struct TestNode { - pub node_id: Option, - pub identity_key: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct TestRoute { - pub gateway: TestNode, - pub layer1: TestNode, - pub layer2: TestNode, - pub layer3: TestNode, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct PartialTestResult { - pub monitor_run_id: i64, - pub timestamp: i64, - pub overall_reliability_for_all_routes_in_monitor_run: Option, - pub test_routes: TestRoute, -} - -pub type MixnodeTestResultResponse = PaginatedResponse; -pub type GatewayTestResultResponse = PaginatedResponse; - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NetworkMonitorRunDetailsResponse { - pub monitor_run_id: i64, - pub network_reliability: f64, - pub total_sent: usize, - pub total_received: usize, - - // integer score to number of nodes with that score - pub mixnode_results: BTreeMap, - pub gateway_results: BTreeMap, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NoiseDetails { - pub key: VersionedNoiseKey, - - pub mixnet_port: u16, - - #[schema(value_type = Vec)] - pub ip_addresses: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct NodeRefreshBody { - #[serde(with = "bs58_ed25519_pubkey")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub node_identity: ed25519::PublicKey, - - // a poor man's nonce - pub request_timestamp: i64, - - #[schemars(with = "PlaceholderJsonSchemaImpl")] - #[schema(value_type = String)] - pub signature: ed25519::Signature, -} - -impl NodeRefreshBody { - pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec { - node_identity - .to_bytes() - .into_iter() - .chain(request_timestamp.to_be_bytes()) - .chain(b"describe-cache-refresh-request".iter().copied()) - .collect() - } - - pub fn new(private_key: &ed25519::PrivateKey) -> Self { - let node_identity = private_key.public_key(); - let request_timestamp = OffsetDateTime::now_utc().unix_timestamp(); - let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp)); - NodeRefreshBody { - node_identity, - request_timestamp, - signature, - } - } - - pub fn verify_signature(&self) -> bool { - self.node_identity - .verify( - Self::plaintext(self.node_identity, self.request_timestamp), - &self.signature, - ) - .is_ok() - } - - pub fn is_stale(&self) -> bool { - let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else { - return true; - }; - let now = OffsetDateTime::now_utc(); - - if encoded > now { - return true; - } - - if (encoded + Duration::from_secs(30)) < now { - return true; - } - - false - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct KeyRotationInfoResponse { - #[serde(flatten)] - pub details: KeyRotationDetails, - - // helper field that holds calculated data based on the `details` field - // this is to expose the information in a format more easily accessible by humans - // without having to do any calculations - pub progress: KeyRotationProgressInfo, -} - -impl From for KeyRotationInfoResponse { - fn from(details: KeyRotationDetails) -> Self { - KeyRotationInfoResponse { - details, - progress: KeyRotationProgressInfo { - current_key_rotation_id: details.current_key_rotation_id(), - current_rotation_starting_epoch: details.current_rotation_starting_epoch_id(), - current_rotation_ending_epoch: details.current_rotation_starting_epoch_id() - + details.key_rotation_state.validity_epochs - - 1, - }, - } - } -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct KeyRotationProgressInfo { - pub current_key_rotation_id: u32, - - pub current_rotation_starting_epoch: u32, - - pub current_rotation_ending_epoch: u32, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct KeyRotationDetails { - pub key_rotation_state: KeyRotationState, - - #[schema(value_type = u32)] - pub current_absolute_epoch_id: EpochId, - - #[serde(with = "time::serde::rfc3339")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub current_epoch_start: OffsetDateTime, - - pub epoch_duration: Duration, -} - -impl KeyRotationDetails { - pub fn current_key_rotation_id(&self) -> u32 { - self.key_rotation_state - .key_rotation_id(self.current_absolute_epoch_id) - } - - pub fn next_rotation_starting_epoch_id(&self) -> EpochId { - self.key_rotation_state - .next_rotation_starting_epoch_id(self.current_absolute_epoch_id) - } - - pub fn current_rotation_starting_epoch_id(&self) -> EpochId { - self.key_rotation_state - .current_rotation_starting_epoch_id(self.current_absolute_epoch_id) - } - - fn current_epoch_progress(&self, now: OffsetDateTime) -> f32 { - let elapsed = (now - self.current_epoch_start).as_seconds_f32(); - elapsed / self.epoch_duration.as_secs_f32() - } - - pub fn is_epoch_stuck(&self) -> bool { - let now = OffsetDateTime::now_utc(); - let progress = self.current_epoch_progress(now); - if progress > 1. { - let into_next = 1. - progress; - // if epoch hasn't progressed for more than 20% of its duration, mark is as stuck - if into_next > 0.2 { - let diff_time = - Duration::from_secs_f32(into_next * self.epoch_duration.as_secs_f32()); - let expected_epoch_end = self.current_epoch_start + self.epoch_duration; - warn!("the current epoch is expected to have been over by {expected_epoch_end}. it's already {} overdue!", humantime_serde::re::humantime::format_duration(diff_time)); - return true; - } - } - - false - } - - // based on the current **TIME**, determine what's the expected current rotation id - pub fn expected_current_rotation_id(&self) -> KeyRotationId { - let now = OffsetDateTime::now_utc(); - let current_end = now + self.epoch_duration; - if now < current_end { - return self - .key_rotation_state - .key_rotation_id(self.current_absolute_epoch_id); - } - - let diff = now - current_end; - let passed_epochs = diff / self.epoch_duration; - let expected_current_epoch = self.current_absolute_epoch_id + passed_epochs.floor() as u32; - - self.key_rotation_state - .key_rotation_id(expected_current_epoch) - } - - pub fn until_next_rotation(&self) -> Option { - let current_epoch_progress = self.current_epoch_progress(OffsetDateTime::now_utc()); - if current_epoch_progress > 1. { - return None; - } - - let next_rotation_epoch = self.next_rotation_starting_epoch_id(); - let full_remaining = - (next_rotation_epoch - self.current_absolute_epoch_id).checked_add(1)?; - - let epochs_until_next_rotation = (1. - current_epoch_progress) + full_remaining as f32; - - Some(Duration::from_secs_f32( - epochs_until_next_rotation * self.epoch_duration.as_secs_f32(), - )) - } - - pub fn epoch_start_time(&self, absolute_epoch_id: EpochId) -> OffsetDateTime { - match absolute_epoch_id.cmp(&self.current_absolute_epoch_id) { - Ordering::Less => { - let diff = self.current_absolute_epoch_id - absolute_epoch_id; - self.current_epoch_start - diff * self.epoch_duration - } - Ordering::Equal => self.current_epoch_start, - Ordering::Greater => { - let diff = absolute_epoch_id - self.current_absolute_epoch_id; - self.current_epoch_start + diff * self.epoch_duration - } - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] -pub struct RewardedSetResponse { - #[serde(default)] - #[schema(value_type = u32)] - pub epoch_id: EpochId, - - pub entry_gateways: Vec, - - pub exit_gateways: Vec, - - pub layer1: Vec, - - pub layer2: Vec, - - pub layer3: Vec, - - pub standby: Vec, -} - -impl From for nym_mixnet_contract_common::EpochRewardedSet { - fn from(res: RewardedSetResponse) -> Self { - nym_mixnet_contract_common::EpochRewardedSet { - epoch_id: res.epoch_id, - assignment: nym_mixnet_contract_common::RewardedSet { - entry_gateways: res.entry_gateways, - exit_gateways: res.exit_gateways, - layer1: res.layer1, - layer2: res.layer2, - layer3: res.layer3, - standby: res.standby, - }, - } - } -} - -impl From for RewardedSetResponse { - fn from(r: nym_mixnet_contract_common::EpochRewardedSet) -> Self { - RewardedSetResponse { - epoch_id: r.epoch_id, - entry_gateways: r.assignment.entry_gateways, - exit_gateways: r.assignment.exit_gateways, - layer1: r.assignment.layer1, - layer2: r.assignment.layer2, - layer3: r.assignment.layer3, - standby: r.assignment.standby, - } - } -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct ChainStatusResponse { - pub connected_nyxd: String, - pub status: DetailedChainStatus, -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct DetailedChainStatus { - pub abci: crate::models::tendermint_types::AbciInfo, - pub latest_block: BlockInfo, -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct BlockInfo { - pub block_id: BlockId, - pub block: FullBlockInfo, - // if necessary we might put block data here later too -} - -impl From for BlockInfo { - fn from(value: tendermint_rpc::endpoint::block::Response) -> Self { - BlockInfo { - block_id: value.block_id.into(), - block: FullBlockInfo { - header: value.block.header.into(), - }, - } - } -} - -#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)] -pub struct FullBlockInfo { - pub header: BlockHeader, -} - -// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields -pub mod tendermint_types { - use schemars::JsonSchema; - use serde::{Deserialize, Serialize}; - use tendermint::abci::response::Info; - use tendermint::block::header::Version; - use tendermint::{block, Hash}; - use utoipa::ToSchema; - - #[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)] - pub struct AbciInfo { - /// Some arbitrary information. - pub data: String, - - /// The application software semantic version. - pub version: String, - - /// The application protocol version. - pub app_version: u64, - - /// The latest block for which the app has called [`Commit`]. - pub last_block_height: u64, - - /// The latest result of [`Commit`]. - pub last_block_app_hash: String, - } - - impl From for AbciInfo { - fn from(value: Info) -> Self { - AbciInfo { - data: value.data, - version: value.version, - app_version: value.app_version, - last_block_height: value.last_block_height.value(), - last_block_app_hash: value.last_block_app_hash.to_string(), - } - } - } - - /// `Version` contains the protocol version for the blockchain and the - /// application. - /// - /// - #[derive( - Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema, - )] - pub struct HeaderVersion { - /// Block version - pub block: u64, - - /// App version - pub app: u64, - } - - impl From for HeaderVersion { - fn from(value: Version) -> Self { - HeaderVersion { - block: value.block, - app: value.app, - } - } - } - - /// Block identifiers which contain two distinct Merkle roots of the block, - /// as well as the number of parts in the block. - /// - /// - /// - /// Default implementation is an empty Id as defined by the Go implementation in - /// . - /// - /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). - /// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId. - /// See: - #[derive( - Serialize, - Deserialize, - Copy, - Clone, - Debug, - Default, - Hash, - Eq, - PartialEq, - PartialOrd, - Ord, - JsonSchema, - ToSchema, - )] - pub struct BlockId { - /// The block's main hash is the Merkle root of all the fields in the - /// block header. - #[schemars(with = "String")] - #[schema(value_type = String)] - pub hash: Hash, - - /// Parts header (if available) is used for secure gossipping of the block - /// during consensus. It is the Merkle root of the complete serialized block - /// cut into parts. - /// - /// PartSet is used to split a byteslice of data into parts (pieces) for - /// transmission. By splitting data into smaller parts and computing a - /// Merkle root hash on the list, you can verify that a part is - /// legitimately part of the complete data, and the part can be forwarded - /// to other peers before all the parts are known. In short, it's a fast - /// way to propagate a large file over a gossip network. - /// - /// - /// - /// PartSetHeader in protobuf is defined as never nil using the gogoproto - /// annotations. This does not translate to Rust, but we can indicate this - /// in the domain type. - pub part_set_header: PartSetHeader, - } - - impl From for BlockId { - fn from(value: block::Id) -> Self { - BlockId { - hash: value.hash, - part_set_header: value.part_set_header.into(), - } - } - } - - /// Block parts header - #[derive( - Clone, - Copy, - Debug, - Default, - Hash, - Eq, - PartialEq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, - ToSchema, - )] - #[non_exhaustive] - pub struct PartSetHeader { - /// Number of parts in this block - pub total: u32, - - /// Hash of the parts set header, - #[schemars(with = "String")] - #[schema(value_type = String)] - pub hash: Hash, - } - - impl From for PartSetHeader { - fn from(value: block::parts::Header) -> Self { - PartSetHeader { - total: value.total, - hash: value.hash, - } - } - } - - /// Block `Header` values contain metadata about the block and about the - /// consensus, as well as commitments to the data in the current block, the - /// previous block, and the results returned by the application. - /// - /// - #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] - pub struct BlockHeader { - /// Header version - pub version: HeaderVersion, - - /// Chain ID - pub chain_id: String, - - /// Current block height - pub height: u64, - - /// Current timestamp - #[schemars(with = "String")] - #[schema(value_type = String)] - pub time: tendermint::Time, - - /// Previous block info - pub last_block_id: Option, - - /// Commit from validators from the last block - #[schemars(with = "Option")] - #[schema(value_type = Option)] - pub last_commit_hash: Option, - - /// Merkle root of transaction hashes - #[schemars(with = "Option")] - #[schema(value_type = Option)] - pub data_hash: Option, - - /// Validators for the current block - #[schemars(with = "String")] - #[schema(value_type = String)] - pub validators_hash: Hash, - - /// Validators for the next block - #[schemars(with = "String")] - #[schema(value_type = String)] - pub next_validators_hash: Hash, - - /// Consensus params for the current block - #[schemars(with = "String")] - #[schema(value_type = String)] - pub consensus_hash: Hash, - - /// State after txs from the previous block - #[schemars(with = "String")] - #[schema(value_type = String)] - pub app_hash: Hash, - - /// Root hash of all results from the txs from the previous block - #[schemars(with = "Option")] - #[schema(value_type = Option)] - pub last_results_hash: Option, - - /// Hash of evidence included in the block - #[schemars(with = "Option")] - #[schema(value_type = Option)] - pub evidence_hash: Option, - - /// Original proposer of the block - #[serde(with = "nym_serde_helpers::hex")] - #[schemars(with = "String")] - #[schema(value_type = String)] - pub proposer_address: Vec, - } - - impl From for BlockHeader { - fn from(value: block::Header) -> Self { - BlockHeader { - version: value.version.into(), - chain_id: value.chain_id.to_string(), - height: value.height.value(), - time: value.time, - last_block_id: value.last_block_id.map(Into::into), - last_commit_hash: value.last_commit_hash, - data_hash: value.data_hash, - validators_hash: value.validators_hash, - next_validators_hash: value.next_validators_hash, - consensus_hash: value.consensus_hash, - app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(), - last_results_hash: value.last_results_hash, - evidence_hash: value.evidence_hash, - proposer_address: value.proposer_address.as_bytes().to_vec(), - } - } - } -} - -use crate::models::tendermint_types::{BlockHeader, BlockId}; -pub use config_score::*; - -pub mod config_score { - use nym_contracts_common::NaiveFloat; - use serde::{Deserialize, Serialize}; - use std::cmp::Ordering; - use utoipa::ToSchema; - - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] - pub struct ConfigScoreDataResponse { - pub parameters: ConfigScoreParams, - pub version_history: Vec, - } - - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] - pub struct HistoricalNymNodeVersionEntry { - /// The unique, ordered, id of this particular entry - pub id: u32, - - /// Data associated with this particular version - pub version_information: HistoricalNymNodeVersion, - } - - impl PartialOrd for HistoricalNymNodeVersionEntry { - fn partial_cmp(&self, other: &Self) -> Option { - // we only care about id for the purposes of ordering as they should have unique data - self.id.partial_cmp(&other.id) - } - } - - impl From - for HistoricalNymNodeVersionEntry - { - fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersionEntry) -> Self { - HistoricalNymNodeVersionEntry { - id: value.id, - version_information: value.version_information.into(), - } - } - } - - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] - pub struct HistoricalNymNodeVersion { - /// Version of the nym node that is going to be used for determining the version score of a node. - /// note: value stored here is pre-validated `semver::Version` - pub semver: String, - - /// Block height of when this version has been added to the contract - pub introduced_at_height: u64, - // for now ignore that field. it will give nothing useful to the users - // pub difference_since_genesis: TotalVersionDifference, - } - - impl From for HistoricalNymNodeVersion { - fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersion) -> Self { - HistoricalNymNodeVersion { - semver: value.semver, - introduced_at_height: value.introduced_at_height, - } - } - } - - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] - pub struct ConfigScoreParams { - /// Defines weights for calculating numbers of versions behind the current release. - pub version_weights: OutdatedVersionWeights, - - /// Defines the parameters of the formula for calculating the version score - pub version_score_formula_params: VersionScoreFormulaParams, - } - - /// Defines weights for calculating numbers of versions behind the current release. - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] - pub struct OutdatedVersionWeights { - pub major: u32, - pub minor: u32, - pub patch: u32, - pub prerelease: u32, - } - - /// Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) - /// define the relevant parameters - #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] - pub struct VersionScoreFormulaParams { - pub penalty: f64, - pub penalty_scaling: f64, - } - - impl From for ConfigScoreParams { - fn from(value: nym_mixnet_contract_common::ConfigScoreParams) -> Self { - ConfigScoreParams { - version_weights: value.version_weights.into(), - version_score_formula_params: value.version_score_formula_params.into(), - } - } - } - - impl From for OutdatedVersionWeights { - fn from(value: nym_mixnet_contract_common::OutdatedVersionWeights) -> Self { - OutdatedVersionWeights { - major: value.major, - minor: value.minor, - patch: value.patch, - prerelease: value.prerelease, - } - } - } - - impl From for VersionScoreFormulaParams { - fn from(value: nym_mixnet_contract_common::VersionScoreFormulaParams) -> Self { - VersionScoreFormulaParams { - penalty: value.penalty.naive_to_f64(), - penalty_scaling: value.penalty_scaling.naive_to_f64(), - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn offset_date_time_json_schema_wrapper_serde_backwards_compat() { - let mut dummy = OffsetDateTimeJsonSchemaWrapper::default(); - dummy.0 += Duration::from_millis(1); - let ser = serde_json::to_string(&dummy).unwrap(); - - assert_eq!("\"1970-01-01 00:00:00.001 +00:00:00\"", ser); - - let human_readable = "\"2024-05-23 07:41:02.756283766 +00:00:00\""; - let rfc3339 = "\"2002-10-02T15:00:00Z\""; - let rfc3339_offset = "\"2002-10-02T10:00:00-05:00\""; - - let de = serde_json::from_str::(human_readable).unwrap(); - assert_eq!(de.0.unix_timestamp(), 1716450062); - - let de = serde_json::from_str::(rfc3339).unwrap(); - assert_eq!(de.0.unix_timestamp(), 1033570800); - - let de = serde_json::from_str::(rfc3339_offset).unwrap(); - assert_eq!(de.0.unix_timestamp(), 1033570800); - - let de = serde_json::from_str::("\"nonsense\"").unwrap(); - assert_eq!(de.0.unix_timestamp(), 0); - } -} diff --git a/nym-api/nym-api-requests/src/models/api_status.rs b/nym-api/nym-api-requests/src/models/api_status.rs new file mode 100644 index 0000000000..c10b9e3bae --- /dev/null +++ b/nym-api/nym-api-requests/src/models/api_status.rs @@ -0,0 +1,68 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use utoipa::ToSchema; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct ApiHealthResponse { + pub status: ApiStatus, + #[serde(default)] + pub chain_status: ChainStatus, + pub uptime: u64, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum ApiStatus { + Up, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, schemars::JsonSchema, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ChainStatus { + Synced, + #[default] + Unknown, + Stalled { + #[serde( + serialize_with = "humantime_serde::serialize", + deserialize_with = "humantime_serde::deserialize" + )] + approximate_amount: Duration, + }, +} + +impl ChainStatus { + pub fn is_synced(&self) -> bool { + matches!(self, ChainStatus::Synced) + } +} + +impl ApiHealthResponse { + pub fn new_healthy(uptime: Duration) -> Self { + ApiHealthResponse { + status: ApiStatus::Up, + chain_status: ChainStatus::Synced, + uptime: uptime.as_secs(), + } + } +} + +impl ApiStatus { + pub fn is_up(&self) -> bool { + matches!(self, ApiStatus::Up) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct SignerInformationResponse { + pub cosmos_address: String, + + pub identity: String, + + pub announce_address: String, + + pub verification_key: Option, +} diff --git a/nym-api/nym-api-requests/src/models/circulating_supply.rs b/nym-api/nym-api-requests/src/models/circulating_supply.rs new file mode 100644 index 0000000000..f191f0ccd2 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/circulating_supply.rs @@ -0,0 +1,19 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::CoinSchema; +use cosmwasm_std::Coin; +use serde::{Deserialize, Serialize}; +use utoipa::{ToResponse, ToSchema}; + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema, ToResponse)] +pub struct CirculatingSupplyResponse { + #[schema(value_type = CoinSchema)] + pub total_supply: Coin, + #[schema(value_type = CoinSchema)] + pub mixmining_reserve: Coin, + #[schema(value_type = CoinSchema)] + pub vesting_tokens: Coin, + #[schema(value_type = CoinSchema)] + pub circulating_supply: Coin, +} diff --git a/nym-api/nym-api-requests/src/models/described.rs b/nym-api/nym-api-requests/src/models/described.rs new file mode 100644 index 0000000000..666cca1996 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/described.rs @@ -0,0 +1,375 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::{BinaryBuildInformationOwned, OffsetDateTimeJsonSchemaWrapper}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; +use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey; +use nym_crypto::asymmetric::{ed25519, x25519}; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT}; +use nym_node_requests::api::v1::authenticator::models::Authenticator; +use nym_node_requests::api::v1::gateway::models::Wireguard; +use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; +use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles}; +use nym_noise_keys::VersionedNoiseKey; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use tracing::warn; +use utoipa::ToSchema; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct HostInformation { + #[schema(value_type = Vec)] + pub ip_address: Vec, + pub hostname: Option, + pub keys: HostKeys, +} + +impl From for HostInformation { + fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self { + HostInformation { + ip_address: value.ip_address, + hostname: value.hostname, + keys: value.keys.into(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct HostKeys { + #[serde(with = "bs58_ed25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub ed25519: ed25519::PublicKey, + + #[deprecated(note = "use the current_x25519_sphinx_key with explicit rotation information")] + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub x25519: x25519::PublicKey, + + pub current_x25519_sphinx_key: SphinxKey, + + #[serde(default)] + pub pre_announced_x25519_sphinx_key: Option, + + #[serde(default)] + pub x25519_versioned_noise: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct SphinxKey { + pub rotation_id: u32, + + #[serde(with = "bs58_x25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub public_key: x25519::PublicKey, +} + +impl From for SphinxKey { + fn from(value: nym_node_requests::api::v1::node::models::SphinxKey) -> Self { + SphinxKey { + rotation_id: value.rotation_id, + public_key: value.public_key, + } + } +} + +impl From for HostKeys { + fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self { + HostKeys { + ed25519: value.ed25519_identity, + x25519: value.x25519_sphinx, + current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), + pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), + x25519_versioned_noise: value.x25519_versioned_noise, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct WebSockets { + pub ws_port: u16, + + pub wss_port: Option, +} + +impl From for WebSockets { + fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self { + WebSockets { + ws_port: value.ws_port, + wss_port: value.wss_port, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NoiseDetails { + pub key: VersionedNoiseKey, + + pub mixnet_port: u16, + + #[schema(value_type = Vec)] + pub ip_addresses: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeDescription { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub contract_node_type: DescribedNodeType, + pub description: NymNodeData, +} + +impl NymNodeDescription { + pub fn version(&self) -> &str { + &self.description.build_information.build_version + } + + pub fn entry_information(&self) -> BasicEntryInformation { + BasicEntryInformation { + hostname: self.description.host_information.hostname.clone(), + ws_port: self.description.mixnet_websockets.ws_port, + wss_port: self.description.mixnet_websockets.wss_port, + } + } + + pub fn ed25519_identity_key(&self) -> ed25519::PublicKey { + self.description.host_information.keys.ed25519 + } + + pub fn current_sphinx_key(&self, current_rotation_id: u32) -> x25519::PublicKey { + let keys = &self.description.host_information.keys; + + if keys.current_x25519_sphinx_key.rotation_id == u32::MAX { + // legacy case (i.e. node doesn't support rotation) + return keys.current_x25519_sphinx_key.public_key; + } + + if current_rotation_id == keys.current_x25519_sphinx_key.rotation_id { + // it's the 'current' key + return keys.current_x25519_sphinx_key.public_key; + } + + if let Some(pre_announced) = &keys.pre_announced_x25519_sphinx_key { + if pre_announced.rotation_id == current_rotation_id { + return pre_announced.public_key; + } + } + + warn!( + "unexpected key rotation {current_rotation_id} for node {}", + self.node_id + ); + // this should never be reached, but just in case, return the fallback option + keys.current_x25519_sphinx_key.public_key + } + + pub fn to_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SkimmedNode { + let keys = &self.description.host_information.keys; + let entry = if self.description.declared_role.entry { + Some(self.entry_information()) + } else { + None + }; + + SkimmedNode { + node_id: self.node_id, + ed25519_identity_pubkey: keys.ed25519, + ip_addresses: self.description.host_information.ip_address.clone(), + mix_port: self.description.mix_port(), + x25519_sphinx_pubkey: self.current_sphinx_key(current_rotation_id), + // we can't use the declared roles, we have to take whatever was provided in the contract. + // why? say this node COULD operate as an exit, but it might be the case the contract decided + // to assign it an ENTRY role only. we have to use that one instead. + role, + supported_roles: self.description.declared_role, + entry, + performance, + } + } + + pub fn to_semi_skimmed_node( + &self, + current_rotation_id: u32, + role: NodeRole, + performance: Performance, + ) -> SemiSkimmedNode { + let skimmed_node = self.to_skimmed_node(current_rotation_id, role, performance); + + SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[serde(rename_all = "snake_case")] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DescribedNodeType.ts" + ) +)] +pub enum DescribedNodeType { + LegacyMixnode, + LegacyGateway, + NymNode, +} + +impl DescribedNodeType { + pub fn is_nym_node(&self) -> bool { + matches!(self, DescribedNodeType::NymNode) + } +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DeclaredRoles.ts" + ) +)] +pub struct DeclaredRoles { + pub mixnode: bool, + pub entry: bool, + pub exit_nr: bool, + pub exit_ipr: bool, +} + +impl DeclaredRoles { + pub fn can_operate_exit_gateway(&self) -> bool { + self.exit_ipr && self.exit_nr + } +} + +impl From for DeclaredRoles { + fn from(value: NodeRoles) -> Self { + DeclaredRoles { + mixnode: value.mixnode_enabled, + entry: value.gateway_enabled, + exit_nr: value.gateway_enabled && value.network_requester_enabled, + exit_ipr: value.gateway_enabled && value.ip_packet_router_enabled, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NetworkRequesterDetails { + /// address of the embedded network requester + pub address: String, + + /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list + pub uses_exit_policy: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct IpPacketRouterDetails { + /// address of the embedded ip packet router + pub address: String, +} + +// works for current simple case. +impl From for IpPacketRouterDetails { + fn from(value: IpPacketRouter) -> Self { + IpPacketRouterDetails { + address: value.address, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct AuthenticatorDetails { + /// address of the embedded authenticator + pub address: String, +} + +// works for current simple case. +impl From for AuthenticatorDetails { + fn from(value: Authenticator) -> Self { + AuthenticatorDetails { + address: value.address, + } + } +} +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct WireguardDetails { + pub port: u16, + pub public_key: String, +} + +// works for current simple case. +impl From for WireguardDetails { + fn from(value: Wireguard) -> Self { + WireguardDetails { + port: value.port, + public_key: value.public_key, + } + } +} + +// this struct is getting quite bloated... +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NymNodeData { + #[serde(default)] + pub last_polled: OffsetDateTimeJsonSchemaWrapper, + + pub host_information: HostInformation, + + #[serde(default)] + pub declared_role: DeclaredRoles, + + #[serde(default)] + pub auxiliary_details: AuxiliaryDetails, + + // TODO: do we really care about ALL build info or just the version? + pub build_information: BinaryBuildInformationOwned, + + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + + #[serde(default)] + pub authenticator: Option, + + #[serde(default)] + pub wireguard: Option, + + // for now we only care about their ws/wss situation, nothing more + pub mixnet_websockets: WebSockets, +} + +impl NymNodeData { + pub fn mix_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .mix_port + .unwrap_or(DEFAULT_MIX_LISTENING_PORT) + } + + pub fn verloc_port(&self) -> u16 { + self.auxiliary_details + .announce_ports + .verloc_port + .unwrap_or(DEFAULT_VERLOC_LISTENING_PORT) + } +} diff --git a/nym-api/nym-api-requests/src/models/legacy.rs b/nym-api/nym-api-requests/src/models/legacy.rs new file mode 100644 index 0000000000..c6cb6b2a78 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/legacy.rs @@ -0,0 +1,364 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::{CoinSchema, DeclaredRoles}; +use crate::legacy::{ + LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer, +}; +use crate::models::{NodePerformance, NymNodeData, StakeSaturation}; +use crate::nym_nodes::{BasicEntryInformation, NodeRole, SemiSkimmedNode, SkimmedNode}; +use cosmwasm_std::{Addr, Coin, Decimal}; +use nym_contracts_common::Percent; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::rewarding::RewardEstimate; +use nym_mixnet_contract_common::{GatewayBond, Interval, MixNode, NodeId, RewardingParams}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::net::IpAddr; +use std::time::Duration; +use thiserror::Error; +use utoipa::{IntoParams, ToSchema}; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct LegacyDescribedGateway { + pub bond: GatewayBond, + pub self_described: Option, +} + +impl From for LegacyDescribedGateway { + fn from(bond: LegacyGatewayBondWithId) -> Self { + LegacyDescribedGateway { + bond: bond.bond, + self_described: None, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct LegacyDescribedMixNode { + pub bond: LegacyMixNodeBondWithLayer, + pub self_described: Option, +} + +impl From for LegacyDescribedMixNode { + fn from(bond: LegacyMixNodeBondWithLayer) -> Self { + LegacyDescribedMixNode { + bond, + self_described: None, + } + } +} + +#[deprecated] +#[derive(Clone, Serialize, schemars::JsonSchema, ToSchema)] +pub struct InclusionProbability { + #[schema(value_type = u32)] + pub mix_id: NodeId, + pub in_active: f64, + pub in_reserve: f64, +} + +#[deprecated] +#[derive(Clone, Serialize, schemars::JsonSchema, ToSchema)] +pub struct AllInclusionProbabilitiesResponse { + pub inclusion_probabilities: Vec, + pub samples: u64, + pub elapsed: Duration, + pub delta_max: f64, + pub delta_l2: f64, + pub as_at: i64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/SelectionChance.ts" + ) +)] +#[deprecated] +pub enum SelectionChance { + High, + Good, + Low, +} + +impl From for SelectionChance { + fn from(p: f64) -> SelectionChance { + match p { + p if p >= 0.7 => SelectionChance::High, + p if p >= 0.3 => SelectionChance::Good, + _ => SelectionChance::Low, + } + } +} + +impl From for SelectionChance { + fn from(p: Decimal) -> Self { + match p { + p if p >= Decimal::from_ratio(70u32, 100u32) => SelectionChance::High, + p if p >= Decimal::from_ratio(30u32, 100u32) => SelectionChance::Good, + _ => SelectionChance::Low, + } + } +} + +impl fmt::Display for SelectionChance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SelectionChance::High => write!(f, "High"), + SelectionChance::Good => write!(f, "Good"), + SelectionChance::Low => write!(f, "Low"), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/InclusionProbabilityResponse.ts" + ) +)] +#[deprecated] +pub struct InclusionProbabilityResponse { + pub in_active: SelectionChance, + pub in_reserve: SelectionChance, +} + +impl fmt::Display for InclusionProbabilityResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "in_active: {}, in_reserve: {}", + self.in_active, self.in_reserve + ) + } +} + +#[derive(Debug, Serialize, Deserialize, JsonSchema, ToSchema, IntoParams)] +pub struct ComputeRewardEstParam { + #[schema(value_type = Option)] + #[param(value_type = Option)] + pub performance: Option, + pub active_in_rewarded_set: Option, + pub pledge_amount: Option, + pub total_delegation: Option, + #[schema(value_type = Option)] + #[param(value_type = Option)] + pub interval_operating_cost: Option, + #[schema(value_type = Option)] + #[param(value_type = Option)] + pub profit_margin_percent: Option, +} + +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/RewardEstimationResponse.ts" + ) +)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct RewardEstimationResponse { + pub estimation: RewardEstimate, + pub reward_params: RewardingParams, + pub epoch: Interval, + #[cfg_attr(feature = "generate-ts", ts(type = "number"))] + pub as_at: i64, +} + +impl MixNodeBondAnnotated { + pub fn mix_node(&self) -> &MixNode { + &self.mixnode_details.bond_information.mix_node + } + + pub fn mix_id(&self) -> NodeId { + self.mixnode_details.mix_id() + } + + pub fn identity_key(&self) -> &str { + self.mixnode_details.bond_information.identity() + } + + pub fn owner(&self) -> &Addr { + self.mixnode_details.bond_information.owner() + } + + pub fn version(&self) -> &str { + &self.mixnode_details.bond_information.mix_node.version + } + + pub fn try_to_skimmed_node(&self, role: NodeRole) -> Result { + Ok(SkimmedNode { + node_id: self.mix_id(), + ed25519_identity_pubkey: self + .identity_key() + .parse() + .map_err(|_| MalformedNodeBond::InvalidEd25519Key)?, + ip_addresses: self.ip_addresses.clone(), + mix_port: self.mix_node().mix_port, + x25519_sphinx_pubkey: self + .mix_node() + .sphinx_key + .parse() + .map_err(|_| MalformedNodeBond::InvalidX25519Key)?, + role, + supported_roles: DeclaredRoles { + mixnode: true, + entry: false, + exit_nr: false, + exit_ipr: false, + }, + entry: None, + performance: self.node_performance.last_24h, + }) + } + + pub fn try_to_semi_skimmed_node( + &self, + role: NodeRole, + ) -> Result { + let skimmed_node = self.try_to_skimmed_node(role)?; + Ok(SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: None, // legacy node won't ever support Noise + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct GatewayBondAnnotated { + pub gateway_bond: LegacyGatewayBondWithId, + + #[serde(default)] + pub self_described: Option, + + // NOTE: the performance field is deprecated in favour of node_performance + #[schema(value_type = String)] + pub performance: Performance, + pub node_performance: NodePerformance, + pub blacklisted: bool, + + #[serde(default)] + #[schema(value_type = Vec)] + pub ip_addresses: Vec, +} + +impl GatewayBondAnnotated { + pub fn version(&self) -> &str { + &self.gateway_bond.gateway.version + } + + pub fn identity(&self) -> &String { + self.gateway_bond.bond.identity() + } + + pub fn owner(&self) -> &Addr { + self.gateway_bond.bond.owner() + } + + pub fn try_to_skimmed_node(&self, role: NodeRole) -> Result { + Ok(SkimmedNode { + node_id: self.gateway_bond.node_id, + ip_addresses: self.ip_addresses.clone(), + ed25519_identity_pubkey: self + .gateway_bond + .gateway + .identity_key + .parse() + .map_err(|_| MalformedNodeBond::InvalidEd25519Key)?, + mix_port: self.gateway_bond.bond.gateway.mix_port, + x25519_sphinx_pubkey: self + .gateway_bond + .gateway + .sphinx_key + .parse() + .map_err(|_| MalformedNodeBond::InvalidX25519Key)?, + role, + supported_roles: DeclaredRoles { + mixnode: false, + entry: true, + exit_nr: false, + exit_ipr: false, + }, + entry: Some(BasicEntryInformation { + hostname: None, + ws_port: self.gateway_bond.bond.gateway.clients_port, + wss_port: None, + }), + performance: self.node_performance.last_24h, + }) + } + + pub fn try_to_semi_skimmed_node( + &self, + role: NodeRole, + ) -> Result { + let skimmed_node = self.try_to_skimmed_node(role)?; + Ok(SemiSkimmedNode { + basic: skimmed_node, + x25519_noise_versioned_key: None, // legacy node won't ever support Noise + }) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct GatewayDescription { + // for now only expose what we need. this struct will evolve in the future (or be incorporated into nym-node properly) +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[schema(title = "LegacyMixNodeDetailsWithLayer")] +pub struct LegacyMixNodeDetailsWithLayerSchema { + /// Basic bond information of this mixnode, such as owner address, original pledge, etc. + #[schema(example = "unimplemented schema")] + pub bond_information: String, + + /// Details used for computation of rewarding related data. + #[schema(example = "unimplemented schema")] + pub rewarding_details: String, + + /// Adjustments to the mixnode that are ought to happen during future epoch transitions. + #[schema(example = "unimplemented schema")] + pub pending_changes: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct MixNodeBondAnnotated { + #[schema(value_type = LegacyMixNodeDetailsWithLayerSchema)] + pub mixnode_details: LegacyMixNodeDetailsWithLayer, + #[schema(value_type = String)] + pub stake_saturation: StakeSaturation, + #[schema(value_type = String)] + pub uncapped_stake_saturation: StakeSaturation, + // NOTE: the performance field is deprecated in favour of node_performance + #[schema(value_type = String)] + pub performance: Performance, + pub node_performance: NodePerformance, + #[schema(value_type = String)] + pub estimated_operator_apy: Decimal, + #[schema(value_type = String)] + pub estimated_delegators_apy: Decimal, + pub blacklisted: bool, + + // a rather temporary thing until we query self-described endpoints of mixnodes + #[serde(default)] + #[schema(value_type = Vec)] + pub ip_addresses: Vec, +} + +#[derive(Debug, Error)] +pub enum MalformedNodeBond { + #[error("the associated ed25519 identity key is malformed")] + InvalidEd25519Key, + + #[error("the associated x25519 sphinx key is malformed")] + InvalidX25519Key, +} diff --git a/nym-api/nym-api-requests/src/models/mixnet.rs b/nym-api/nym-api-requests/src/models/mixnet.rs new file mode 100644 index 0000000000..42f8bf1a72 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/mixnet.rs @@ -0,0 +1,242 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_mixnet_contract_common::nym_node::Role; +use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState, NodeId}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::time::Duration; +use time::OffsetDateTime; +use tracing::warn; +use utoipa::ToSchema; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct KeyRotationInfoResponse { + #[serde(flatten)] + pub details: KeyRotationDetails, + + // helper field that holds calculated data based on the `details` field + // this is to expose the information in a format more easily accessible by humans + // without having to do any calculations + pub progress: KeyRotationProgressInfo, +} + +impl From for KeyRotationInfoResponse { + fn from(details: KeyRotationDetails) -> Self { + KeyRotationInfoResponse { + details, + progress: KeyRotationProgressInfo { + current_key_rotation_id: details.current_key_rotation_id(), + current_rotation_starting_epoch: details.current_rotation_starting_epoch_id(), + current_rotation_ending_epoch: details.current_rotation_starting_epoch_id() + + details.key_rotation_state.validity_epochs + - 1, + }, + } + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct KeyRotationProgressInfo { + pub current_key_rotation_id: u32, + + pub current_rotation_starting_epoch: u32, + + pub current_rotation_ending_epoch: u32, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct KeyRotationDetails { + pub key_rotation_state: KeyRotationState, + + #[schema(value_type = u32)] + pub current_absolute_epoch_id: EpochId, + + #[serde(with = "time::serde::rfc3339")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub current_epoch_start: OffsetDateTime, + + pub epoch_duration: Duration, +} + +impl KeyRotationDetails { + pub fn current_key_rotation_id(&self) -> u32 { + self.key_rotation_state + .key_rotation_id(self.current_absolute_epoch_id) + } + + pub fn next_rotation_starting_epoch_id(&self) -> EpochId { + self.key_rotation_state + .next_rotation_starting_epoch_id(self.current_absolute_epoch_id) + } + + pub fn current_rotation_starting_epoch_id(&self) -> EpochId { + self.key_rotation_state + .current_rotation_starting_epoch_id(self.current_absolute_epoch_id) + } + + fn current_epoch_progress(&self, now: OffsetDateTime) -> f32 { + let elapsed = (now - self.current_epoch_start).as_seconds_f32(); + elapsed / self.epoch_duration.as_secs_f32() + } + + pub fn is_epoch_stuck(&self) -> bool { + let now = OffsetDateTime::now_utc(); + let progress = self.current_epoch_progress(now); + if progress > 1. { + let into_next = 1. - progress; + // if epoch hasn't progressed for more than 20% of its duration, mark is as stuck + if into_next > 0.2 { + let diff_time = + Duration::from_secs_f32(into_next * self.epoch_duration.as_secs_f32()); + let expected_epoch_end = self.current_epoch_start + self.epoch_duration; + warn!("the current epoch is expected to have been over by {expected_epoch_end}. it's already {} overdue!", humantime_serde::re::humantime::format_duration(diff_time)); + return true; + } + } + + false + } + + // based on the current **TIME**, determine what's the expected current rotation id + pub fn expected_current_rotation_id(&self) -> KeyRotationId { + let now = OffsetDateTime::now_utc(); + let current_end = now + self.epoch_duration; + if now < current_end { + return self + .key_rotation_state + .key_rotation_id(self.current_absolute_epoch_id); + } + + let diff = now - current_end; + let passed_epochs = diff / self.epoch_duration; + let expected_current_epoch = self.current_absolute_epoch_id + passed_epochs.floor() as u32; + + self.key_rotation_state + .key_rotation_id(expected_current_epoch) + } + + pub fn until_next_rotation(&self) -> Option { + let current_epoch_progress = self.current_epoch_progress(OffsetDateTime::now_utc()); + if current_epoch_progress > 1. { + return None; + } + + let next_rotation_epoch = self.next_rotation_starting_epoch_id(); + let full_remaining = + (next_rotation_epoch - self.current_absolute_epoch_id).checked_add(1)?; + + let epochs_until_next_rotation = (1. - current_epoch_progress) + full_remaining as f32; + + Some(Duration::from_secs_f32( + epochs_until_next_rotation * self.epoch_duration.as_secs_f32(), + )) + } + + pub fn epoch_start_time(&self, absolute_epoch_id: EpochId) -> OffsetDateTime { + match absolute_epoch_id.cmp(&self.current_absolute_epoch_id) { + Ordering::Less => { + let diff = self.current_absolute_epoch_id - absolute_epoch_id; + self.current_epoch_start - diff * self.epoch_duration + } + Ordering::Equal => self.current_epoch_start, + Ordering::Greater => { + let diff = absolute_epoch_id - self.current_absolute_epoch_id; + self.current_epoch_start + diff * self.epoch_duration + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct RewardedSetResponse { + #[serde(default)] + #[schema(value_type = u32)] + pub epoch_id: EpochId, + + pub entry_gateways: Vec, + + pub exit_gateways: Vec, + + pub layer1: Vec, + + pub layer2: Vec, + + pub layer3: Vec, + + pub standby: Vec, +} + +impl From for nym_mixnet_contract_common::EpochRewardedSet { + fn from(res: RewardedSetResponse) -> Self { + nym_mixnet_contract_common::EpochRewardedSet { + epoch_id: res.epoch_id, + assignment: nym_mixnet_contract_common::RewardedSet { + entry_gateways: res.entry_gateways, + exit_gateways: res.exit_gateways, + layer1: res.layer1, + layer2: res.layer2, + layer3: res.layer3, + standby: res.standby, + }, + } + } +} + +impl From for RewardedSetResponse { + fn from(r: nym_mixnet_contract_common::EpochRewardedSet) -> Self { + RewardedSetResponse { + epoch_id: r.epoch_id, + entry_gateways: r.assignment.entry_gateways, + exit_gateways: r.assignment.exit_gateways, + layer1: r.assignment.layer1, + layer2: r.assignment.layer2, + layer3: r.assignment.layer3, + standby: r.assignment.standby, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, JsonSchema, ToSchema)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export, export_to = "ts-packages/types/src/types/rust/DisplayRole.ts") +)] +pub enum DisplayRole { + EntryGateway, + Layer1, + Layer2, + Layer3, + ExitGateway, + Standby, +} + +impl From for DisplayRole { + fn from(role: Role) -> Self { + match role { + Role::EntryGateway => DisplayRole::EntryGateway, + Role::Layer1 => DisplayRole::Layer1, + Role::Layer2 => DisplayRole::Layer2, + Role::Layer3 => DisplayRole::Layer3, + Role::ExitGateway => DisplayRole::ExitGateway, + Role::Standby => DisplayRole::Standby, + } + } +} + +impl From for Role { + fn from(role: DisplayRole) -> Self { + match role { + DisplayRole::EntryGateway => Role::EntryGateway, + DisplayRole::Layer1 => Role::Layer1, + DisplayRole::Layer2 => Role::Layer2, + DisplayRole::Layer3 => Role::Layer3, + DisplayRole::ExitGateway => Role::ExitGateway, + DisplayRole::Standby => Role::Standby, + } + } +} diff --git a/nym-api/nym-api-requests/src/models/mod.rs b/nym-api/nym-api-requests/src/models/mod.rs new file mode 100644 index 0000000000..76074be659 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/mod.rs @@ -0,0 +1,62 @@ +// Copyright 2022-2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![allow(deprecated)] + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::fmt::{Display, Formatter}; + +pub mod api_status; +pub mod circulating_supply; +pub mod described; +pub mod legacy; +pub mod mixnet; +pub mod network; +pub mod network_monitor; +pub mod node_status; +pub mod schema_helpers; + +// don't break existing imports +pub use api_status::*; +pub use circulating_supply::*; +pub use described::*; +pub use legacy::*; +pub use mixnet::*; +pub use network::*; +pub use network_monitor::*; +pub use node_status::*; +pub use schema_helpers::*; + +pub use nym_mixnet_contract_common::{EpochId, KeyRotationId, KeyRotationState}; +pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +pub struct RequestError { + message: String, +} + +impl RequestError { + pub fn new>(msg: S) -> Self { + RequestError { + message: msg.into(), + } + } + + pub fn message(&self) -> &str { + &self.message + } + + pub fn empty() -> Self { + Self { + message: String::new(), + } + } +} + +impl Display for RequestError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.message, f) + } +} diff --git a/nym-api/nym-api-requests/src/models/network.rs b/nym-api/nym-api-requests/src/models/network.rs new file mode 100644 index 0000000000..e998656c67 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/network.rs @@ -0,0 +1,562 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::ecash::models::EcashSignerStatusResponse; +use crate::models::tendermint_types::{BlockHeader, BlockId}; +use crate::models::{ChainStatus, SignerInformationResponse}; +use crate::signable::SignedMessage; +use nym_coconut_dkg_common::types::EpochId; +use nym_crypto::asymmetric::ed25519::PublicKey; +use nym_ecash_signer_check_types::helper_traits::{ + ChainResponse, LegacyChainResponse, LegacySignerResponse, SignerResponse, TimestampedResponse, + Verifiable, +}; +use nym_ecash_signer_check_types::status::SignerResult; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use time::OffsetDateTime; +use utoipa::ToSchema; + +pub type ChainBlocksStatusResponse = SignedMessage; +pub type SignersStatusResponse = SignedMessage; +pub type DetailedSignersStatusResponse = SignedMessage; + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SignersStatusResponseBody { + #[serde(with = "time::serde::rfc3339")] + #[schema(value_type = String)] + pub as_at: OffsetDateTime, + + pub overview: SignersStatusOverview, + + pub results: Vec, +} + +pub type TypedSignerResult = SignerResult< + SignerInformationResponse, + EcashSignerStatusResponse, + ChainStatusResponse, + ChainBlocksStatusResponse, +>; + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MinimalSignerResult { + pub announce_address: String, + pub owner_address: String, + pub node_index: u64, + pub public_key: String, + + pub local_chain_working: bool, + pub credential_issuance_available: bool, +} + +impl From<&TypedSignerResult> for MinimalSignerResult { + fn from(result: &TypedSignerResult) -> MinimalSignerResult { + MinimalSignerResult { + announce_address: result.information.announce_address.clone(), + owner_address: result.information.owner_address.clone(), + node_index: result.information.node_index, + public_key: result.information.public_key.clone(), + local_chain_working: result.chain_available(), + credential_issuance_available: result.signing_available(), + } + } +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct DetailedSignersStatusResponseBody { + #[serde(with = "time::serde::rfc3339")] + #[schema(value_type = String)] + pub as_at: OffsetDateTime, + + pub overview: SignersStatusOverview, + + pub details: Vec, +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SignersStatusOverview { + #[schema(value_type = Option)] + pub epoch_id: Option, + + pub signing_threshold: Option, + pub threshold_available: Option, + + pub total_signers: usize, + pub unreachable_signers: usize, + pub malformed_signers: usize, + + // unreachable or outdated + pub unknown_local_chain_status: usize, + pub working_local_chain: usize, + + // i.e. provided signature + pub provably_stalled_local_chain: usize, + pub unprovably_stalled_local_chain: usize, + + // unreachable or outdated + pub unknown_credential_issuance_status: usize, + pub working_credential_issuance: usize, + + // i.e. provided signature + pub provably_unavailable_credential_issuance: usize, + pub unprovably_unavailable_credential_issuance: usize, +} + +impl SignersStatusOverview { + pub fn new(results: &[TypedSignerResult], signing_threshold: Option) -> Self { + let epoch_id = results.first().map(|r| r.dkg_epoch_id); + + let mut unreachable_signers = 0; + let mut malformed_signers = 0; + let mut unknown_local_chain_status = 0; + let mut working_local_chain = 0; + let mut provably_stalled_local_chain = 0; + let mut unprovably_stalled_local_chain = 0; + let mut unknown_credential_issuance_status = 0; + let mut working_credential_issuance = 0; + let mut provably_unavailable_credential_issuance = 0; + let mut unprovably_unavailable_credential_issuance = 0; + + for result in results { + if result.signer_unreachable() { + unreachable_signers += 1; + } + if result.malformed_details() { + malformed_signers += 1; + } + + if result.unknown_chain_status() { + unknown_local_chain_status += 1; + } + if result.chain_available() { + working_local_chain += 1; + } + if result.chain_provably_stalled() { + provably_stalled_local_chain += 1; + } + if result.chain_unprovably_stalled() { + unprovably_stalled_local_chain += 1; + } + + if result.unknown_signing_status() { + unknown_credential_issuance_status += 1; + } + if result.signing_available() { + working_credential_issuance += 1; + } + if result.signing_provably_unavailable() { + provably_unavailable_credential_issuance += 1; + } + if result.signing_unprovably_unavailable() { + unprovably_unavailable_credential_issuance += 1; + } + } + + SignersStatusOverview { + epoch_id, + signing_threshold, + threshold_available: signing_threshold.map(|threshold| { + (working_local_chain as u64) >= threshold + && (working_credential_issuance as u64) >= threshold + }), + total_signers: results.len(), + unreachable_signers, + malformed_signers, + unknown_local_chain_status, + working_local_chain, + provably_stalled_local_chain, + unprovably_stalled_local_chain, + unknown_credential_issuance_status, + working_credential_issuance, + provably_unavailable_credential_issuance, + unprovably_unavailable_credential_issuance, + } + } +} + +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ChainBlocksStatusResponseBody { + #[serde(with = "time::serde::rfc3339")] + #[schema(value_type = String)] + pub current_time: OffsetDateTime, + + pub latest_cached_block: Option, + + // explicit indication of THIS signer whether it thinks the chain is stalled + pub chain_status: ChainStatus, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct ChainStatusResponse { + pub connected_nyxd: String, + pub status: DetailedChainStatus, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct DetailedChainStatus { + pub abci: crate::models::tendermint_types::AbciInfo, + pub latest_block: BlockInfo, +} + +impl DetailedChainStatus { + pub fn stall_status(&self, now: OffsetDateTime, threshold: Duration) -> ChainStatus { + let block_time: OffsetDateTime = self.latest_block.block.header.time.into(); + let diff = now - block_time; + if diff > threshold { + ChainStatus::Stalled { + approximate_amount: diff.unsigned_abs(), + } + } else { + ChainStatus::Synced + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct BlockInfo { + pub block_id: BlockId, + pub block: FullBlockInfo, + // if necessary we might put block data here later too +} + +impl From for BlockInfo { + fn from(value: tendermint_rpc::endpoint::block::Response) -> Self { + BlockInfo { + block_id: value.block_id.into(), + block: FullBlockInfo { + header: value.block.header.into(), + }, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct FullBlockInfo { + pub header: BlockHeader, +} + +// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields +pub mod tendermint_types { + use schemars::JsonSchema; + use serde::{Deserialize, Serialize}; + use tendermint::abci::response::Info; + use tendermint::block::header::Version; + use tendermint::{block, Hash}; + use utoipa::ToSchema; + + #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] + pub struct AbciInfo { + /// Some arbitrary information. + pub data: String, + + /// The application software semantic version. + pub version: String, + + /// The application protocol version. + pub app_version: u64, + + /// The latest block for which the app has called [`Commit`]. + pub last_block_height: u64, + + /// The latest result of [`Commit`]. + pub last_block_app_hash: String, + } + + impl From for AbciInfo { + fn from(value: Info) -> Self { + AbciInfo { + data: value.data, + version: value.version, + app_version: value.app_version, + last_block_height: value.last_block_height.value(), + last_block_app_hash: value.last_block_app_hash.to_string(), + } + } + } + + /// `Version` contains the protocol version for the blockchain and the + /// application. + /// + /// + #[derive( + Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema, + )] + pub struct HeaderVersion { + /// Block version + pub block: u64, + + /// App version + pub app: u64, + } + + impl From for HeaderVersion { + fn from(value: Version) -> Self { + HeaderVersion { + block: value.block, + app: value.app, + } + } + } + + /// Block identifiers which contain two distinct Merkle roots of the block, + /// as well as the number of parts in the block. + /// + /// + /// + /// Default implementation is an empty Id as defined by the Go implementation in + /// . + /// + /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). + /// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId. + /// See: + #[derive( + Serialize, + Deserialize, + Copy, + Clone, + Debug, + Default, + Hash, + Eq, + PartialEq, + PartialOrd, + Ord, + JsonSchema, + ToSchema, + )] + pub struct BlockId { + /// The block's main hash is the Merkle root of all the fields in the + /// block header. + #[schemars(with = "String")] + #[schema(value_type = String)] + pub hash: Hash, + + /// Parts header (if available) is used for secure gossipping of the block + /// during consensus. It is the Merkle root of the complete serialized block + /// cut into parts. + /// + /// PartSet is used to split a byteslice of data into parts (pieces) for + /// transmission. By splitting data into smaller parts and computing a + /// Merkle root hash on the list, you can verify that a part is + /// legitimately part of the complete data, and the part can be forwarded + /// to other peers before all the parts are known. In short, it's a fast + /// way to propagate a large file over a gossip network. + /// + /// + /// + /// PartSetHeader in protobuf is defined as never nil using the gogoproto + /// annotations. This does not translate to Rust, but we can indicate this + /// in the domain type. + pub part_set_header: PartSetHeader, + } + + impl From for BlockId { + fn from(value: block::Id) -> Self { + BlockId { + hash: value.hash, + part_set_header: value.part_set_header.into(), + } + } + } + + /// Block parts header + #[derive( + Clone, + Copy, + Debug, + Default, + Hash, + Eq, + PartialEq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, + ToSchema, + )] + #[non_exhaustive] + pub struct PartSetHeader { + /// Number of parts in this block + pub total: u32, + + /// Hash of the parts set header, + #[schemars(with = "String")] + #[schema(value_type = String)] + pub hash: Hash, + } + + impl From for PartSetHeader { + fn from(value: block::parts::Header) -> Self { + PartSetHeader { + total: value.total, + hash: value.hash, + } + } + } + + /// Block `Header` values contain metadata about the block and about the + /// consensus, as well as commitments to the data in the current block, the + /// previous block, and the results returned by the application. + /// + /// + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)] + pub struct BlockHeader { + /// Header version + pub version: HeaderVersion, + + /// Chain ID + pub chain_id: String, + + /// Current block height + pub height: u64, + + /// Current timestamp + #[schemars(with = "String")] + #[schema(value_type = String)] + pub time: tendermint::Time, + + /// Previous block info + pub last_block_id: Option, + + /// Commit from validators from the last block + #[schemars(with = "Option")] + #[schema(value_type = Option)] + pub last_commit_hash: Option, + + /// Merkle root of transaction hashes + #[schemars(with = "Option")] + #[schema(value_type = Option)] + pub data_hash: Option, + + /// Validators for the current block + #[schemars(with = "String")] + #[schema(value_type = String)] + pub validators_hash: Hash, + + /// Validators for the next block + #[schemars(with = "String")] + #[schema(value_type = String)] + pub next_validators_hash: Hash, + + /// Consensus params for the current block + #[schemars(with = "String")] + #[schema(value_type = String)] + pub consensus_hash: Hash, + + /// State after txs from the previous block + #[schemars(with = "String")] + #[schema(value_type = String)] + pub app_hash: Hash, + + /// Root hash of all results from the txs from the previous block + #[schemars(with = "Option")] + #[schema(value_type = Option)] + pub last_results_hash: Option, + + /// Hash of evidence included in the block + #[schemars(with = "Option")] + #[schema(value_type = Option)] + pub evidence_hash: Option, + + /// Original proposer of the block + #[serde(with = "nym_serde_helpers::hex")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub proposer_address: Vec, + } + + impl From for BlockHeader { + fn from(value: block::Header) -> Self { + BlockHeader { + version: value.version.into(), + chain_id: value.chain_id.to_string(), + height: value.height.value(), + time: value.time, + last_block_id: value.last_block_id.map(Into::into), + last_commit_hash: value.last_commit_hash, + data_hash: value.data_hash, + validators_hash: value.validators_hash, + next_validators_hash: value.next_validators_hash, + consensus_hash: value.consensus_hash, + app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(), + last_results_hash: value.last_results_hash, + evidence_hash: value.evidence_hash, + proposer_address: value.proposer_address.as_bytes().to_vec(), + } + } + } +} + +// implement required traits for the signer responses + +impl LegacyChainResponse for ChainStatusResponse { + fn chain_synced(&self, now: OffsetDateTime, stall_threshold: Duration) -> bool { + self.status.stall_status(now, stall_threshold).is_synced() + } +} + +impl Verifiable for ChainBlocksStatusResponse { + fn verify_signature(&self, pub_key: &PublicKey) -> bool { + self.verify_signature(pub_key) + } +} + +impl TimestampedResponse for ChainBlocksStatusResponse { + fn timestamp(&self) -> OffsetDateTime { + self.body.current_time + } +} + +impl ChainResponse for ChainBlocksStatusResponse { + fn chain_synced(&self) -> bool { + self.body.chain_status.is_synced() + } +} + +impl LegacySignerResponse for SignerInformationResponse { + fn signer_identity(&self) -> &str { + &self.identity + } + + fn signer_verification_key(&self) -> &Option { + &self.verification_key + } +} + +impl Verifiable for EcashSignerStatusResponse { + fn verify_signature(&self, pub_key: &PublicKey) -> bool { + self.verify_signature(pub_key) + } +} + +impl TimestampedResponse for EcashSignerStatusResponse { + fn timestamp(&self) -> OffsetDateTime { + self.body.current_time + } +} + +impl SignerResponse for EcashSignerStatusResponse { + fn has_signing_keys(&self) -> bool { + self.body.has_signing_keys + } + + fn signer_disabled(&self) -> bool { + self.body.signer_disabled + } + + fn is_ecash_signer(&self) -> bool { + self.body.is_ecash_signer + } + + fn dkg_ecash_epoch_id(&self) -> EpochId { + self.body.dkg_ecash_epoch_id + } +} diff --git a/nym-api/nym-api-requests/src/models/network_monitor.rs b/nym-api/nym-api-requests/src/models/network_monitor.rs new file mode 100644 index 0000000000..c253bd28ca --- /dev/null +++ b/nym-api/nym-api-requests/src/models/network_monitor.rs @@ -0,0 +1,74 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::pagination::PaginatedResponse; +use nym_mixnet_contract_common::NodeId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use utoipa::ToSchema; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, Default, ToSchema)] +pub struct TestNode { + pub node_id: Option, + pub identity_key: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct TestRoute { + pub gateway: TestNode, + pub layer1: TestNode, + pub layer2: TestNode, + pub layer3: TestNode, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct PartialTestResult { + pub monitor_run_id: i64, + pub timestamp: i64, + pub overall_reliability_for_all_routes_in_monitor_run: Option, + pub test_routes: TestRoute, +} + +pub type MixnodeTestResultResponse = PaginatedResponse; +pub type GatewayTestResultResponse = PaginatedResponse; + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NetworkMonitorRunDetailsResponse { + pub monitor_run_id: i64, + pub network_reliability: f64, + pub total_sent: usize, + pub total_received: usize, + + // integer score to number of nodes with that score + pub mixnode_results: BTreeMap, + pub gateway_results: BTreeMap, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/MixnodeCoreStatusResponse.ts" + ) +)] +pub struct MixnodeCoreStatusResponse { + pub mix_id: NodeId, + pub count: i64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/GatewayCoreStatusResponse.ts" + ) +)] +pub struct GatewayCoreStatusResponse { + pub identity: String, + pub count: i64, +} diff --git a/nym-api/nym-api-requests/src/models/node_status.rs b/nym-api/nym-api-requests/src/models/node_status.rs new file mode 100644 index 0000000000..03bd0ce2d8 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/node_status.rs @@ -0,0 +1,587 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::helpers::PlaceholderJsonSchemaImpl; +use crate::pagination::PaginatedResponse; +use cosmwasm_std::Decimal; +use nym_contracts_common::{IdentityKey, NaiveFloat}; +use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_pubkey; +use nym_mixnet_contract_common::reward_params::Performance; +use nym_mixnet_contract_common::NodeId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use time::{Date, OffsetDateTime}; +use utoipa::ToSchema; + +use crate::models::DisplayRole; +pub use config_score::*; + +pub type StakeSaturation = Decimal; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/StakeSaturationResponse.ts" + ) +)] +pub struct StakeSaturationResponse { + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + #[schema(value_type = String)] + pub saturation: StakeSaturation, + + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + #[schema(value_type = String)] + pub uncapped_saturation: StakeSaturation, + pub as_at: i64, +} + +pub mod config_score { + use nym_contracts_common::NaiveFloat; + use serde::{Deserialize, Serialize}; + use std::cmp::Ordering; + use utoipa::ToSchema; + + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] + pub struct ConfigScoreDataResponse { + pub parameters: ConfigScoreParams, + pub version_history: Vec, + } + + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] + pub struct HistoricalNymNodeVersionEntry { + /// The unique, ordered, id of this particular entry + pub id: u32, + + /// Data associated with this particular version + pub version_information: HistoricalNymNodeVersion, + } + + impl PartialOrd for HistoricalNymNodeVersionEntry { + fn partial_cmp(&self, other: &Self) -> Option { + // we only care about id for the purposes of ordering as they should have unique data + self.id.partial_cmp(&other.id) + } + } + + impl From + for HistoricalNymNodeVersionEntry + { + fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersionEntry) -> Self { + HistoricalNymNodeVersionEntry { + id: value.id, + version_information: value.version_information.into(), + } + } + } + + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)] + pub struct HistoricalNymNodeVersion { + /// Version of the nym node that is going to be used for determining the version score of a node. + /// note: value stored here is pre-validated `semver::Version` + pub semver: String, + + /// Block height of when this version has been added to the contract + pub introduced_at_height: u64, + // for now ignore that field. it will give nothing useful to the users + // pub difference_since_genesis: TotalVersionDifference, + } + + impl From for HistoricalNymNodeVersion { + fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersion) -> Self { + HistoricalNymNodeVersion { + semver: value.semver, + introduced_at_height: value.introduced_at_height, + } + } + } + + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] + pub struct ConfigScoreParams { + /// Defines weights for calculating numbers of versions behind the current release. + pub version_weights: OutdatedVersionWeights, + + /// Defines the parameters of the formula for calculating the version score + pub version_score_formula_params: VersionScoreFormulaParams, + } + + /// Defines weights for calculating numbers of versions behind the current release. + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] + pub struct OutdatedVersionWeights { + pub major: u32, + pub minor: u32, + pub patch: u32, + pub prerelease: u32, + } + + /// Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) + /// define the relevant parameters + #[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] + pub struct VersionScoreFormulaParams { + pub penalty: f64, + pub penalty_scaling: f64, + } + + impl From for ConfigScoreParams { + fn from(value: nym_mixnet_contract_common::ConfigScoreParams) -> Self { + ConfigScoreParams { + version_weights: value.version_weights.into(), + version_score_formula_params: value.version_score_formula_params.into(), + } + } + } + + impl From for OutdatedVersionWeights { + fn from(value: nym_mixnet_contract_common::OutdatedVersionWeights) -> Self { + OutdatedVersionWeights { + major: value.major, + minor: value.minor, + patch: value.patch, + prerelease: value.prerelease, + } + } + } + + impl From for VersionScoreFormulaParams { + fn from(value: nym_mixnet_contract_common::VersionScoreFormulaParams) -> Self { + VersionScoreFormulaParams { + penalty: value.penalty.naive_to_f64(), + penalty_scaling: value.penalty_scaling.naive_to_f64(), + } + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct NodeRefreshBody { + #[serde(with = "bs58_ed25519_pubkey")] + #[schemars(with = "String")] + #[schema(value_type = String)] + pub node_identity: ed25519::PublicKey, + + // a poor man's nonce + pub request_timestamp: i64, + + #[schemars(with = "PlaceholderJsonSchemaImpl")] + #[schema(value_type = String)] + pub signature: ed25519::Signature, +} + +impl NodeRefreshBody { + pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec { + node_identity + .to_bytes() + .into_iter() + .chain(request_timestamp.to_be_bytes()) + .chain(b"describe-cache-refresh-request".iter().copied()) + .collect() + } + + pub fn new(private_key: &ed25519::PrivateKey) -> Self { + let node_identity = private_key.public_key(); + let request_timestamp = OffsetDateTime::now_utc().unix_timestamp(); + let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp)); + NodeRefreshBody { + node_identity, + request_timestamp, + signature, + } + } + + pub fn verify_signature(&self) -> bool { + self.node_identity + .verify( + Self::plaintext(self.node_identity, self.request_timestamp), + &self.signature, + ) + .is_ok() + } + + pub fn is_stale(&self) -> bool { + let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else { + return true; + }; + let now = OffsetDateTime::now_utc(); + + if encoded > now { + return true; + } + + if (encoded + Duration::from_secs(30)) < now { + return true; + } + + false + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct UptimeResponse { + #[schema(value_type = u32)] + pub mix_id: NodeId, + // The same as node_performance.last_24h. Legacy + pub avg_uptime: u8, + pub node_performance: NodePerformance, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct GatewayUptimeResponse { + pub identity: String, + // The same as node_performance.last_24h. Legacy + pub avg_uptime: u8, + pub node_performance: NodePerformance, +} + +type Uptime = u8; + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct MixnodeStatusReportResponse { + pub mix_id: NodeId, + pub identity: IdentityKey, + pub owner: String, + #[schema(value_type = u8)] + pub most_recent: Uptime, + #[schema(value_type = u8)] + pub last_hour: Uptime, + #[schema(value_type = u8)] + pub last_day: Uptime, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct GatewayStatusReportResponse { + pub identity: String, + pub owner: String, + #[schema(value_type = u8)] + pub most_recent: Uptime, + #[schema(value_type = u8)] + pub last_hour: Uptime, + #[schema(value_type = u8)] + pub last_day: Uptime, +} + +#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/PerformanceHistoryResponse.ts" + ) +)] +pub struct PerformanceHistoryResponse { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub history: PaginatedResponse, +} + +#[derive(Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/UptimeHistoryResponse.ts" + ) +)] +pub struct UptimeHistoryResponse { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub history: PaginatedResponse, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/HistoricalUptimeResponse.ts" + ) +)] +pub struct HistoricalUptimeResponse { + #[schema(value_type = String, example = "1970-01-01")] + #[schemars(with = "String")] + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + pub date: Date, + + pub uptime: Uptime, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/HistoricalPerformanceResponse.ts" + ) +)] +pub struct HistoricalPerformanceResponse { + #[schema(value_type = String, example = "1970-01-01")] + #[schemars(with = "String")] + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + pub date: Date, + + pub performance: f64, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct OldHistoricalUptimeResponse { + pub date: String, + #[schema(value_type = u8)] + pub uptime: Uptime, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct MixnodeUptimeHistoryResponse { + pub mix_id: NodeId, + pub identity: String, + pub owner: String, + pub history: Vec, +} + +#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema, ToSchema)] +pub struct GatewayUptimeHistoryResponse { + pub identity: String, + pub owner: String, + pub history: Vec, +} + +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, ToSchema, Default, +)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/MixnodeStatus.ts" + ) +)] +#[serde(rename_all = "snake_case")] +pub enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set, but is bonded + #[default] + NotFound, // doesn't even exist in the bonded set +} +impl MixnodeStatus { + pub fn is_active(&self) -> bool { + *self == MixnodeStatus::Active + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/MixnodeStatusResponse.ts" + ) +)] +pub struct MixnodeStatusResponse { + pub status: MixnodeStatus, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] +pub struct NodePerformance { + #[schema(value_type = String)] + pub most_recent: Performance, + #[schema(value_type = String)] + pub last_hour: Performance, + #[schema(value_type = String)] + pub last_24h: Performance, +} + +// imo for now there's no point in exposing more than that, +// nym-api shouldn't be calculating apy or stake saturation for you. +// it should just return its own metrics (performance) and then you can do with it as you wish +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/NodeAnnotation.ts" + ) +)] +pub struct NodeAnnotation { + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + // legacy + #[schema(value_type = String)] + pub last_24h_performance: Performance, + pub current_role: Option, + + pub detailed_performance: DetailedNodePerformance, +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/DetailedNodePerformance.ts" + ) +)] +#[non_exhaustive] +pub struct DetailedNodePerformance { + /// routing_score * config_score + pub performance_score: f64, + + pub routing_score: RoutingScore, + pub config_score: ConfigScore, +} + +impl DetailedNodePerformance { + pub fn new( + performance_score: f64, + routing_score: RoutingScore, + config_score: ConfigScore, + ) -> DetailedNodePerformance { + Self { + performance_score, + routing_score, + config_score, + } + } + + pub fn to_rewarding_performance(&self) -> Performance { + Performance::naive_try_from_f64(self.performance_score).unwrap_or_default() + } +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export, export_to = "ts-packages/types/src/types/rust/RoutingScore.ts") +)] +#[non_exhaustive] +pub struct RoutingScore { + /// Total score after taking all the criteria into consideration + pub score: f64, +} + +impl RoutingScore { + pub fn new(score: f64) -> RoutingScore { + Self { score } + } + + pub const fn zero() -> RoutingScore { + RoutingScore { score: 0.0 } + } + + pub fn legacy_performance(&self) -> Performance { + Performance::naive_try_from_f64(self.score).unwrap_or_default() + } +} + +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export, export_to = "ts-packages/types/src/types/rust/ConfigScore.ts") +)] +#[non_exhaustive] +pub struct ConfigScore { + /// Total score after taking all the criteria into consideration + pub score: f64, + + pub versions_behind: Option, + pub self_described_api_available: bool, + pub accepted_terms_and_conditions: bool, + pub runs_nym_node_binary: bool, +} + +impl ConfigScore { + pub fn new( + score: f64, + versions_behind: u32, + accepted_terms_and_conditions: bool, + runs_nym_node_binary: bool, + ) -> ConfigScore { + Self { + score, + versions_behind: Some(versions_behind), + self_described_api_available: true, + accepted_terms_and_conditions, + runs_nym_node_binary, + } + } + + pub fn bad_semver() -> ConfigScore { + ConfigScore { + score: 0.0, + versions_behind: None, + self_described_api_available: true, + accepted_terms_and_conditions: false, + runs_nym_node_binary: false, + } + } + + pub fn unavailable() -> ConfigScore { + ConfigScore { + score: 0.0, + versions_behind: None, + self_described_api_available: false, + accepted_terms_and_conditions: false, + runs_nym_node_binary: false, + } + } +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/AnnotationResponse.ts" + ) +)] +pub struct AnnotationResponse { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub annotation: Option, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/NodePerformanceResponse.ts" + ) +)] +pub struct NodePerformanceResponse { + #[schema(value_type = u32)] + pub node_id: NodeId, + pub performance: Option, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, ToSchema)] +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts( + export, + export_to = "ts-packages/types/src/types/rust/NodeDatePerformanceResponse.ts" + ) +)] +pub struct NodeDatePerformanceResponse { + #[schema(value_type = u32)] + pub node_id: NodeId, + #[schema(value_type = String, example = "1970-01-01")] + #[schemars(with = "String")] + #[cfg_attr(feature = "generate-ts", ts(type = "string"))] + pub date: Date, + pub performance: Option, +} diff --git a/nym-api/nym-api-requests/src/models/schema_helpers.rs b/nym-api/nym-api-requests/src/models/schema_helpers.rs new file mode 100644 index 0000000000..d42c18fc44 --- /dev/null +++ b/nym-api/nym-api-requests/src/models/schema_helpers.rs @@ -0,0 +1,128 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::helpers::unix_epoch; +use cosmwasm_std::Uint128; +use schemars::schema::{InstanceType, Schema, SchemaObject}; +use schemars::{JsonSchema, SchemaGenerator}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt; +use std::fmt::{Display, Formatter}; +use std::ops::{Deref, DerefMut}; +use time::OffsetDateTime; +use utoipa::ToSchema; + +#[derive(ToSchema)] +#[schema(title = "Coin")] +pub struct CoinSchema { + pub denom: String, + #[schema(value_type = String)] + pub amount: Uint128, +} + +pub fn de_rfc3339_or_default<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Ok(time::serde::rfc3339::deserialize(deserializer).unwrap_or_else(|_| unix_epoch())) +} + +// for all intents and purposes it's just OffsetDateTime, but we need JsonSchema... +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, ToSchema)] +pub struct OffsetDateTimeJsonSchemaWrapper( + #[serde( + default = "unix_epoch", + with = "crate::helpers::overengineered_offset_date_time_serde" + )] + #[schema(inline)] + pub OffsetDateTime, +); + +impl Default for OffsetDateTimeJsonSchemaWrapper { + fn default() -> Self { + OffsetDateTimeJsonSchemaWrapper(unix_epoch()) + } +} + +impl Display for OffsetDateTimeJsonSchemaWrapper { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl From for OffsetDateTime { + fn from(value: OffsetDateTimeJsonSchemaWrapper) -> Self { + value.0 + } +} + +impl From for OffsetDateTimeJsonSchemaWrapper { + fn from(value: OffsetDateTime) -> Self { + OffsetDateTimeJsonSchemaWrapper(value) + } +} + +impl Deref for OffsetDateTimeJsonSchemaWrapper { + type Target = OffsetDateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for OffsetDateTimeJsonSchemaWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// implementation taken from: https://github.com/GREsau/schemars/pull/207 +impl JsonSchema for OffsetDateTimeJsonSchemaWrapper { + fn is_referenceable() -> bool { + false + } + + fn schema_name() -> String { + "DateTime".into() + } + + fn json_schema(_: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::String.into()), + format: Some("date-time".into()), + ..Default::default() + } + .into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + #[test] + fn offset_date_time_json_schema_wrapper_serde_backwards_compat() { + let mut dummy = OffsetDateTimeJsonSchemaWrapper::default(); + dummy.0 += Duration::from_millis(1); + let ser = serde_json::to_string(&dummy).unwrap(); + + assert_eq!("\"1970-01-01 00:00:00.001 +00:00:00\"", ser); + + let human_readable = "\"2024-05-23 07:41:02.756283766 +00:00:00\""; + let rfc3339 = "\"2002-10-02T15:00:00Z\""; + let rfc3339_offset = "\"2002-10-02T10:00:00-05:00\""; + + let de = serde_json::from_str::(human_readable).unwrap(); + assert_eq!(de.0.unix_timestamp(), 1716450062); + + let de = serde_json::from_str::(rfc3339).unwrap(); + assert_eq!(de.0.unix_timestamp(), 1033570800); + + let de = serde_json::from_str::(rfc3339_offset).unwrap(); + assert_eq!(de.0.unix_timestamp(), 1033570800); + + let de = serde_json::from_str::("\"nonsense\"").unwrap(); + assert_eq!(de.0.unix_timestamp(), 0); + } +} diff --git a/nym-api/nym-api-requests/src/signable.rs b/nym-api/nym-api-requests/src/signable.rs new file mode 100644 index 0000000000..5b1bb90584 --- /dev/null +++ b/nym-api/nym-api-requests/src/signable.rs @@ -0,0 +1,75 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_crypto::asymmetric::ed25519; +use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_signature; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +// the trait is not public as it's only defined on types that are guaranteed to not panic when serialised +pub trait SignableMessageBody: Serialize + sealed::Sealed { + fn sign(self, key: &ed25519::PrivateKey) -> SignedMessage + where + Self: Sized, + { + let signature = key.sign(self.plaintext()); + SignedMessage { + body: self, + signature, + } + } + + fn plaintext(&self) -> Vec { + #[allow(clippy::unwrap_used)] + // SAFETY: all types that implement this trait have valid serialisations + serde_json::to_vec(&self).unwrap() + } +} + +impl SignableMessageBody for T where T: Serialize + sealed::Sealed {} + +#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct SignedMessage { + pub body: T, + #[schema(value_type = String)] + #[serde(with = "bs58_ed25519_signature")] + pub signature: ed25519::Signature, +} + +impl SignedMessage { + pub fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool + where + T: SignableMessageBody, + { + let plaintext = self.body.plaintext(); + if plaintext.is_empty() { + return false; + } + + pub_key.verify(&plaintext, &self.signature).is_ok() + } +} + +// make sure only our types can implement this trait (to ensure infallible serialisation) +pub(crate) mod sealed { + use crate::ecash::models::*; + use crate::models::{ + ChainBlocksStatusResponseBody, DetailedSignersStatusResponseBody, SignersStatusResponseBody, + }; + + pub trait Sealed {} + + // requests + impl Sealed for IssuedTicketbooksChallengeCommitmentRequestBody {} + impl Sealed for IssuedTicketbooksDataRequestBody {} + + // responses + impl Sealed for IssuedTicketbooksChallengeCommitmentResponseBody {} + impl Sealed for IssuedTicketbooksForResponseBody {} + impl Sealed for IssuedTicketbooksDataResponseBody {} + impl Sealed for EcashSignerStatusResponseBody {} + impl Sealed for ChainBlocksStatusResponseBody {} + impl Sealed for SignersStatusResponseBody {} + impl Sealed for DetailedSignersStatusResponseBody {} +} diff --git a/nym-api/src/ecash/api_routes/handlers.rs b/nym-api/src/ecash/api_routes/handlers.rs index 9114ebba9f..5b7edfefbe 100644 --- a/nym-api/src/ecash/api_routes/handlers.rs +++ b/nym-api/src/ecash/api_routes/handlers.rs @@ -4,8 +4,10 @@ use crate::ecash::api_routes::aggregation::aggregation_routes; use crate::ecash::api_routes::issued::issued_routes; use crate::ecash::api_routes::partial_signing::partial_signing_routes; +use crate::ecash::api_routes::signer_status::signer_status; use crate::ecash::api_routes::spending::spending_routes; use crate::support::http::state::AppState; +use axum::routing::get; use axum::Router; pub(crate) fn ecash_routes() -> Router { @@ -14,4 +16,5 @@ pub(crate) fn ecash_routes() -> Router { .merge(issued_routes()) .merge(partial_signing_routes()) .merge(spending_routes()) + .route("/signer-status", get(signer_status)) } diff --git a/nym-api/src/ecash/api_routes/issued.rs b/nym-api/src/ecash/api_routes/issued.rs index fd63ccdfe1..914b087b97 100644 --- a/nym-api/src/ecash/api_routes/issued.rs +++ b/nym-api/src/ecash/api_routes/issued.rs @@ -11,8 +11,9 @@ use nym_api_requests::ecash::models::{ IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksCountResponse, IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse, IssuedTicketbooksForResponse, - IssuedTicketbooksOnCountResponse, SignableMessageBody, + IssuedTicketbooksOnCountResponse, }; +use nym_api_requests::signable::SignableMessageBody; use nym_http_api_common::{FormattedResponse, OutputParams}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/nym-api/src/ecash/api_routes/mod.rs b/nym-api/src/ecash/api_routes/mod.rs index 18bdf01d4e..2588905654 100644 --- a/nym-api/src/ecash/api_routes/mod.rs +++ b/nym-api/src/ecash/api_routes/mod.rs @@ -6,4 +6,5 @@ pub(crate) mod handlers; mod helpers; pub(crate) mod issued; pub(crate) mod partial_signing; +pub(crate) mod signer_status; pub(crate) mod spending; diff --git a/nym-api/src/ecash/api_routes/signer_status.rs b/nym-api/src/ecash/api_routes/signer_status.rs new file mode 100644 index 0000000000..739482cd35 --- /dev/null +++ b/nym-api/src/ecash/api_routes/signer_status.rs @@ -0,0 +1,45 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::ecash::state::EcashState; +use crate::node_status_api::models::ApiResult; +use axum::extract::{Query, State}; +use nym_api_requests::ecash::models::{EcashSignerStatusResponse, EcashSignerStatusResponseBody}; +use nym_api_requests::signable::SignableMessageBody; +use nym_http_api_common::{FormattedResponse, OutputParams}; +use std::sync::Arc; +use time::OffsetDateTime; + +#[utoipa::path( + tag = "Ecash", + get, + path = "/signer-status", + context_path = "/v1/ecash", + responses( + (status = 200, content( + (EcashSignerStatusResponse = "application/json"), + (EcashSignerStatusResponse = "application/yaml"), + (EcashSignerStatusResponse = "application/bincode") + )), + ), + params(OutputParams) +)] +pub(crate) async fn signer_status( + Query(params): Query, + State(state): State>, +) -> ApiResult> { + let output = params.get_output(); + + let dkg_ecash_epoch_id = state.current_dkg_epoch().await?; + + Ok(output.to_response( + EcashSignerStatusResponseBody { + current_time: OffsetDateTime::now_utc(), + dkg_ecash_epoch_id, + signer_disabled: state.local.explicitly_disabled, + is_ecash_signer: state.is_dkg_signer(dkg_ecash_epoch_id).await?, + has_signing_keys: state.ecash_signing_key().await.is_ok(), + } + .sign(state.local.identity_keypair.private_key()), + )) +} diff --git a/nym-api/src/ecash/state/mod.rs b/nym-api/src/ecash/state/mod.rs index d9ac0e5dce..644a4b32fa 100644 --- a/nym-api/src/ecash/state/mod.rs +++ b/nym-api/src/ecash/state/mod.rs @@ -50,7 +50,6 @@ use nym_validator_client::nyxd::AccountId; use nym_validator_client::EcashApiClient; use rand::{thread_rng, RngCore}; use std::collections::HashMap; -use std::ops::Deref; use time::{Date, OffsetDateTime}; use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use tokio::task::JoinHandle; @@ -162,14 +161,11 @@ impl EcashState { } } - /// Ensures that this nym-api is one of ecash signers for the current epoch - pub(crate) async fn ensure_signer(&self) -> Result<()> { - if self.local.explicitly_disabled { - return Err(EcashError::NotASigner); - } - - let epoch_id = self.aux.current_epoch().await?; + pub(crate) async fn current_dkg_epoch(&self) -> Result { + self.aux.current_epoch().await + } + pub(crate) async fn is_dkg_signer(&self, epoch_id: EpochId) -> Result { let is_epoch_signer = self .local .active_signer @@ -183,8 +179,19 @@ impl EcashState { Ok(ecash_signers.iter().any(|c| c.cosmos_address == address)) }) .await?; + Ok(*is_epoch_signer) + } - if !is_epoch_signer.deref() { + /// Ensures that this nym-api is one of ecash signers for the current epoch + pub(crate) async fn ensure_signer(&self) -> Result<()> { + if self.local.explicitly_disabled { + return Err(EcashError::NotASigner); + } + + let epoch_id = self.current_dkg_epoch().await?; + let is_epoch_signer = self.is_dkg_signer(epoch_id).await?; + + if !is_epoch_signer { return Err(EcashError::NotASigner); } diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index faa142c2ea..2152ae7274 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -32,9 +32,10 @@ use cw3::{Proposal, ProposalResponse, Vote, VoteInfo, VoteResponse, Votes}; use cw4::{Cw4Contract, MemberResponse}; use nym_api_requests::ecash::models::{ IssuedTicketbooksChallengeCommitmentRequestBody, IssuedTicketbooksChallengeCommitmentResponse, - IssuedTicketbooksForResponse, SignableMessageBody, + IssuedTicketbooksForResponse, }; use nym_api_requests::ecash::{BlindSignRequestBody, BlindedSignatureResponse}; +use nym_api_requests::signable::SignableMessageBody; use nym_coconut_dkg_common::dealer::{ DealerDetails, DealerDetailsResponse, DealerType, RegisteredDealerDetails, }; @@ -1277,6 +1278,7 @@ impl TestFixture { AppState { nyxd_client, chain_status_cache: ChainStatusCache::new(Duration::from_secs(42)), + ecash_signers_cache: Default::default(), address_info_cache: AddressInfoCache::new(Duration::from_secs(42), 1000), forced_refresh: ForcedRefresh::new(true), mixnet_contract_cache: MixnetContractCache::new(), diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index c07a6c2e8f..a36027bd3c 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -8,7 +8,6 @@ use ::nym_config::defaults::setup_env; use clap::Parser; use mixnet_contract_cache::cache::MixnetContractCache; use node_status_api::NodeStatusCache; -use nym_bin_common::logging::setup_tracing_logger; use support::nyxd; use tracing::{info, trace}; @@ -23,6 +22,7 @@ pub(crate) mod node_describe_cache; mod node_performance; pub(crate) mod node_status_api; pub(crate) mod nym_nodes; +mod signers_cache; mod status; pub(crate) mod support; mod unstable_routes; @@ -32,10 +32,10 @@ async fn main() -> Result<(), anyhow::Error> { cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { // instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time console_subscriber::init(); + } else { + nym_bin_common::logging::setup_tracing_logger(); }} - setup_tracing_logger(); - info!("Starting nym api..."); let args = cli::Cli::parse(); diff --git a/nym-api/src/network/handlers.rs b/nym-api/src/network/handlers.rs index 4d6c8e01f7..ef9396beff 100644 --- a/nym-api/src/network/handlers.rs +++ b/nym-api/src/network/handlers.rs @@ -3,13 +3,19 @@ use crate::network::models::{ContractInformation, NetworkDetails}; use crate::node_status_api::models::AxumResult; +use crate::signers_cache::handlers::signers_routes; +use crate::support::config::CHAIN_STALL_THRESHOLD; use crate::support::http::state::AppState; use axum::extract::{Query, State}; use axum::Router; -use nym_api_requests::models::ChainStatusResponse; +use nym_api_requests::models::{ + ChainBlocksStatusResponse, ChainBlocksStatusResponseBody, ChainStatus, ChainStatusResponse, +}; +use nym_api_requests::signable::SignableMessageBody; use nym_contracts_common::ContractBuildInformation; use nym_http_api_common::{FormattedResponse, OutputParams}; use std::collections::HashMap; +use time::OffsetDateTime; use tower_http::compression::CompressionLayer; use utoipa::ToSchema; @@ -17,18 +23,24 @@ pub(crate) fn nym_network_routes() -> Router { Router::new() .route("/details", axum::routing::get(network_details)) .route("/chain-status", axum::routing::get(chain_status)) + .route( + "/chain-blocks-status", + axum::routing::get(chain_blocks_status), + ) .route("/nym-contracts", axum::routing::get(nym_contracts)) .route( "/nym-contracts-detailed", axum::routing::get(nym_contracts_detailed), ) + .nest("/signers", signers_routes()) .layer(CompressionLayer::new()) } #[utoipa::path( tag = "network", get, - path = "/v1/network/details", + context_path = "/v1/network", + path = "/details", responses( (status = 200, content( (NetworkDetails = "application/json"), @@ -50,7 +62,8 @@ async fn network_details( #[utoipa::path( tag = "network", get, - path = "/v1/network/chain-status", + context_path = "/v1/network", + path = "/chain-status", responses( (status = 200, content( (ChainStatusResponse = "application/json"), @@ -79,6 +92,47 @@ async fn chain_status( })) } +#[utoipa::path( + tag = "network", + get, + context_path = "/v1/network", + path = "/chain-blocks-status", + responses( + (status = 200, content( + (ChainBlocksStatusResponse = "application/json"), + (ChainBlocksStatusResponse = "application/yaml"), + (ChainBlocksStatusResponse = "application/bincode") + )) + ), + params(OutputParams) +)] +async fn chain_blocks_status( + Query(params): Query, + State(state): State, +) -> FormattedResponse { + let output = params.get_output(); + + let current_time = OffsetDateTime::now_utc(); + let latest_cached_block = state + .chain_status_cache + .get_or_refresh(&state.nyxd_client) + .await + .ok(); + let chain_status = latest_cached_block + .as_ref() + .map(|detailed| detailed.stall_status(current_time, CHAIN_STALL_THRESHOLD)) + .unwrap_or(ChainStatus::Unknown); + + output.to_response( + ChainBlocksStatusResponseBody { + current_time, + latest_cached_block, + chain_status, + } + .sign(state.private_signing_key()), + ) +} + // it's used for schema generation so dead_code is fine #[allow(dead_code)] #[derive(ToSchema)] @@ -90,7 +144,7 @@ pub(crate) struct ContractVersionSchemaResponse { /// version is any string that this implementation knows. It may be simple counter "1", "2". /// or semantic version on release tags "v0.7.0", or some custom feature flag list. /// the only code that needs to understand the version parsing is code that knows how to - /// migrate from the given contract (and is tied to it's implementation somehow) + /// migrate from the given contract (and is tied to its implementation somehow) pub version: String, } @@ -104,7 +158,8 @@ pub struct ContractInformationContractVersion { #[utoipa::path( tag = "network", get, - path = "/v1/network/nym-contracts", + context_path = "/v1/network", + path = "/nym-contracts", responses( (status = 200, content( (HashMap = "application/json"), @@ -151,7 +206,8 @@ pub struct ContractInformationBuildInformation { #[utoipa::path( tag = "network", get, - path = "/v1/network/nym-contracts-detailed", + context_path = "/v1/network", + path = "/nym-contracts-detailed", responses( (status = 200, content( (HashMap = "application/json"), diff --git a/nym-api/src/signers_cache/cache/data.rs b/nym-api/src/signers_cache/cache/data.rs new file mode 100644 index 0000000000..5111033b0b --- /dev/null +++ b/nym-api/src/signers_cache/cache/data.rs @@ -0,0 +1,2 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only diff --git a/nym-api/src/signers_cache/cache/mod.rs b/nym-api/src/signers_cache/cache/mod.rs new file mode 100644 index 0000000000..a44ab4da1b --- /dev/null +++ b/nym-api/src/signers_cache/cache/mod.rs @@ -0,0 +1,11 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_ecash_signer_check::SignersTestResult; + +pub(crate) mod data; +pub(crate) mod refresher; + +pub(crate) struct SignersCacheData { + pub(crate) signers_results: SignersTestResult, +} diff --git a/nym-api/src/signers_cache/cache/refresher.rs b/nym-api/src/signers_cache/cache/refresher.rs new file mode 100644 index 0000000000..aa0a341a6f --- /dev/null +++ b/nym-api/src/signers_cache/cache/refresher.rs @@ -0,0 +1,33 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::signers_cache::cache::SignersCacheData; +use crate::support::caching::refresher::CacheItemProvider; +use crate::support::nyxd::Client; +use async_trait::async_trait; +use nym_ecash_signer_check::{check_signers_with_client, SignerCheckError}; + +pub(crate) struct SignersCacheDataProvider { + nyxd_client: Client, +} + +#[async_trait] +impl CacheItemProvider for SignersCacheDataProvider { + type Item = SignersCacheData; + type Error = SignerCheckError; + + async fn try_refresh(&mut self) -> Result, Self::Error> { + self.refresh().await.map(Some) + } +} + +impl SignersCacheDataProvider { + pub(crate) fn new(nyxd_client: Client) -> Self { + SignersCacheDataProvider { nyxd_client } + } + + async fn refresh(&self) -> Result { + let signers_results = check_signers_with_client(&self.nyxd_client).await?; + Ok(SignersCacheData { signers_results }) + } +} diff --git a/nym-api/src/signers_cache/handlers.rs b/nym-api/src/signers_cache/handlers.rs new file mode 100644 index 0000000000..23046caad1 --- /dev/null +++ b/nym-api/src/signers_cache/handlers.rs @@ -0,0 +1,95 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::node_status_api::models::ApiResult; +use crate::support::http::state::AppState; +use axum::extract::{Query, State}; +use axum::routing::get; +use axum::Router; +use nym_api_requests::models::{ + DetailedSignersStatusResponse, DetailedSignersStatusResponseBody, SignersStatusOverview, + SignersStatusResponse, SignersStatusResponseBody, +}; +use nym_api_requests::signable::SignableMessageBody; +use nym_http_api_common::{FormattedResponse, OutputParams}; + +pub(crate) fn signers_routes() -> Router { + Router::new() + .route("/status", get(signers_status)) + .route("/status-detailed", get(signers_status_detailed)) +} + +#[utoipa::path( + tag = "network", + get, + context_path = "/v1/network/signers", + path = "/status", + responses( + (status = 200, content( + (SignersStatusResponse = "application/json"), + (SignersStatusResponse = "application/yaml"), + (SignersStatusResponse = "application/bincode") + )) + ), + params(OutputParams) +)] +pub(crate) async fn signers_status( + Query(params): Query, + State(state): State, +) -> ApiResult> { + let output = params.get_output(); + + let cached = state.ecash_signers_cache.get().await?; + let as_at = cached.timestamp(); + Ok(output.to_response( + SignersStatusResponseBody { + as_at, + overview: SignersStatusOverview::new( + &cached.signers_results.results, + cached.signers_results.threshold, + ), + results: cached + .signers_results + .results + .iter() + .map(Into::into) + .collect(), + } + .sign(state.private_signing_key()), + )) +} + +#[utoipa::path( + tag = "network", + get, + context_path = "/v1/network/signers", + path = "/status-detailed", + responses( + (status = 200, content( + (DetailedSignersStatusResponse = "application/json"), + (DetailedSignersStatusResponse = "application/yaml"), + (DetailedSignersStatusResponse = "application/bincode") + )) + ), + params(OutputParams) +)] +pub(crate) async fn signers_status_detailed( + Query(params): Query, + State(state): State, +) -> ApiResult> { + let output = params.get_output(); + + let cached = state.ecash_signers_cache.get().await?; + let as_at = cached.timestamp(); + Ok(output.to_response( + DetailedSignersStatusResponseBody { + as_at, + overview: SignersStatusOverview::new( + &cached.signers_results.results, + cached.signers_results.threshold, + ), + details: cached.signers_results.results.clone(), + } + .sign(state.private_signing_key()), + )) +} diff --git a/nym-api/src/signers_cache/mod.rs b/nym-api/src/signers_cache/mod.rs new file mode 100644 index 0000000000..f79e1a1f1d --- /dev/null +++ b/nym-api/src/signers_cache/mod.rs @@ -0,0 +1,30 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::signers_cache::cache::refresher::SignersCacheDataProvider; +use crate::signers_cache::cache::SignersCacheData; +use crate::support::caching::cache::SharedCache; +use crate::support::caching::refresher::CacheRefresher; +use crate::support::{config, nyxd}; +use nym_task::TaskManager; + +pub(crate) mod cache; +pub(crate) mod handlers; + +pub(crate) fn start_refresher( + config: &config::SignersCache, + nyxd_client: nyxd::Client, + task_manager: &TaskManager, +) -> SharedCache { + let refresher = CacheRefresher::new( + SignersCacheDataProvider::new(nyxd_client), + config.debug.refresh_interval, + ) + .named("signers-cache-refresher"); + let shared_cache = refresher.get_shared_cache(); + refresher.start_with_delay( + task_manager.subscribe_named("signers-cache-refresher"), + config.debug.refresher_start_delay, + ); + shared_cache +} diff --git a/nym-api/src/status/handlers.rs b/nym-api/src/status/handlers.rs index 6745a7934e..aa1d5101b2 100644 --- a/nym-api/src/status/handlers.rs +++ b/nym-api/src/status/handlers.rs @@ -3,6 +3,7 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult}; use crate::status::ApiStatusState; +use crate::support::config::CHAIN_STALL_THRESHOLD; use crate::support::http::state::AppState; use axum::extract::{Query, State}; use axum::Router; @@ -12,7 +13,6 @@ use nym_api_requests::models::{ use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_compact_ecash::Base58; use nym_http_api_common::{FormattedResponse, OutputParams}; -use std::time::Duration; use time::OffsetDateTime; pub(crate) fn api_status_routes() -> Router { @@ -42,8 +42,6 @@ async fn health( Query(output): Query, State(state): State, ) -> FormattedResponse { - const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60); - let output = output.output.unwrap_or_default(); let uptime = state.api_status.startup_time.elapsed(); @@ -54,15 +52,7 @@ async fn health( { Ok(res) => { let now = OffsetDateTime::now_utc(); - let block_time: OffsetDateTime = res.latest_block.block.header.time.into(); - let diff = now - block_time; - if diff > CHAIN_STALL_THRESHOLD { - ChainStatus::Stalled { - approximate_amount: diff.unsigned_abs(), - } - } else { - ChainStatus::Synced - } + res.stall_status(now, CHAIN_STALL_THRESHOLD) } Err(_) => ChainStatus::Unknown, }; diff --git a/nym-api/src/support/caching/refresher.rs b/nym-api/src/support/caching/refresher.rs index b26a918f73..cfb412e449 100644 --- a/nym-api/src/support/caching/refresher.rs +++ b/nym-api/src/support/caching/refresher.rs @@ -60,6 +60,11 @@ pub(crate) trait CacheItemProvider { async fn try_refresh(&mut self) -> Result, Self::Error>; } +// Generics explanation: +// T: the actual type held in the cache +// E: Error type associated with refresh failure +// S: data type retrieved during update operation. it must be convertible into T +// (so that initial state could be established or when no `custom_fn` is set) impl CacheRefresher where E: std::error::Error, @@ -107,6 +112,8 @@ where } } + /// Rather than performing default behaviour of overwriting all existing values in the cache, + /// provide a custom update function that will define the update behaviour. #[must_use] pub(crate) fn with_update_fn( mut self, @@ -259,6 +266,26 @@ where tokio::spawn(async move { self.run(task_client).await }); } + pub fn start_with_delay(mut self, mut task_client: TaskClient, delay: Duration) + where + T: Send + Sync + 'static, + E: Send + Sync + 'static, + S: Send + Sync + 'static, + { + tokio::spawn(async move { + let sleep = tokio::time::sleep(delay); + tokio::select! { + biased; + _ = task_client.recv() => { + trace!("{}: Received shutdown", self.name); + return + } + _ = sleep => {}, + } + self.run(task_client).await + }); + } + pub fn start_with_watcher(self, task_client: TaskClient) -> CacheUpdateWatcher where T: Send + Sync + 'static, diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index bed8c6efaf..eb8f609ee4 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -34,7 +34,7 @@ use crate::support::storage::NymApiStorage; use crate::unstable_routes::v1::account::cache::AddressInfoCache; use crate::{ ecash, epoch_operations, mixnet_contract_cache, network_monitor, node_describe_cache, - node_performance, node_status_api, + node_performance, node_status_api, signers_cache, }; use anyhow::{bail, Context}; use nym_config::defaults::NymNetworkDetails; @@ -202,10 +202,18 @@ async fn start_nym_api_tasks(config: &Config) -> anyhow::Result None }; + // check if signers cache is enabled, and if so, start the refresher + let ecash_signers_cache = if config.signers_cache.enabled { + signers_cache::start_refresher(&config.signers_cache, nyxd_client.clone(), &task_manager) + } else { + SharedCache::new() + }; + ecash_state.spawn_background_cleaner(); let router = router.with_state(AppState { nyxd_client: nyxd_client.clone(), chain_status_cache: ChainStatusCache::new(DEFAULT_CHAIN_STATUS_CACHE_TTL), + ecash_signers_cache, address_info_cache: AddressInfoCache::new( config.address_cache.time_to_live, config.address_cache.capacity, diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 8be4ee0bc0..612de6d815 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -63,12 +63,17 @@ pub(crate) const DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL: Duration = Duration::from pub(crate) const DEFAULT_NODE_DESCRIBE_BATCH_SIZE: usize = 50; // TODO: make it configurable -pub(crate) const DEFAULT_CHAIN_STATUS_CACHE_TTL: Duration = Duration::from_secs(60); +pub(crate) const DEFAULT_CHAIN_STATUS_CACHE_TTL: Duration = Duration::from_secs(30); +pub(crate) const CHAIN_STALL_THRESHOLD: Duration = Duration::from_secs(5 * 60); // contract info is changed very infrequently (essentially once per release cycle) // so this default is more than enough pub(crate) const DEFAULT_CONTRACT_DETAILS_CACHE_TTL: Duration = Duration::from_secs(60 * 60); +pub(crate) const DEFAULT_NODE_SIGNERS_CACHE_REFRESH_INTERVAL: Duration = Duration::from_secs(600); +pub(crate) const DEFAULT_NODE_SIGNERS_CACHE_REFRESHER_START_DELAY: Duration = + Duration::from_secs(30); + const DEFAULT_MONITOR_THRESHOLD: u8 = 60; const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50; const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20; @@ -126,6 +131,9 @@ pub struct Config { pub rewarding: Rewarding, + #[serde(default)] + pub signers_cache: SignersCache, + #[serde(alias = "coconut_signer")] pub ecash_signer: EcashSigner, @@ -151,6 +159,7 @@ impl Config { describe_cache: Default::default(), contracts_info_cache: Default::default(), rewarding: Default::default(), + signers_cache: Default::default(), ecash_signer: EcashSigner::new_default(id.as_ref()), address_cache: Default::default(), } @@ -413,6 +422,44 @@ impl Default for PerformanceProviderDebug { } } +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct SignersCache { + pub enabled: bool, + + pub debug: SignersCacheDebug, +} + +impl Default for SignersCache { + fn default() -> Self { + SignersCache { + enabled: true, + debug: Default::default(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct SignersCacheDebug { + // TODO: make it into a decaying function so that if multiple signers are down, + // the refresh interval would decrease + #[serde(with = "humantime_serde")] + pub refresh_interval: Duration, + + // give it some time so that the actual api on THIS singer could start + // and it wouldn't self-report itself as being down + #[serde(with = "humantime_serde")] + pub refresher_start_delay: Duration, +} + +impl Default for SignersCacheDebug { + fn default() -> Self { + SignersCacheDebug { + refresh_interval: DEFAULT_NODE_SIGNERS_CACHE_REFRESH_INTERVAL, + refresher_start_delay: DEFAULT_NODE_SIGNERS_CACHE_REFRESHER_START_DELAY, + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct AddressCacheConfig { pub time_to_live: Duration, diff --git a/nym-api/src/support/http/state/mod.rs b/nym-api/src/support/http/state/mod.rs index ec0d2ca9bb..7a9bb8abb9 100644 --- a/nym-api/src/support/http/state/mod.rs +++ b/nym-api/src/support/http/state/mod.rs @@ -8,6 +8,7 @@ use crate::node_describe_cache::cache::DescribedNodes; use crate::node_status_api::handlers::unstable; use crate::node_status_api::models::AxumErrorResponse; use crate::node_status_api::NodeStatusCache; +use crate::signers_cache::cache::SignersCacheData; use crate::status::ApiStatusState; use crate::support::caching::cache::SharedCache; use crate::support::caching::Cache; @@ -20,6 +21,7 @@ use crate::unstable_routes::v1::account::cache::AddressInfoCache; use crate::unstable_routes::v1::account::models::NyxAccountDetails; use axum::extract::FromRef; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation}; +use nym_crypto::asymmetric::ed25519; use nym_mixnet_contract_common::NodeId; use nym_topology::CachedEpochRewardedSet; use std::collections::HashMap; @@ -41,6 +43,10 @@ pub(crate) struct AppState { /// Note, it is not updated on every request. It follows the embedded ttl. pub(crate) chain_status_cache: ChainStatusCache, + /// Holds cached state of the statuses of all [ecash] signers on the network - + /// their perceived chain statuses and signing capabilities. + pub(crate) ecash_signers_cache: SharedCache, + /// Holds mapping between a nyx address and tokens/delegations it holds pub(crate) address_info_cache: AddressInfoCache, @@ -100,7 +106,19 @@ impl FromRef for MixnetContractCache { } } +impl FromRef for SharedCache { + fn from_ref(app_state: &AppState) -> Self { + app_state.ecash_signers_cache.clone() + } +} + impl AppState { + pub(crate) fn private_signing_key(&self) -> &ed25519::PrivateKey { + // even though we have to go through ecash state, the key is always available + // (moving it would involve some refactoring that's not worth it now) + self.ecash_state.local.identity_keypair.private_key() + } + pub(crate) fn nym_contract_cache(&self) -> &MixnetContractCache { &self.mixnet_contract_cache } diff --git a/nym-api/tests/public-api/nym_nodes.rs b/nym-api/tests/public-api/nym_nodes.rs index dda646b3b8..425a115620 100644 --- a/nym-api/tests/public-api/nym_nodes.rs +++ b/nym-api/tests/public-api/nym_nodes.rs @@ -1,5 +1,5 @@ use crate::utils::{base_url, get_any_node_id, make_request, test_client, validate_json_response}; -use chrono::Utc; +use time::OffsetDateTime; #[tokio::test] async fn test_get_bonded_nodes() -> Result<(), String> { @@ -83,7 +83,7 @@ async fn test_get_annotation_for_node() -> Result<(), String> { #[tokio::test] async fn test_get_historical_performance() -> Result<(), String> { let id = get_any_node_id().await?; - let date = Utc::now().date_naive().to_string(); + let date = OffsetDateTime::now_utc().date().to_string(); let url = format!("{}/v1/nym-nodes/historical-performance/{}", base_url()?, id); let res = test_client() .get(&url) diff --git a/nym-node-status-api/README.md b/nym-node-status-api/README.md index 03da6ca3cb..1ef6706c1d 100644 --- a/nym-node-status-api/README.md +++ b/nym-node-status-api/README.md @@ -4,3 +4,61 @@ The Node Status API serves information about individual `nym-nodes` in the Mixne We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible. You can find build and operation instructions in the [docs](https://nym.com/docs/apis/ns-api). + +## Database Support + +The Node Status API supports both SQLite and PostgreSQL databases through Cargo feature flags: + +- **SQLite** (default): Lightweight, file-based database suitable for development and small deployments +- **PostgreSQL**: Full-featured database recommended for production deployments + +### Building with Different Database Backends + +```bash +# Build with SQLite (default) +cargo build --features sqlite --no-default-features + +# Build with PostgreSQL +cargo build --features pg --no-default-features +``` + +### Running Tests + +```bash +# Test with SQLite +cargo test --features sqlite --no-default-features + +# Test with PostgreSQL +make test-db # This sets up a test PostgreSQL instance +``` + +### Development Commands + +The project includes a Makefile with helpful commands for both database backends: + +```bash +# Check code compilation +make check-sqlite # Check with SQLite +make check-pg # Check with PostgreSQL + +# Run clippy linter +make clippy-sqlite # Lint with SQLite +make clippy-pg # Lint with PostgreSQL +make clippy # Run both + +# PostgreSQL development +make dev-db # Start a PostgreSQL instance for development +make prepare-pg # Prepare SQLx offline cache for PostgreSQL +``` + +### Implementation Details + +The database abstraction is implemented using a query wrapper that automatically converts SQLite-style `?` placeholders to PostgreSQL-style `$1, $2, ...` placeholders at runtime. This allows writing queries once using SQLite syntax while maintaining compatibility with both databases. + +Key differences handled: +- Placeholder syntax (`?` vs `$1, $2, ...`) +- Type conversions (SQLite uses i64, PostgreSQL uses i32 for many fields) +- SQL dialect differences (e.g., `INSERT OR IGNORE` vs `ON CONFLICT DO NOTHING`) +- RETURNING clause behavior + +For more details on PostgreSQL setup, see [README_PG.md](nym-node-status-api/README_PG.md). diff --git a/nym-node-status-api/nym-node-status-agent/Cargo.toml b/nym-node-status-api/nym-node-status-agent/Cargo.toml index 615b2c484f..f6671e611b 100644 --- a/nym-node-status-api/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-api/nym-node-status-agent/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-agent" -version = "1.0.0" +version = "1.0.4" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -14,13 +14,25 @@ rust-version.workspace = true readme.workspace = true [dependencies] -anyhow = { workspace = true} +anyhow = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } -nym-bin-common = { path = "../../common/bin-common", features = ["models"]} +futures = { workspace = true } +# nym-bin-common = { path = "../../common/bin-common", features = ["models"] } +nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "models", +] } nym-node-status-client = { path = "../nym-node-status-client" } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] } +nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "asymmetric", + "rand", +] } rand = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process", "fs"] } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", + "process", + "fs", +] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/nym-node-status-api/nym-node-status-agent/Dockerfile b/nym-node-status-api/nym-node-status-agent/Dockerfile index fcab377e26..192af6678d 100644 --- a/nym-node-status-api/nym-node-status-agent/Dockerfile +++ b/nym-node-status-api/nym-node-status-agent/Dockerfile @@ -16,7 +16,7 @@ WORKDIR /usr/src/nym-vpn-client/nym-vpn-core RUN cargo build --release --package nym-gateway-probe COPY ./ /usr/src/nym -WORKDIR /usr/src/nym/nym-node-status-agent +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-agent RUN cargo build --release #------------------------------------------------------------------- diff --git a/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh b/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh new file mode 100755 index 0000000000..2cf972a8f3 --- /dev/null +++ b/nym-node-status-api/nym-node-status-agent/build-push-node-status-agent.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Build and push Node Status Agent container to harbor.nymte.ch + +set -e + +# Configuration +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKING_DIRECTORY="${SCRIPT_DIR}" +CONTAINER_NAME="node-status-agent" +REGISTRY="harbor.nymte.ch" +NAMESPACE="nym" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to display usage +usage() { + echo "Usage: $0 " + echo " gateway-probe-git-ref - Git reference (branch/tag/commit) for gateway probe" + echo "" + echo "Example: $0 main" + echo "Example: $0 release/2025.11-cheddar" + echo "Example: $0 v1.2.3" + exit 1 +} + +# Parse arguments +if [ $# -ne 1 ]; then + usage +fi + +GATEWAY_PROBE_GIT_REF="$1" + +# Get version from Cargo.toml +VERSION=$(grep "^version = " "${WORKING_DIRECTORY}/Cargo.toml" | sed -E 's/version = "(.*)"/\1/') +if [ -z "$VERSION" ]; then + echo -e "${RED}Error: Could not extract version from Cargo.toml${NC}" + exit 1 +fi + +# Clean up git ref for use in tag (replace / with -) +GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}" + +echo -e "${YELLOW}Building Node Status Agent${NC}" +echo -e "${YELLOW}Version: ${VERSION}${NC}" +echo -e "${YELLOW}Gateway Probe Git Ref: ${GATEWAY_PROBE_GIT_REF} (slug: ${GIT_REF_SLUG})${NC}" + +# Login to Harbor +echo -e "${GREEN}Logging into Harbor...${NC}" +docker login "${REGISTRY}" + +# Build the container +echo -e "${GREEN}Building container with gateway probe from ${GATEWAY_PROBE_GIT_REF}...${NC}" +# Build from repository root (two levels up from script location) +docker build \ + --build-arg GIT_REF="${GATEWAY_PROBE_GIT_REF}" \ + -f "${WORKING_DIRECTORY}/Dockerfile" \ + "${SCRIPT_DIR}/../.." \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}" \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${GIT_REF_SLUG}" + +# Push to Harbor +echo -e "${GREEN}Pushing container to Harbor...${NC}" +docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}" +docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${GIT_REF_SLUG}" + +echo -e "${GREEN}Successfully built and pushed ${CONTAINER_NAME}:${VERSION}-${GIT_REF_SLUG}${NC}" \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-agent/run.sh b/nym-node-status-api/nym-node-status-agent/run.sh index 544c6ca731..d0f3018fdd 100755 --- a/nym-node-status-api/nym-node-status-agent/run.sh +++ b/nym-node-status-api/nym-node-status-agent/run.sh @@ -21,6 +21,7 @@ export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1" export NODE_STATUS_AGENT_SERVER_PORT="8000" export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe" export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT" +export NODE_STATUS_AGENT_PROBE_MNEMONIC="$MNEMONIC" export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1" workers=${1:-1} diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs index 75601548ed..24283b081d 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/mod.rs @@ -1,17 +1,45 @@ use crate::probe::GwProbe; use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; -use std::sync::OnceLock; +use nym_crypto::asymmetric::ed25519::PrivateKey; +use std::{env, sync::OnceLock}; pub(crate) mod generate_keypair; pub(crate) mod run_probe; +#[derive(Debug)] +pub(crate) struct ServerConfig { + pub(crate) address: String, + pub(crate) port: u16, + pub(crate) auth_key: PrivateKey, +} + // Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } +fn parse_server_config(s: &str) -> Result { + let parts: Vec<&str> = s.split('|').collect(); + if parts.len() != 2 { + return Err("Server config must be in format 'address|port'".to_string()); + } + + let address = parts[0].to_string(); + let port = parts[1] + .parse::() + .map_err(|_| "Invalid port number".to_string())?; + let auth_key = + PrivateKey::from_base58_string(env::var("NODE_STATUS_AGENT_AUTH_KEY").unwrap()).unwrap(); + + Ok(ServerConfig { + address, + port, + auth_key, + }) +} + #[derive(Parser, Debug)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] pub(crate) struct Args { @@ -22,20 +50,19 @@ pub(crate) struct Args { #[derive(Subcommand, Debug)] pub(crate) enum Command { RunProbe { - #[arg(short, long, env = "NODE_STATUS_AGENT_SERVER_ADDRESS")] - server_address: String, - - #[arg(short = 'p', long, env = "NODE_STATUS_AGENT_SERVER_PORT")] - server_port: u16, - - /// base58-encoded private key - #[arg(long, env = "NODE_STATUS_AGENT_AUTH_KEY")] - ns_api_auth_key: String, + /// Server configurations in format "address:port:auth_key" + /// Can be specified multiple times for multiple servers + #[arg(short, long, required = true)] + server: Vec, /// path of binary to run #[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")] probe_path: String, + /// mnemonic for acquiring zk-nyms + #[arg(long, env = "NYM_NODE_MNEMONICS")] + mnemonic: String, + #[arg( long, env = "NODE_STATUS_AGENT_PROBE_EXTRA_ARGS", @@ -54,22 +81,29 @@ impl Args { pub(crate) async fn execute(&self) -> anyhow::Result<()> { match &self.command { Command::RunProbe { - server_address, - server_port, - ns_api_auth_key, + server, probe_path, + mnemonic, probe_extra_args, - } => run_probe::run_probe( - server_address, - server_port.to_owned(), - ns_api_auth_key, - probe_path, - probe_extra_args, - ) - .await - .inspect_err(|err| { - tracing::error!("{err}"); - })?, + } => { + // Parse server configs + let mut servers = Vec::new(); + for s in server { + match parse_server_config(s) { + Ok(config) => servers.push(config), + Err(e) => { + tracing::error!("Invalid server config '{}': {}", s, e); + anyhow::bail!("Invalid server config '{}': {}", s, e); + } + } + } + + run_probe::run_probe(&servers, probe_path, mnemonic, probe_extra_args) + .await + .inspect_err(|err| { + tracing::error!("{err}"); + })? + } Command::GenerateKeypair { path } => { let path = path .to_owned() diff --git a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs index 8cf779cfbc..07fa3b514f 100644 --- a/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/cli/run_probe.rs @@ -1,33 +1,143 @@ -use crate::cli::GwProbe; -use anyhow::Context; -use nym_crypto::asymmetric::ed25519::PrivateKey; +use crate::cli::{GwProbe, ServerConfig}; pub(crate) async fn run_probe( - server_ip: &str, - server_port: u16, - ns_api_auth_key: &str, + servers: &[ServerConfig], probe_path: &str, + mnemonic: &str, probe_extra_args: &Vec, ) -> anyhow::Result<()> { - let auth_key = PrivateKey::from_base58_string(ns_api_auth_key) - .context("Couldn't parse auth key, exiting")?; - - let ns_api_client = nym_node_status_client::NsApiClient::new(server_ip, server_port, auth_key); + if servers.is_empty() { + anyhow::bail!("No servers configured"); + } let probe = GwProbe::new(probe_path.to_string()); let version = probe.version().await; tracing::info!("Probe version:\n{}", version); - if let Some(testrun) = ns_api_client.request_testrun().await? { - let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key), probe_extra_args); + // Always use first server as primary + let primary_server = &servers[0]; + tracing::info!( + "Requesting testrun from primary server: {}:{}", + primary_server.address, + primary_server.port + ); - ns_api_client - .submit_results(testrun.testrun_id, log, testrun.assigned_at_utc) - .await?; - } else { - tracing::info!("No testruns available, exiting") + let auth_key = nym_crypto::asymmetric::ed25519::PrivateKey::from_bytes( + &primary_server.auth_key.to_bytes(), + ) + .expect("Failed to clone auth key"); + let ns_api_client = nym_node_status_client::NsApiClient::new( + &primary_server.address, + primary_server.port, + auth_key, + ); + + match ns_api_client.request_testrun().await { + Ok(Some(testrun)) => { + tracing::info!( + "Received testrun {} for gateway {} from primary", + testrun.testrun_id, + testrun.gateway_identity_key + ); + + // Run the probe + let log = probe.run_and_get_log( + &Some(testrun.gateway_identity_key.clone()), + mnemonic, + probe_extra_args, + ); + + // Submit to ALL servers in parallel + let handles = servers + .iter() + .enumerate() + .map(|(idx, server)| { + let testrun = testrun.clone(); + let log = log.clone(); + + async move { + let auth_key = nym_crypto::asymmetric::ed25519::PrivateKey::from_bytes( + &server.auth_key.to_bytes(), + ) + .expect("Failed to clone auth key"); + let client = nym_node_status_client::NsApiClient::new( + &server.address, + server.port, + auth_key, + ); + + let result = if idx == 0 { + // Primary server: submit regular results without context + client + .submit_results( + testrun.testrun_id as i64, + log, + testrun.assigned_at_utc, + ) + .await + } else { + // Other servers: submit results with context + client + .submit_results_with_context( + testrun.testrun_id, + log, + testrun.assigned_at_utc, + testrun.gateway_identity_key, + ) + .await + }; + + (idx, server.address.clone(), server.port, result) + } + }) + .collect::>(); + + let results = futures::future::join_all(handles).await; + + for result in results { + match result.3 { + Ok(()) => { + let method = if result.0 == 0 { + "regular" + } else { + "with context" + }; + tracing::info!( + "✅ Successfully submitted {} to server[{}] {}:{}", + method, + result.0, + result.1, + result.2 + ); + } + Err(e) => { + let method = if result.0 == 0 { + "regular" + } else { + "with context" + }; + tracing::warn!( + "❌ Failed to submit {} to server[{}] {}:{} - {}", + method, + result.0, + result.1, + result.2, + e + ); + } + } + } + + Ok(()) + } + Ok(None) => { + tracing::info!("No testruns available from primary server"); + Ok(()) + } + Err(e) => { + tracing::error!("Failed to contact primary server: {}", e); + Err(e) + } } - - Ok(()) } diff --git a/nym-node-status-api/nym-node-status-agent/src/probe.rs b/nym-node-status-api/nym-node-status-agent/src/probe.rs index 08d0a823aa..b9fcd5c040 100644 --- a/nym-node-status-api/nym-node-status-agent/src/probe.rs +++ b/nym-node-status-api/nym-node-status-agent/src/probe.rs @@ -73,6 +73,7 @@ impl GwProbe { pub(crate) fn run_and_get_log( &self, gateway_key: &Option, + mnemonic: &str, probe_extra_args: &Vec, ) -> String { let mut command = std::process::Command::new(&self.path); @@ -81,6 +82,7 @@ impl GwProbe { if let Some(gateway_id) = gateway_key { command.arg("--gateway").arg(gateway_id); } + command.arg("--mnemonic").arg(mnemonic); tracing::info!("Extra args for the probe:"); for arg in probe_extra_args { diff --git a/nym-node-status-api/nym-node-status-api/.env.example b/nym-node-status-api/nym-node-status-api/.env.example new file mode 100644 index 0000000000..7625347a7c --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.env.example @@ -0,0 +1,32 @@ +# Example environment variables for nym-node-status-api + +# Database configuration +# For SQLite: +# DATABASE_URL=sqlite://nym-node-status-api.sqlite + +# For PostgreSQL: +# DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# Network configuration +NETWORK_NAME=sandbox +NYM_API=https://sandbox-nym-api1.nymtech.net/api +NYXD=https://rpc.sandbox.nymtech.net + +# API configuration +NYM_NODE_STATUS_API_HTTP_PORT=8000 +NYM_API_CLIENT_TIMEOUT=15 +SQLX_BUSY_TIMEOUT_S=5 + +# Monitoring intervals +NODE_STATUS_API_MONITOR_REFRESH_INTERVAL=300 +NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=300 +NODE_STATUS_API_GEODATA_TTL=86400 + +# Agent keys (comma-separated list) +NODE_STATUS_API_AGENT_KEY_LIST= + +# External service tokens +IPINFO_API_TOKEN=your_token_here + +# MixNodes (Optional) +DATA_PROVIDER_DELEGATION_LIST= \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json deleted file mode 100644 index d59ea9da1a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_daily_stats (\n mix_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = mixnode_daily_stats.packets_received + excluded.packets_received,\n packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent,\n packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "01ee4a30bc3104712e5bc371a45d614a89d88adf02358800433e06100c13c548" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json deleted file mode 100644 index eb5cdd6aaa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT mix_id as node_id, host, http_api_port\n FROM mixnodes\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "host", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "http_api_port", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "021c6c65d1ed806d8430bef7883906b42a7e4b280c8efb32db15d7c6a51d7a27" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json deleted file mode 100644 index 205bfdc79b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_description (\n mix_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (mix_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "06065394c157927e4002ddd5c7c1af626ae15728d615f539470cd7c189312385" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json deleted file mode 100644 index ad57224826..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n gateway_identity_key\n FROM\n gateways\n WHERE\n id = ?", - "describe": { - "columns": [ - { - "name": "gateway_identity_key", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "06b17d1e5f61201a1b7542896ba55c69cd5c1a7e7d87073c94600c783a0a3984" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json deleted file mode 100644 index fda05c400a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n testruns\n SET\n status = ?\n WHERE\n status = ?\n AND\n last_assigned_utc < ?\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "0cf0e4d4f30e90caecffd6255ef85dab12730e538be194438f19ed7f198bd50e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json index 75534f6ea7..b32a31ab5a 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07.json @@ -1,43 +1,43 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake) as \"total_stake!: i64\",\n SUM(packets_received) as \"total_packets_received!: i64\",\n SUM(packets_sent) as \"total_packets_sent!: i64\",\n SUM(packets_dropped) as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ", "describe": { "columns": [ { - "name": "date_utc!", "ordinal": 0, - "type_info": "Text" + "name": "date_utc!", + "type_info": "Varchar" }, { - "name": "total_stake!: i64", "ordinal": 1, - "type_info": "Integer" + "name": "total_stake!: i64", + "type_info": "Numeric" }, { - "name": "total_packets_received!: i64", "ordinal": 2, - "type_info": "Integer" + "name": "total_packets_received!: i64", + "type_info": "Int8" }, { - "name": "total_packets_sent!: i64", "ordinal": 3, - "type_info": "Integer" + "name": "total_packets_sent!: i64", + "type_info": "Int8" }, { - "name": "total_packets_dropped!: i64", "ordinal": 4, - "type_info": "Integer" + "name": "total_packets_dropped!: i64", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false, - false, - true, - true, - true + null, + null, + null, + null, + null ] }, "hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json deleted file mode 100644 index 810e133a8a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n key as \"key!\",\n value_json as \"value_json!\",\n last_updated_utc as \"last_updated_utc!\"\n FROM summary", - "describe": { - "columns": [ - { - "name": "key!", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "value_json!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "last_updated_utc!", - "ordinal": 2, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - true, - true, - false - ] - }, - "hash": "1327b5118f9144dddbcf8edb11f7dc549cf503409fd6dfedcdc02dbcd61d5454" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json deleted file mode 100644 index 52929bc782..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO mixnode_packet_stats_raw (\n mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "21e44766729777756f6eb04bf3b81df3e591008a1e3fd664ed83ca86ac51bd8c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json deleted file mode 100644 index 0e24c697ac..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n id,\n gateway_identity_key\n FROM gateways\n WHERE id = ?\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_identity_key", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false - ] - }, - "hash": "2236299f9f691376db54cbd58ec5ceb89b9925cba46efcf4ed79ef0759a01129" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json deleted file mode 100644 index a6b5055677..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n bond_info IS NOT NULL\n AND\n self_described IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true - ] - }, - "hash": "227539374e7473f6f9642289c5b5d1bcd636315ab23537cb5f6d2f82a2bcb7bf" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json deleted file mode 100644 index fa5ef3a17f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT gateway_identity_key\n FROM gateways\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "gateway_identity_key", - "ordinal": 0, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false - ] - }, - "hash": "25300e435780101fa207c8e26ef2f49ba5db84d63e89440bb494e8327fe73686" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json deleted file mode 100644 index 5413fe16e5..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n AND\n bond_info IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "ed25519_identity_pubkey", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "total_stake", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "ip_addresses!: serde_json::Value", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "mix_port", - "ordinal": 4, - "type_info": "Integer" - }, - { - "name": "x25519_sphinx_pubkey", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "node_role: serde_json::Value", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "supported_roles: serde_json::Value", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "entry: serde_json::Value", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "performance", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 11, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - true - ] - }, - "hash": "283f49a65c7d70bf271702ff6a5c7ad6e68c81932d295ff18ed198c54706a57c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json deleted file mode 100644 index 7aeb02d48b..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT OR IGNORE INTO gateway_session_stats\n (gateway_identity_key, node_id, day,\n unique_active_clients, session_started, users_hashes,\n vpn_sessions, mixnet_sessions, unknown_sessions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 9 - }, - "nullable": [] - }, - "hash": "3243cf5646255a9430d1e6710970505d0dbcc62703f40e090e80ff48c77723c4" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json deleted file mode 100644 index 60ecc30618..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO mixnodes\n (mix_id, identity_key, bonded, total_stake,\n host, http_api_port, full_details,\n self_described, last_updated_utc, is_dp_delegatee)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(mix_id) DO UPDATE SET\n bonded=excluded.bonded,\n total_stake=excluded.total_stake, host=excluded.host,\n http_api_port=excluded.http_api_port,\n full_details=excluded.full_details,self_described=excluded.self_described,\n last_updated_utc=excluded.last_updated_utc,\n is_dp_delegatee = excluded.is_dp_delegatee;", - "describe": { - "columns": [], - "parameters": { - "Right": 10 - }, - "nullable": [] - }, - "hash": "3cd5cb4bfca4243925da4ddbccd811e842090e98982e1032670df77961870b32" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json deleted file mode 100644 index d6b7e9be72..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_identity_key as \"gateway_identity_key!\",\n self_described as \"self_described?\",\n explorer_pretty_bond as \"explorer_pretty_bond?\"\n FROM gateways\n WHERE gateway_identity_key = ?\n AND bonded = true\n ORDER BY gateway_identity_key\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_identity_key!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "self_described?", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "explorer_pretty_bond?", - "ordinal": 3, - "type_info": "Text" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - true, - false, - false, - true - ] - }, - "hash": "3e7e987780937873cdb393b157d7708c9f01047b0689eb0d4f7a973b328c609d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json deleted file mode 100644 index 78a970dc41..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n gw.gateway_identity_key as \"gateway_identity_key!\",\n gw.bonded as \"bonded: bool\",\n gw.performance as \"performance!\",\n gw.self_described as \"self_described?\",\n gw.explorer_pretty_bond as \"explorer_pretty_bond?\",\n gw.last_probe_result as \"last_probe_result?\",\n gw.last_probe_log as \"last_probe_log?\",\n gw.last_testrun_utc as \"last_testrun_utc?\",\n gw.last_updated_utc as \"last_updated_utc!\",\n COALESCE(gd.moniker, \"NA\") as \"moniker!\",\n COALESCE(gd.website, \"NA\") as \"website!\",\n COALESCE(gd.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(gd.details, \"NA\") as \"details!\"\n FROM gateways gw\n LEFT JOIN gateway_description gd\n ON gw.gateway_identity_key = gd.gateway_identity_key\n ORDER BY gw.gateway_identity_key", - "describe": { - "columns": [ - { - "name": "gateway_identity_key!", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "bonded: bool", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "performance!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "self_described?", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "explorer_pretty_bond?", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "last_probe_result?", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_probe_log?", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "last_testrun_utc?", - "ordinal": 7, - "type_info": "Integer" - }, - { - "name": "last_updated_utc!", - "ordinal": 8, - "type_info": "Integer" - }, - { - "name": "moniker!", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "website!", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "security_contact!", - "ordinal": 11, - "type_info": "Text" - }, - { - "name": "details!", - "ordinal": 12, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - true, - true, - true, - true, - false, - false, - false, - false, - false - ] - }, - "hash": "3eb1d8491bda3c1d6e071b6eb364b9a979f4bdb11ea81b2d0f022555bab51ecb" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json deleted file mode 100644 index 9f31fbd172..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n total_stake\n FROM mixnodes\n WHERE mix_id = ?\n ", - "describe": { - "columns": [ - { - "name": "total_stake", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "3fc2baabf194b147b20be2a49401cc0c100a1d7a7c347393adde2410fa6f4dfe" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json deleted file mode 100644 index f9eb3657e7..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE testruns SET status = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json deleted file mode 100644 index 6a9fd9d4eb..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_probe_log = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json deleted file mode 100644 index 08b9e93fff..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES (?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json new file mode 100644 index 0000000000..ee07d52cc2 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO gateway_session_stats\n (gateway_identity_key, node_id, day,\n unique_active_clients, session_started, users_hashes,\n vpn_sessions, mixnet_sessions, unknown_sessions)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int8", + "Date", + "Int8", + "Int8", + "Varchar", + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "4fca38abbb416d9457c34a8ba4faf481a837eda4f3e1bee1d430a4eb102a5b3d" +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json deleted file mode 100644 index 08bc84273c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_node_daily_mixing_stats (\n node_id, date_utc,\n total_stake, packets_received,\n packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id, date_utc) DO UPDATE SET\n total_stake = excluded.total_stake,\n packets_received = nym_node_daily_mixing_stats.packets_received + excluded.packets_received,\n packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent,\n packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "5912ea335a957d217f5e2b3a63a25b31715c2098310fe7a9db688bc2fd36aad4" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json deleted file mode 100644 index 0fd89dbe54..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE testruns\n SET\n status = ?,\n last_assigned_utc = ?\n WHERE rowid =\n (\n SELECT rowid\n FROM testruns\n WHERE status = ?\n ORDER BY created_utc asc\n LIMIT 1\n )\n RETURNING\n id as \"id!\",\n gateway_id\n ", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id", - "ordinal": 1, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 3 - }, - "nullable": [ - false, - false - ] - }, - "hash": "5e9cbb39f5fb53774803270f422989e199aac4d4a71913c7074359b4bd676b02" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json deleted file mode 100644 index 2ba3ec13fa..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO nym_nodes\n (node_id, ed25519_identity_pubkey,\n total_stake,\n ip_addresses, mix_port,\n x25519_sphinx_pubkey, node_role,\n supported_roles, entry,\n self_described,\n bond_info,\n performance, last_updated_utc\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(node_id) DO UPDATE SET\n ed25519_identity_pubkey=excluded.ed25519_identity_pubkey,\n ip_addresses=excluded.ip_addresses,\n mix_port=excluded.mix_port,\n x25519_sphinx_pubkey=excluded.x25519_sphinx_pubkey,\n node_role=excluded.node_role,\n supported_roles=excluded.supported_roles,\n entry=excluded.entry,\n self_described=excluded.self_described,\n bond_info=excluded.bond_info,\n performance=excluded.performance,\n last_updated_utc=excluded.last_updated_utc\n ;", - "describe": { - "columns": [], - "parameters": { - "Right": 13 - }, - "nullable": [] - }, - "hash": "664e059ac2c58e1115fe214376a6b326b31c93298f20019772cce2e277a194f8" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json index dca66fc2ad..5e60c5a545 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b.json @@ -1,19 +1,19 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT count(id) FROM mixnodes", "describe": { "columns": [ { - "name": "count(id)", "ordinal": 0, - "type_info": "Integer" + "name": "count", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false + null ] }, "hash": "670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json deleted file mode 100644 index 6ff736dbbb..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_node_descriptions (\n node_id, moniker, website, security_contact, details, last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (node_id) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "6a9780aad1f2f0c8ef780e51fe856679d5e28f95143f4e2a2b409009dc0f55ba" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json new file mode 100644 index 0000000000..b9690c68ee --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES ($1, $2, $3, $4, $5) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Int4", + "Int4", + "Varchar", + "Int8", + "Varchar" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6de15de62c0caa545910a17877a3ac5ebe44a398b199ab0120207a5569f54d0b" +} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json deleted file mode 100644 index 9d93b219f9..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_probe_result = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json similarity index 61% rename from nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json rename to nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json index 7e222fb211..7fa6221256 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd.json @@ -1,66 +1,66 @@ { - "db_name": "SQLite", - "query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n COALESCE(md.moniker, \"NA\") as \"moniker!\",\n COALESCE(md.website, \"NA\") as \"website!\",\n COALESCE(md.security_contact, \"NA\") as \"security_contact!\",\n COALESCE(md.details, \"NA\") as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id", + "db_name": "PostgreSQL", + "query": "SELECT\n mn.mix_id as \"mix_id!\",\n mn.bonded as \"bonded: bool\",\n mn.is_dp_delegatee as \"is_dp_delegatee: bool\",\n mn.total_stake as \"total_stake!\",\n mn.full_details as \"full_details!\",\n mn.self_described as \"self_described\",\n mn.last_updated_utc as \"last_updated_utc!\",\n md.moniker as \"moniker!\",\n md.website as \"website!\",\n md.security_contact as \"security_contact!\",\n md.details as \"details!\"\n FROM mixnodes mn\n LEFT JOIN mixnode_description md ON mn.mix_id = md.mix_id\n ORDER BY mn.mix_id", "describe": { "columns": [ { - "name": "mix_id!", "ordinal": 0, - "type_info": "Integer" + "name": "mix_id!", + "type_info": "Int8" }, { - "name": "bonded: bool", "ordinal": 1, - "type_info": "Integer" + "name": "bonded: bool", + "type_info": "Bool" }, { - "name": "is_dp_delegatee: bool", "ordinal": 2, - "type_info": "Integer" + "name": "is_dp_delegatee: bool", + "type_info": "Bool" }, { - "name": "total_stake!", "ordinal": 3, - "type_info": "Integer" + "name": "total_stake!", + "type_info": "Int8" }, { - "name": "full_details!", "ordinal": 4, - "type_info": "Text" + "name": "full_details!", + "type_info": "Varchar" }, { - "name": "self_described", "ordinal": 5, - "type_info": "Text" + "name": "self_described", + "type_info": "Varchar" }, { - "name": "last_updated_utc!", "ordinal": 6, - "type_info": "Integer" + "name": "last_updated_utc!", + "type_info": "Int8" }, { - "name": "moniker!", "ordinal": 7, - "type_info": "Text" + "name": "moniker!", + "type_info": "Varchar" }, { - "name": "website!", "ordinal": 8, - "type_info": "Text" + "name": "website!", + "type_info": "Varchar" }, { - "name": "security_contact!", "ordinal": 9, - "type_info": "Text" + "name": "security_contact!", + "type_info": "Varchar" }, { - "name": "details!", "ordinal": 10, - "type_info": "Text" + "name": "details!", + "type_info": "Varchar" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ false, @@ -70,11 +70,11 @@ true, true, false, - false, - false, - false, - false + true, + true, + true, + true ] }, - "hash": "f75af377da33db1455c6e0f612e0fa9583888f343b8b59faf37fc6799b244379" + "hash": "74b76cd0d94c1afc51c21c14c12236a2b964b981c8cbcc282117fe9bc38338dd" } diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json deleted file mode 100644 index e697946d25..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n date as \"date!\",\n timestamp_utc as \"timestamp_utc!\",\n value_json as \"value_json!\"\n FROM summary_history\n ORDER BY date DESC\n LIMIT 30", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "date!", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "timestamp_utc!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "value_json!", - "ordinal": 3, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - true, - false, - false, - true - ] - }, - "hash": "7600823da7ce80b8ffda933608603a2752e28df775d1af8fd943a5fc8d7dc00d" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json deleted file mode 100644 index b68078e512..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n COALESCE(packets_received, 0) as \"packets_received!: _\",\n COALESCE(packets_sent, 0) as \"packets_sent!: _\",\n COALESCE(packets_dropped, 0) as \"packets_dropped!: _\"\n FROM mixnode_packet_stats_raw\n WHERE mix_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ", - "describe": { - "columns": [ - { - "name": "packets_received!: _", - "ordinal": 0, - "type_info": "Null" - }, - { - "name": "packets_sent!: _", - "ordinal": 1, - "type_info": "Null" - }, - { - "name": "packets_dropped!: _", - "ordinal": 2, - "type_info": "Null" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "786b6a5d954e38b204cebf322711c74c8cf1c08e5e2896a1d6d5b85c91991753" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json deleted file mode 100644 index 5252ccf2e7..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO summary_history\n (date, timestamp_utc, value_json)\n VALUES (?, ?, ?)\n ON CONFLICT(date) DO UPDATE SET\n timestamp_utc=excluded.timestamp_utc,\n value_json=excluded.value_json;", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "788515c34588aec352773df4b6e6c5e41f3c0bb56a27648b5e25466b8634a578" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json index 89c1e86878..3d922cb7f0 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5.json @@ -1,19 +1,19 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT count(id) FROM gateways", "describe": { "columns": [ { - "name": "count(id)", "ordinal": 0, - "type_info": "Integer" + "name": "count", + "type_info": "Int8" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ - false + null ] }, "hash": "86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5" diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json deleted file mode 100644 index b3a5353a1f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n COALESCE(packets_received, 0) as \"packets_received!: _\",\n COALESCE(packets_sent, 0) as \"packets_sent!: _\",\n COALESCE(packets_dropped, 0) as \"packets_dropped!: _\"\n FROM nym_nodes_packet_stats_raw\n WHERE node_id = ?\n ORDER BY timestamp_utc DESC\n LIMIT 1 OFFSET 1\n ", - "describe": { - "columns": [ - { - "name": "packets_received!: _", - "ordinal": 0, - "type_info": "Null" - }, - { - "name": "packets_sent!: _", - "ordinal": 1, - "type_info": "Null" - }, - { - "name": "packets_dropped!: _", - "ordinal": 2, - "type_info": "Null" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - null, - null, - null - ] - }, - "hash": "8bdf85a61e443fa5f4835bffd0bffc8ed1011f56714fde6007e50951e569854b" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json index 993afdac99..d87f4c21ca 100644 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json +++ b/nym-node-status-api/nym-node-status-api/.sqlx/query-930a41e612b4e964ae214843da190f6c66c14d4267a2cc2ca73354becc2c8bb8.json @@ -1,21 +1,21 @@ { - "db_name": "SQLite", + "db_name": "PostgreSQL", "query": "SELECT\n gateway_identity_key as \"gateway_identity_key!\",\n bonded as \"bonded: bool\"\n FROM gateways\n ORDER BY last_testrun_utc", "describe": { "columns": [ { - "name": "gateway_identity_key!", "ordinal": 0, - "type_info": "Text" + "name": "gateway_identity_key!", + "type_info": "Varchar" }, { - "name": "bonded: bool", "ordinal": 1, - "type_info": "Integer" + "name": "bonded: bool", + "type_info": "Bool" } ], "parameters": { - "Right": 0 + "Left": [] }, "nullable": [ false, diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json deleted file mode 100644 index 55968f7c3a..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n ed25519_identity_pubkey,\n total_stake,\n ip_addresses as \"ip_addresses!: serde_json::Value\",\n mix_port,\n x25519_sphinx_pubkey,\n node_role as \"node_role: serde_json::Value\",\n supported_roles as \"supported_roles: serde_json::Value\",\n entry as \"entry: serde_json::Value\",\n performance,\n self_described as \"self_described: serde_json::Value\",\n bond_info as \"bond_info: serde_json::Value\"\n FROM\n nym_nodes\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "ed25519_identity_pubkey", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "total_stake", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "ip_addresses!: serde_json::Value", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "mix_port", - "ordinal": 4, - "type_info": "Integer" - }, - { - "name": "x25519_sphinx_pubkey", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "node_role: serde_json::Value", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "supported_roles: serde_json::Value", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "entry: serde_json::Value", - "ordinal": 8, - "type_info": "Text" - }, - { - "name": "performance", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 10, - "type_info": "Text" - }, - { - "name": "bond_info: serde_json::Value", - "ordinal": 11, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - true - ] - }, - "hash": "9334f0c91252fcd7ec72558a271222615bb282e5334665700709ae475a5daea2" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json deleted file mode 100644 index 967a2c648c..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO gateways\n (gateway_identity_key, bonded,\n self_described, explorer_pretty_bond,\n last_updated_utc, performance)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(gateway_identity_key) DO UPDATE SET\n bonded=excluded.bonded,\n self_described=excluded.self_described,\n explorer_pretty_bond=excluded.explorer_pretty_bond,\n last_updated_utc=excluded.last_updated_utc,\n performance = excluded.performance;", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "9796d354ae075eab4cbd3438839c39da94025494395ec7b093aefef696f2d0c5" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json deleted file mode 100644 index 7a370a2753..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n created_utc as \"created_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\",\n last_assigned_utc\n FROM testruns\n WHERE gateway_id = ? AND status != 2\n ORDER BY id DESC\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id!", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "status!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "created_utc!", - "ordinal": 3, - "type_info": "Integer" - }, - { - "name": "ip_address!", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "log!", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_assigned_utc", - "ordinal": 6, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "a1d9eb816acd1a91ed0975c801c9295c01a78861a2a0597ad28b1579a14bf008" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json deleted file mode 100644 index d4324443b4..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n COUNT(id) as \"count: i64\"\n FROM testruns\n WHERE\n status = ?\n ", - "describe": { - "columns": [ - { - "name": "count: i64", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "a79a61b87325f3f1d9c5a4fb386ccd585be0641e5878acb6283b879f22ed2b4c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json deleted file mode 100644 index 62ac2b4bb6..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n nd.node_id,\n moniker,\n website,\n security_contact,\n details\n FROM\n nym_node_descriptions nd\n INNER JOIN\n nym_nodes\n WHERE\n bond_info IS NOT NULL\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "moniker", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "website", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "security_contact", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "details", - "ordinal": 4, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true, - true, - true, - true - ] - }, - "hash": "aa602604c0e7b6eef41ea3cd83c16610e15be2d7ee3f6c4d3debf23f95fb9c2e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json deleted file mode 100644 index 383c3681f0..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE nym_nodes\n SET\n self_described = NULL,\n bond_info = NULL", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "b68796d1d8d2384b30f1aace06269682c4ae96f774261f5c298264d3c12e5b67" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json deleted file mode 100644 index 8cc95e72de..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json deleted file mode 100644 index 86b8a642bd..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n mixnodes\n SET\n bonded = false\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json deleted file mode 100644 index 54fef15e56..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n node_id,\n self_described as \"self_described: serde_json::Value\"\n FROM\n nym_nodes\n WHERE\n self_described IS NOT NULL\n ORDER BY\n node_id\n ", - "describe": { - "columns": [ - { - "name": "node_id", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "self_described: serde_json::Value", - "ordinal": 1, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true - ] - }, - "hash": "c7656b2b1b4328415772ce69d0568bd5438d6c8496ca9cbdcfb70bb5375b345e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json deleted file mode 100644 index 5c88b5737e..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT mix_id\n FROM mixnodes\n WHERE bonded = true\n ", - "describe": { - "columns": [ - { - "name": "mix_id", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false - ] - }, - "hash": "c910788edefe64bbb34379702bcbde9ec6159c9fa03b13652e1f620dcd92125e" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json deleted file mode 100644 index 0d313098b6..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "DELETE FROM gateway_session_stats WHERE day <= ?", - "describe": { - "columns": [], - "parameters": { - "Right": 1 - }, - "nullable": [] - }, - "hash": "c9e61180ec35dfab8623333fafa3b216b93440d0fddc0a37dd1b6c1813741f6a" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json deleted file mode 100644 index 09138b5556..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n SELECT\n total_stake\n FROM nym_nodes\n WHERE node_id = ?\n ", - "describe": { - "columns": [ - { - "name": "total_stake", - "ordinal": 0, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 1 - }, - "nullable": [ - false - ] - }, - "hash": "d2e07d44594ca5b44a6100482ff432c39d761f2a0ac1d6515cf73416f2eb6c61" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json deleted file mode 100644 index 5f7443bd50..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO summary\n (key, value_json, last_updated_utc)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value_json=excluded.value_json,\n last_updated_utc=excluded.last_updated_utc;", - "describe": { - "columns": [], - "parameters": { - "Right": 3 - }, - "nullable": [] - }, - "hash": "e0c76a959276e3b0f44c720af9c74a5bf4912ee73468e62e7d0d96b1d9074cbe" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json deleted file mode 100644 index 9f0d25204f..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO gateway_description (\n gateway_identity_key,\n moniker,\n website,\n security_contact,\n details,\n last_updated_utc\n ) VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT (gateway_identity_key) DO UPDATE SET\n moniker = excluded.moniker,\n website = excluded.website,\n security_contact = excluded.security_contact,\n details = excluded.details,\n last_updated_utc = excluded.last_updated_utc\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 6 - }, - "nullable": [] - }, - "hash": "e9790b63ebe4bff5172bb8cb7bfc288364855003cf0e4d63e95047e7b502c650" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json deleted file mode 100644 index 7790b606af..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT\n id as \"id!\",\n gateway_id as \"gateway_id!\",\n status as \"status!\",\n created_utc as \"created_utc!\",\n ip_address as \"ip_address!\",\n log as \"log!\",\n last_assigned_utc\n FROM testruns\n WHERE\n id = ?\n AND\n status = ?\n ORDER BY created_utc\n LIMIT 1", - "describe": { - "columns": [ - { - "name": "id!", - "ordinal": 0, - "type_info": "Integer" - }, - { - "name": "gateway_id!", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "status!", - "ordinal": 2, - "type_info": "Integer" - }, - { - "name": "created_utc!", - "ordinal": 3, - "type_info": "Integer" - }, - { - "name": "ip_address!", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "log!", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "last_assigned_utc", - "ordinal": 6, - "type_info": "Integer" - } - ], - "parameters": { - "Right": 2 - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true - ] - }, - "hash": "f0c64794cbaed87a1d3166251d8e6720c9a9fc929144188460849be85d915004" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json deleted file mode 100644 index da95053996..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE\n gateways\n SET\n bonded = false\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "f7e3fa31d68c028bf39cc95389f29f8758ec922dd2e7ea064a1e537e580c9ee5" -} diff --git a/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json b/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json deleted file mode 100644 index 296c6389d2..0000000000 --- a/nym-node-status-api/nym-node-status-api/.sqlx/query-fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO nym_nodes_packet_stats_raw (\n node_id, timestamp_utc, packets_received, packets_sent, packets_dropped\n ) VALUES (?, ?, ?, ?, ?)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 5 - }, - "nullable": [] - }, - "hash": "fcb1698d9e0e3a14524c92e7c99a811588c2bbc50d4975487a0464321a1b18c9" -} diff --git a/nym-node-status-api/nym-node-status-api/Cargo.toml b/nym-node-status-api/nym-node-status-api/Cargo.toml index aa455a0ac6..8ff0de8917 100644 --- a/nym-node-status-api/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/nym-node-status-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-api" -version = "3.1.2" +version = "3.2.2" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -28,11 +28,15 @@ moka = { workspace = true, features = ["future"] } # Nym API: revert after Cheddar is out nym-contracts-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-mixnet-contract-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["openapi"]} -nym-node-status-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } +nym-bin-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "openapi", +] } +nym-node-status-client = { path = "../nym-node-status-client" } nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } -nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = ["middleware"]} +nym-http-api-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "middleware", +] } nym-network-defaults = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-serde-helpers = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } nym-statistics-common = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } @@ -62,7 +66,7 @@ serde_json = { workspace = true } serde_json_path = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } -sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "time"] } thiserror = { workspace = true } time = { workspace = true, features = ["formatting"] } tokio = { workspace = true, features = ["rt-multi-thread"] } @@ -77,16 +81,20 @@ utoipauto = { workspace = true } nym-node-metrics = { path = "../../nym-node/nym-node-metrics" } +[features] +default = ["pg"] +sqlite = ["sqlx/sqlite"] +pg = ["sqlx/postgres"] [build-dependencies] anyhow = { workspace = true } tokio = { workspace = true, features = ["macros"] } sqlx = { workspace = true, features = [ "runtime-tokio-rustls", - "sqlite", "macros", "migrate", ] } [dev-dependencies] +axum-test = "17.3.0" time = { workspace = true, features = ["macros"] } diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile b/nym-node-status-api/nym-node-status-api/Dockerfile-pg similarity index 90% rename from nym-node-status-api/nym-node-status-api/Dockerfile rename to nym-node-status-api/nym-node-status-api/Dockerfile-pg index 6eeaf8c6ee..4a6c7b24c5 100644 --- a/nym-node-status-api/nym-node-status-api/Dockerfile +++ b/nym-node-status-api/nym-node-status-api/Dockerfile-pg @@ -2,9 +2,9 @@ FROM harbor.nymte.ch/dockerhub/rust:latest AS builder COPY ./ /usr/src/nym -WORKDIR /usr/src/nym/nym-node-status-api +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ -RUN cargo build --release +RUN cargo build --release --features pg #------------------------------------------------------------------- diff --git a/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite b/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite new file mode 100644 index 0000000000..c37afedb08 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/Dockerfile-sqlite @@ -0,0 +1,37 @@ +# this will only work with VPN, otherwise remove the harbor part +FROM harbor.nymte.ch/dockerhub/rust:latest AS builder + +COPY ./ /usr/src/nym +WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-api/ + +RUN cargo build --release --features sqlite --no-default-features + + +#------------------------------------------------------------------- +# The following environment variables are required at runtime: +# +# EXPLORER_API +# NYXD +# NYM_API +# DATABASE_URL +# +# And optionally: +# +# NYM_NODE_STATUS_API_NYM_HTTP_CACHE_TTL +# NYM_NODE_STATUS_API_HTTP_PORT +# NYM_API_CLIENT_TIMEOUT +# EXPLORER_CLIENT_TIMEOUT +# NODE_STATUS_API_MONITOR_REFRESH_INTERVAL +# NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL +# +# see https://github.com/nymtech/nym/blob/develop/nym-node-status-api/src/cli.rs for details +#------------------------------------------------------------------- + +FROM harbor.nymte.ch/dockerhub/ubuntu:24.04 + +RUN apt-get update && apt-get install -y ca-certificates + +WORKDIR /nym + +COPY --from=builder /usr/src/nym/target/release/nym-node-status-api ./ +ENTRYPOINT [ "/nym/nym-node-status-api" ] diff --git a/nym-node-status-api/nym-node-status-api/Makefile b/nym-node-status-api/nym-node-status-api/Makefile new file mode 100644 index 0000000000..5f2881c1b4 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/Makefile @@ -0,0 +1,117 @@ +# Makefile for nym-node-status-api database management + +# --- Configuration --- +TEST_DATABASE_URL := postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# Docker compose service names +DB_SERVICE_NAME := postgres-test +DB_CONTAINER_NAME := nym_node_status_api_postgres_test + +# Default target +.PHONY: default +default: help + +# --- Main Targets --- +.PHONY: prepare-pg +prepare-pg: test-db-up test-db-wait test-db-migrate test-db-prepare test-db-down ## Setup PostgreSQL and prepare SQLx offline cache + +.PHONY: test-db +test-db: test-db-up test-db-wait test-db-migrate test-db-run test-db-down ## Run tests with PostgreSQL database + +.PHONY: dev-db +dev-db: test-db-up test-db-wait test-db-migrate ## Start PostgreSQL for development (keeps running) + @echo "PostgreSQL is running on port 5433" + @echo "Connection string: $(TEST_DATABASE_URL)" + +# --- Docker Compose Targets --- +.PHONY: test-db-up +test-db-up: ## Start the PostgreSQL test database in the background + @echo "Starting PostgreSQL test database..." + docker compose up -d $(DB_SERVICE_NAME) + +.PHONY: test-db-wait +test-db-wait: ## Wait for the PostgreSQL database to be healthy + @echo "Waiting for PostgreSQL database..." + @while ! docker inspect --format='{{.State.Health.Status}}' $(DB_CONTAINER_NAME) 2>/dev/null | grep -q 'healthy'; do \ + echo -n "."; \ + sleep 1; \ + done; \ + echo " Database is healthy!" + +.PHONY: test-db-down +test-db-down: ## Stop and remove the test database + @echo "Stopping PostgreSQL test database..." + docker compose down + +# --- SQLx Targets --- +.PHONY: test-db-migrate +test-db-migrate: ## Run database migrations against PostgreSQL + @echo "Running PostgreSQL migrations..." + DATABASE_URL="$(TEST_DATABASE_URL)" sqlx migrate run --source migrations_pg + +.PHONY: test-db-prepare +test-db-prepare: ## Run sqlx prepare for compile-time query verification + @echo "Running sqlx prepare for PostgreSQL..." + DATABASE_URL="$(TEST_DATABASE_URL)" cargo sqlx prepare -- --features pg + +# --- Build and Test Targets --- +.PHONY: test-db-run +test-db-run: ## Run tests with PostgreSQL feature + @echo "Running tests with PostgreSQL..." + DATABASE_URL="$(TEST_DATABASE_URL)" cargo test --features pg --no-default-features + +.PHONY: build-pg +build-pg: ## Build with PostgreSQL feature + @echo "Building with PostgreSQL feature..." + cargo build --features pg --no-default-features + +.PHONY: build-sqlite +build-sqlite: ## Build with SQLite feature (default) + @echo "Building with SQLite feature..." + cargo build --features sqlite --no-default-features + +.PHONY: check-pg +check-pg: ## Check code with PostgreSQL feature + @echo "Checking code with PostgreSQL feature..." + cargo check --features pg --no-default-features + +.PHONY: check-sqlite +check-sqlite: ## Check code with SQLite feature + @echo "Checking code with SQLite feature..." + cargo check --features sqlite --no-default-features + +.PHONY: clippy +clippy: clippy-pg clippy-sqlite + +.PHONY: clippy-pg +clippy-pg: ## Run clippy with PostgreSQL feature + @echo "Running clippy with PostgreSQL feature..." + cargo clippy --features pg --no-default-features -- -D warnings + +.PHONY: clippy-sqlite +clippy-sqlite: ## Run clippy with SQLite feature (default) + @echo "Running clippy with SQLite feature..." + cargo clippy --features sqlite --no-default-features -- -D warnings + +# --- Cleanup Targets --- +.PHONY: clean +clean: ## Clean build artifacts and SQLx cache + cargo clean + rm -rf .sqlx + +.PHONY: clean-db +clean-db: test-db-down ## Stop database and clean volumes + docker volume rm -f nym-node-status-api_postgres_test_data 2>/dev/null || true + +# --- Utility Targets --- +.PHONY: sqlx-cli +sqlx-cli: ## Install sqlx-cli if not already installed + @command -v sqlx >/dev/null 2>&1 || cargo install sqlx-cli --features postgres,sqlite + +.PHONY: psql +psql: ## Connect to the running PostgreSQL database with psql + @docker exec -it $(DB_CONTAINER_NAME) psql -U testuser -d nym_node_status_api_test + +.PHONY: help +help: ## Show help for Makefile targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/nym-node-status-api/nym-node-status-api/README_PG.md b/nym-node-status-api/nym-node-status-api/README_PG.md new file mode 100644 index 0000000000..8a6d2c3f0b --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/README_PG.md @@ -0,0 +1,104 @@ +# PostgreSQL Support for nym-node-status-api + +This project now supports both SQLite (default) and PostgreSQL databases. + +## Quick Start with PostgreSQL + +### 1. Install Prerequisites + +```bash +# Install sqlx-cli if not already installed +make sqlx-cli +``` + +### 2. Prepare PostgreSQL for Development + +```bash +# This will: +# - Start PostgreSQL in Docker +# - Run migrations +# - Generate SQLx offline query cache +# - Stop the database +make prepare-pg +``` + +### 3. Build with PostgreSQL + +```bash +# Build with PostgreSQL feature +make build-pg + +# Or manually: +cargo build --features pg --no-default-features +``` + +### 4. Run with PostgreSQL + +```bash +# Start PostgreSQL for development (keeps running) +make dev-db + +# In another terminal, run the application +DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test \ +cargo run --features pg --no-default-features +``` + +## Database Features + +- `sqlite` (default): Uses SQLite database +- `pg`: Uses PostgreSQL database + +Only one database feature can be active at a time. + +## Migration Differences + +SQLite migrations are in `migrations/`, PostgreSQL migrations are in `migrations_pg/`. + +Key differences: +- **AUTOINCREMENT** → **SERIAL** +- **INTEGER CHECK (0,1)** → **BOOLEAN** +- **REAL** → **DOUBLE PRECISION** +- No table recreation needed for constraint changes in PostgreSQL + +## Makefile Targets + +```bash +make help # Show all available targets +make prepare-pg # Setup PostgreSQL and prepare SQLx cache +make dev-db # Start PostgreSQL for development +make test-db # Run tests with PostgreSQL +make build-pg # Build with PostgreSQL +make build-sqlite # Build with SQLite +make psql # Connect to running PostgreSQL +make clean # Clean build artifacts +make clean-db # Stop database and clean volumes +``` + +## Environment Variables + +See `.env.example` for all configuration options. Key variable: + +```bash +# For PostgreSQL: +DATABASE_URL=postgres://testuser:testpass@localhost:5433/nym_node_status_api_test + +# For SQLite: +DATABASE_URL=sqlite://nym-node-status-api.sqlite +``` + +## Troubleshooting + +### SQLx Offline Mode + +If you see "no cached data for this query" errors: + +1. Ensure PostgreSQL is running: `make dev-db` +2. Run: `make test-db-prepare` + +### Connection Refused + +If you see "Connection refused" errors: + +1. Check Docker is running: `docker ps` +2. Check PostgreSQL container: `docker ps | grep nym_node_status_api_postgres_test` +3. Restart database: `make test-db-down && make dev-db` \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh b/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh new file mode 100755 index 0000000000..8aaa823ba6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/build-push-node-status-api.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Build and push Node Status API container to harbor.nymte.ch + +set -e + +# Configuration +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +WORKING_DIRECTORY="${SCRIPT_DIR}" +CONTAINER_NAME="node-status-api" +REGISTRY="harbor.nymte.ch" +NAMESPACE="nym" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to display usage +usage() { + echo "Usage: $0 [pg|sqlite|both]" + echo " pg - Build and push PostgreSQL version" + echo " sqlite - Build and push SQLite version" + echo " both - Build and push both versions (default)" + exit 1 +} + +# Parse arguments +DB_TYPE="${1:-both}" + +if [[ ! "$DB_TYPE" =~ ^(pg|sqlite|both)$ ]]; then + usage +fi + +# Get version from Cargo.toml +VERSION=$(grep "^version = " "${WORKING_DIRECTORY}/Cargo.toml" | sed -E 's/version = "(.*)"/\1/') +if [ -z "$VERSION" ]; then + echo -e "${RED}Error: Could not extract version from Cargo.toml${NC}" + exit 1 +fi +echo -e "${YELLOW}Version: ${VERSION}${NC}" + +# Login to Harbor +echo -e "${GREEN}Logging into Harbor...${NC}" +docker login "${REGISTRY}" + +# Function to build and push +build_and_push() { + local db_type=$1 + local dockerfile="Dockerfile-${db_type}" + + echo -e "${GREEN}Building ${db_type} container...${NC}" + # Build from repository root (two levels up from script location) + docker build -f "${WORKING_DIRECTORY}/${dockerfile}" "${SCRIPT_DIR}/../.." \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${db_type}" \ + -t "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${db_type}" + + echo -e "${GREEN}Pushing ${db_type} container to Harbor...${NC}" + docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:${VERSION}-${db_type}" + docker push "${REGISTRY}/${NAMESPACE}/${CONTAINER_NAME}:latest-${db_type}" + + echo -e "${GREEN}Successfully built and pushed ${CONTAINER_NAME}:${VERSION}-${db_type}${NC}" +} + +# Build based on selection +case "$DB_TYPE" in + pg) + build_and_push "pg" + ;; + sqlite) + build_and_push "sqlite" + ;; + both) + build_and_push "pg" + echo "" + build_and_push "sqlite" + ;; +esac + +echo -e "${GREEN}All builds completed successfully!${NC}" \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/build.rs b/nym-node-status-api/nym-node-status-api/build.rs index 9da6d48d2a..a3b6635aef 100644 --- a/nym-node-status-api/nym-node-status-api/build.rs +++ b/nym-node-status-api/nym-node-status-api/build.rs @@ -1,17 +1,20 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; +#[cfg(feature = "sqlite")] use sqlx::{Connection, SqliteConnection}; +#[cfg(feature = "sqlite")] #[cfg(target_family = "unix")] use std::fs::Permissions; +#[cfg(feature = "sqlite")] #[cfg(target_family = "unix")] use std::os::unix::fs::PermissionsExt; +#[cfg(feature = "sqlite")] use tokio::{fs::File, io::AsyncWriteExt}; +#[cfg(feature = "sqlite")] const SQLITE_DB_FILENAME: &str = "nym-node-status-api.sqlite"; -/// If you need to re-run migrations or reset the db, just run -/// cargo clean -p nym-node-status-api -#[tokio::main(flavor = "current_thread")] -async fn main() -> Result<()> { +#[cfg(feature = "sqlite")] +async fn init_db() -> Result<()> { let out_dir = read_env_var("OUT_DIR")?; let database_path = format!("{out_dir}/{SQLITE_DB_FILENAME}?mode=rwc"); @@ -30,11 +33,22 @@ async fn main() -> Result<()> { Ok(()) } +/// If you need to re-run migrations or reset the db, just run +/// cargo clean -p nym-node-status-api +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + #[cfg(feature = "sqlite")] + init_db().await?; + Ok(()) +} + +#[cfg(feature = "sqlite")] fn read_env_var(var: &str) -> Result { - std::env::var(var).map_err(|_| anyhow!("You need to set {} env var", var)) + std::env::var(var).map_err(|_| anyhow::anyhow!("You need to set {} env var", var)) } /// use `./enter_db.sh` to inspect DB +#[cfg(feature = "sqlite")] async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Result<()> { let mut file = File::create("settings.sql").await?; let settings = ".mode columns diff --git a/nym-node-status-api/nym-node-status-api/docker-compose.yml b/nym-node-status-api/nym-node-status-api/docker-compose.yml new file mode 100644 index 0000000000..12dbe51031 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/docker-compose.yml @@ -0,0 +1,21 @@ +services: + postgres-test: + image: postgres:16-alpine + container_name: nym_node_status_api_postgres_test + environment: + POSTGRES_DB: nym_node_status_api_test + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpass + ports: + - '5433:5432' # Map to 5433 to avoid conflicts with default PostgreSQL + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U testuser -d nym_node_status_api_test'] + interval: 5s + timeout: 5s + retries: 5 + # Optional: Add volume for persistent data during development + # volumes: + # - postgres_test_data:/var/lib/postgresql/data + +# volumes: +# postgres_test_data: \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql b/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql new file mode 100644 index 0000000000..235b50e171 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations/008_performance_indexes.sql @@ -0,0 +1,16 @@ +-- Add partial indexes for NOT NULL filtering to improve performance of /explorer/v3/nodes endpoint + +-- Index for queries filtering on self_described IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_self_described_not_null +ON nym_nodes(node_id) +WHERE self_described IS NOT NULL; + +-- Index for queries filtering on bond_info IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_info_not_null +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL; + +-- Composite index for queries filtering on both bond_info AND self_described +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_self_described +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL AND self_described IS NOT NULL; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql new file mode 100644 index 0000000000..23f84e8667 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000000_init.sql @@ -0,0 +1,113 @@ +CREATE TABLE gateways +( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR NOT NULL UNIQUE, + self_described VARCHAR NOT NULL, + explorer_pretty_bond VARCHAR, + last_probe_result VARCHAR, + last_probe_log VARCHAR, + config_score INTEGER NOT NULL DEFAULT 0, + config_score_successes DOUBLE PRECISION NOT NULL DEFAULT 0, + config_score_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + routing_score INTEGER NOT NULL DEFAULT 0, + routing_score_successes DOUBLE PRECISION NOT NULL DEFAULT 0, + routing_score_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + test_run_samples DOUBLE PRECISION NOT NULL DEFAULT 0, + last_testrun_utc BIGINT, + last_updated_utc BIGINT NOT NULL, + bonded BOOLEAN NOT NULL DEFAULT FALSE, + blacklisted BOOLEAN NOT NULL DEFAULT FALSE, + performance INTEGER NOT NULL DEFAULT 0 +); + +CREATE INDEX idx_gateway_description_gateway_identity_key ON gateways (gateway_identity_key); + + +CREATE TABLE mixnodes ( + id SERIAL PRIMARY KEY, + identity_key VARCHAR NOT NULL UNIQUE, + mix_id BIGINT NOT NULL UNIQUE, + bonded BOOLEAN NOT NULL DEFAULT FALSE, + total_stake BIGINT NOT NULL, + host VARCHAR NOT NULL, + http_api_port BIGINT NOT NULL, + blacklisted BOOLEAN NOT NULL DEFAULT FALSE, + full_details VARCHAR, + self_described VARCHAR, + last_updated_utc BIGINT NOT NULL, + is_dp_delegatee BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX idx_mixnodes_mix_id ON mixnodes (mix_id); +CREATE INDEX idx_mixnodes_identity_key ON mixnodes (identity_key); + +CREATE TABLE mixnode_description ( + id SERIAL PRIMARY KEY, + mix_id BIGINT UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc BIGINT NOT NULL, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id) +); + +-- Indexes for description table +CREATE INDEX idx_mixnode_description_mix_id ON mixnode_description (mix_id); + + +CREATE TABLE summary +( + key VARCHAR PRIMARY KEY, + value_json VARCHAR, + last_updated_utc BIGINT NOT NULL +); + + +CREATE TABLE summary_history +( + id SERIAL PRIMARY KEY, + date VARCHAR UNIQUE NOT NULL, + timestamp_utc BIGINT NOT NULL, + value_json VARCHAR +); + +CREATE INDEX idx_summary_history_timestamp_utc ON summary_history (timestamp_utc); +CREATE INDEX idx_summary_history_date ON summary_history (date); + + +CREATE TABLE gateway_description ( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc BIGINT NOT NULL, + FOREIGN KEY (gateway_identity_key) REFERENCES gateways (gateway_identity_key) +); + + +CREATE TABLE mixnode_daily_stats ( + id SERIAL PRIMARY KEY, + mix_id BIGINT NOT NULL, + total_stake BIGINT NOT NULL, + date_utc VARCHAR NOT NULL, + packets_received INTEGER DEFAULT 0, + packets_sent INTEGER DEFAULT 0, + packets_dropped INTEGER DEFAULT 0, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id), + UNIQUE (mix_id, date_utc) -- This constraint automatically creates an index +); + + +CREATE TABLE testruns +( + id SERIAL PRIMARY KEY, + gateway_id INTEGER NOT NULL, + status INTEGER NOT NULL, -- 0=pending, 1=in-progress, 2=complete + timestamp_utc BIGINT NOT NULL, + ip_address VARCHAR NOT NULL, + log VARCHAR NOT NULL, + FOREIGN KEY (gateway_id) REFERENCES gateways (id) +); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql new file mode 100644 index 0000000000..f5929f0034 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000001_last_assigned_utc.sql @@ -0,0 +1,5 @@ +ALTER TABLE testruns +RENAME COLUMN timestamp_utc TO created_utc; + +ALTER TABLE testruns +ADD COLUMN last_assigned_utc BIGINT; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql new file mode 100644 index 0000000000..e2d8e8440c --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000002_session_stats.sql @@ -0,0 +1,16 @@ +CREATE TABLE gateway_session_stats ( + id SERIAL PRIMARY KEY, + gateway_identity_key VARCHAR NOT NULL, + node_id BIGINT NOT NULL, + day DATE NOT NULL, + unique_active_clients BIGINT NOT NULL, + session_started BIGINT NOT NULL, + users_hashes VARCHAR, + vpn_sessions VARCHAR, + mixnet_sessions VARCHAR, + unknown_sessions VARCHAR, + UNIQUE (node_id, day) -- This constraint automatically creates an index +); + +CREATE INDEX idx_gateway_session_stats_identity_key ON gateway_session_stats (gateway_identity_key); +CREATE INDEX idx_gateway_session_stats_day ON gateway_session_stats (day); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql new file mode 100644 index 0000000000..ba591789d5 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000003_scraper_tables.sql @@ -0,0 +1,11 @@ +CREATE TABLE mixnode_packet_stats_raw ( + id SERIAL PRIMARY KEY, + mix_id BIGINT NOT NULL, + timestamp_utc BIGINT NOT NULL, + packets_received INTEGER, + packets_sent INTEGER, + packets_dropped INTEGER, + FOREIGN KEY (mix_id) REFERENCES mixnodes (mix_id) +); + +CREATE INDEX idx_mixnode_packet_stats_raw_mix_id_timestamp_utc ON mixnode_packet_stats_raw (mix_id, timestamp_utc); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql new file mode 100644 index 0000000000..a473ab1190 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000004_obsolete_fields.sql @@ -0,0 +1,54 @@ +ALTER TABLE mixnodes DROP COLUMN blacklisted; +ALTER TABLE gateways DROP COLUMN blacklisted; + +CREATE TABLE nym_nodes ( + node_id INTEGER PRIMARY KEY, + ed25519_identity_pubkey VARCHAR NOT NULL UNIQUE, + total_stake BIGINT NOT NULL, + ip_addresses TEXT NOT NULL, + mix_port INTEGER NOT NULL, + x25519_sphinx_pubkey VARCHAR NOT NULL UNIQUE, + node_role TEXT NOT NULL, + supported_roles TEXT NOT NULL, + performance VARCHAR NOT NULL, + entry TEXT, + last_updated_utc INTEGER NOT NULL +); + +CREATE INDEX idx_nym_nodes_node_id ON nym_nodes (node_id); +CREATE INDEX idx_nym_nodes_ed25519_identity_pubkey ON nym_nodes (ed25519_identity_pubkey); + +CREATE TABLE nym_node_descriptions ( + id SERIAL PRIMARY KEY, + node_id INTEGER UNIQUE NOT NULL, + moniker VARCHAR, + website VARCHAR, + security_contact VARCHAR, + details VARCHAR, + last_updated_utc INTEGER NOT NULL, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) +); + +CREATE TABLE nym_nodes_packet_stats_raw ( + id SERIAL PRIMARY KEY, + node_id INTEGER NOT NULL, + timestamp_utc INTEGER NOT NULL, + packets_received INTEGER, + packets_sent INTEGER, + packets_dropped INTEGER, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) +); + +CREATE INDEX idx_nym_nodes_packet_stats_raw_node_id_timestamp_utc ON nym_nodes_packet_stats_raw (node_id, timestamp_utc); + +CREATE TABLE nym_node_daily_mixing_stats ( + id SERIAL PRIMARY KEY, + node_id INTEGER NOT NULL, + total_stake BIGINT NOT NULL, + date_utc VARCHAR NOT NULL, + packets_received INTEGER DEFAULT 0, + packets_sent INTEGER DEFAULT 0, + packets_dropped INTEGER DEFAULT 0, + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id), + UNIQUE (node_id, date_utc) -- This constraint automatically creates an index +); \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql new file mode 100644 index 0000000000..8e7bc7ef10 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000005_node_self_described.sql @@ -0,0 +1,23 @@ +ALTER TABLE nym_nodes ADD COLUMN self_described TEXT; +ALTER TABLE nym_nodes ADD COLUMN bond_info TEXT; + +-- PostgreSQL doesn't need table recreation for adding ON DELETE CASCADE +-- We can drop and recreate the foreign key constraints directly + +-- Drop existing foreign key constraints +ALTER TABLE nym_node_descriptions DROP CONSTRAINT IF EXISTS nym_node_descriptions_node_id_fkey; +ALTER TABLE nym_nodes_packet_stats_raw DROP CONSTRAINT IF EXISTS nym_nodes_packet_stats_raw_node_id_fkey; +ALTER TABLE nym_node_daily_mixing_stats DROP CONSTRAINT IF EXISTS nym_node_daily_mixing_stats_node_id_fkey; + +-- Add foreign key constraints with ON DELETE CASCADE +ALTER TABLE nym_node_descriptions + ADD CONSTRAINT nym_node_descriptions_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; + +ALTER TABLE nym_nodes_packet_stats_raw + ADD CONSTRAINT nym_nodes_packet_stats_raw_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; + +ALTER TABLE nym_node_daily_mixing_stats + ADD CONSTRAINT nym_node_daily_mixing_stats_node_id_fkey + FOREIGN KEY (node_id) REFERENCES nym_nodes (node_id) ON DELETE CASCADE; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql new file mode 100644 index 0000000000..98a7b75ca7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000006_remove_unique_constraint.sql @@ -0,0 +1,9 @@ +-- Removing UNIQUE constraints on nym_nodes +-- In PostgreSQL, we can drop constraints directly without recreating the table + +-- Drop the unique constraints +ALTER TABLE nym_nodes DROP CONSTRAINT IF EXISTS nym_nodes_ed25519_identity_pubkey_key; +ALTER TABLE nym_nodes DROP CONSTRAINT IF EXISTS nym_nodes_x25519_sphinx_pubkey_key; + +-- The columns and indexes remain, only the unique constraints are removed +-- The existing indexes idx_nym_nodes_node_id and idx_nym_nodes_ed25519_identity_pubkey remain unchanged \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql new file mode 100644 index 0000000000..64fb3064f7 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000007_date_fix.sql @@ -0,0 +1,112 @@ +-- for a couple of days after migrating chrono -> time, we stored dates as +-- 2025-June-DD instead of 2025-06-DD. This migration fixes those entries. +-- +-- Because of a UNIQUE constraint on (node_id, date_utc), we can't just rename in-place. +-- - merge (add) node stats back to the original table where conflict (node_id, date_utc) would exist +-- - delete invalid records from original table (those stats were merged into correct rows above) +-- - insert rows that did not have a conflicting (node_Id, date_utc) combo +-- Conflicts affect only the date which has both kinds of entries, +-- e.g. 2025-06-05 and 2025-June-05 (date when this change was deployed) +-- +-- This applies to both affected tables. + +-- ---------------------------------------- +-- mixnode_daily_stats +-- ---------------------------------------- + +-- First, copy over rows with invalid date to a temp table (in the correct date format) +CREATE TEMP TABLE tmp_mix AS +SELECT + mix_id, + REPLACE(date_utc,'June','06') AS new_date, + SUM(total_stake) AS total_stake_sum, + SUM(packets_received) AS packets_received_sum, + SUM(packets_sent) AS packets_sent_sum, + SUM(packets_dropped) AS packets_dropped_sum +FROM mixnode_daily_stats +WHERE date_utc LIKE '%June%' +GROUP BY mix_id, new_date; + +UPDATE mixnode_daily_stats AS m +SET + total_stake = m.total_stake, + packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc), + packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc), + packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_mix WHERE mix_id = m.mix_id AND new_date = m.date_utc) +WHERE EXISTS ( + SELECT 1 FROM tmp_mix + WHERE mix_id = m.mix_id + AND new_date = m.date_utc +); + +DELETE FROM mixnode_daily_stats + WHERE date_utc LIKE '%June%'; + +INSERT INTO mixnode_daily_stats + (mix_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped) +SELECT + mix_id, + new_date, + total_stake_sum, + packets_received_sum, + packets_sent_sum, + packets_dropped_sum +FROM tmp_mix AS t +-- only those whose new_date did _not_ already exist +WHERE NOT EXISTS ( + SELECT 1 FROM mixnode_daily_stats AS m + WHERE m.mix_id = t.mix_id + AND m.date_utc = t.new_date +); + +DROP TABLE tmp_mix; + + +-- ---------------------------------------- +-- nym_node_daily_mixing_stats +-- ---------------------------------------- + +CREATE TEMP TABLE tmp_nym_node_stats AS +SELECT + node_id, + REPLACE(date_utc,'June','06') AS new_date, + SUM(total_stake) AS total_stake_sum, + SUM(packets_received) AS packets_received_sum, + SUM(packets_sent) AS packets_sent_sum, + SUM(packets_dropped) AS packets_dropped_sum +FROM nym_node_daily_mixing_stats +WHERE date_utc LIKE '%June%' +GROUP BY node_id, new_date; + +UPDATE nym_node_daily_mixing_stats AS m +SET + total_stake = m.total_stake, + packets_received = m.packets_received + (SELECT packets_received_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc), + packets_sent = m.packets_sent + (SELECT packets_sent_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc), + packets_dropped = m.packets_dropped + (SELECT packets_dropped_sum FROM tmp_nym_node_stats WHERE node_id = m.node_id AND new_date = m.date_utc) +WHERE EXISTS ( + SELECT 1 FROM tmp_nym_node_stats + WHERE node_id = m.node_id + AND new_date = m.date_utc +); + +DELETE FROM nym_node_daily_mixing_stats + WHERE date_utc LIKE '%June%'; + +INSERT INTO nym_node_daily_mixing_stats + (node_id, date_utc, total_stake, packets_received, packets_sent, packets_dropped) +SELECT + node_id, + new_date, + total_stake_sum, + packets_received_sum, + packets_sent_sum, + packets_dropped_sum +FROM tmp_nym_node_stats AS t +WHERE NOT EXISTS ( + SELECT 1 FROM nym_node_daily_mixing_stats AS m + WHERE m.node_id = t.node_id + AND m.date_utc = t.new_date +); + +DROP TABLE tmp_nym_node_stats; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql new file mode 100644 index 0000000000..4ebfec1595 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000008_jsonb_columns.sql @@ -0,0 +1,3 @@ +-- Convert ip_addresses column from TEXT to JSONB for better type safety +ALTER TABLE nym_nodes + ALTER COLUMN ip_addresses TYPE JSONB USING ip_addresses::JSONB; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql new file mode 100644 index 0000000000..1dcec55fea --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000009_more_jsonb_columns.sql @@ -0,0 +1,7 @@ +-- Convert remaining TEXT columns that store JSON to JSONB +ALTER TABLE nym_nodes + ALTER COLUMN node_role TYPE JSONB USING node_role::JSONB, + ALTER COLUMN supported_roles TYPE JSONB USING supported_roles::JSONB, + ALTER COLUMN entry TYPE JSONB USING entry::JSONB, + ALTER COLUMN self_described TYPE JSONB USING self_described::JSONB, + ALTER COLUMN bond_info TYPE JSONB USING bond_info::JSONB; \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql new file mode 100644 index 0000000000..15bee4ac30 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/migrations_pg/20240101000010_performance_indexes_postgres.sql @@ -0,0 +1,17 @@ +-- Add partial indexes for NOT NULL filtering to improve performance of /explorer/v3/nodes endpoint +-- PostgreSQL version + +-- Index for queries filtering on self_described IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_self_described_not_null +ON nym_nodes(node_id) +WHERE self_described IS NOT NULL; + +-- Index for queries filtering on bond_info IS NOT NULL +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_info_not_null +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL; + +-- Composite index for queries filtering on both bond_info AND self_described +CREATE INDEX IF NOT EXISTS idx_nym_nodes_bond_self_described +ON nym_nodes(node_id) +WHERE bond_info IS NOT NULL AND self_described IS NOT NULL; diff --git a/nym-node-status-api/nym-node-status-api/src/db/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/mod.rs index 3f7d944906..964193455e 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/mod.rs @@ -1,24 +1,52 @@ use anyhow::{anyhow, Result}; -use sqlx::{ - migrate::Migrator, - query, - sqlite::{SqliteAutoVacuum, SqliteConnectOptions, SqliteSynchronous}, - ConnectOptions, SqlitePool, -}; use std::{str::FromStr, time::Duration}; pub(crate) mod models; pub(crate) mod queries; +pub(crate) mod query_wrapper; +#[cfg(test)] +mod tests; + +// Re-export the query wrapper functions for easier access +pub(crate) use query_wrapper::query; +#[allow(unused_imports)] +pub(crate) use query_wrapper::query_as; + +#[cfg(feature = "sqlite")] +use sqlx::{ + migrate::Migrator, + sqlite::{SqliteAutoVacuum, SqliteConnectOptions, SqliteSynchronous}, + ConnectOptions, SqlitePool, +}; + +#[cfg(feature = "pg")] +use sqlx::{migrate::Migrator, postgres::PgConnectOptions, ConnectOptions, PgPool}; + +#[cfg(feature = "sqlite")] static MIGRATOR: Migrator = sqlx::migrate!("./migrations"); +#[cfg(feature = "pg")] +static MIGRATOR: Migrator = sqlx::migrate!("./migrations_pg"); + +#[cfg(feature = "sqlite")] pub(crate) type DbPool = SqlitePool; +#[cfg(feature = "pg")] +pub(crate) type DbPool = PgPool; + +#[cfg(feature = "sqlite")] +pub(crate) type DbConnection = sqlx::pool::PoolConnection; + +#[cfg(feature = "pg")] +pub(crate) type DbConnection = sqlx::pool::PoolConnection; + pub(crate) struct Storage { pool: DbPool, } impl Storage { + #[cfg(feature = "sqlite")] pub async fn init(connection_url: String, busy_timeout: Duration) -> Result { let connect_options = SqliteConnectOptions::from_str(&connection_url)? .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) @@ -41,16 +69,39 @@ impl Storage { Ok(Storage { pool }) } + #[cfg(feature = "pg")] + pub async fn init(connection_url: String, _busy_timeout: Duration) -> Result { + use std::env; + let mut connect_options = + PgConnectOptions::from_str(&connection_url)?.disable_statement_logging(); + + let ssl_cert_path = env::var("PG_CERT").ok(); + + if let Some(ssl_cert) = ssl_cert_path { + connect_options = connect_options + .ssl_mode(sqlx::postgres::PgSslMode::Require) + .ssl_root_cert(ssl_cert); + } + let pool = sqlx::PgPool::connect_with(connect_options) + .await + .map_err(|err| anyhow!("Failed to connect to {}: {}", &connection_url, err))?; + + MIGRATOR.run(&pool).await?; + + Ok(Storage { pool }) + } + /// Cloning pool is cheap, it's the same underlying set of connections pub fn pool_owned(&self) -> DbPool { self.pool.clone() } + #[cfg(feature = "sqlite")] async fn assert_busy_timeout(pool: DbPool, expected_busy_timeout_s: i64) -> Result<()> { let mut conn = pool.acquire().await?; // Sqlite stores this value as miliseconds // https://www.sqlite.org/pragma.html#pragma_busy_timeout - let busy_timeout_db = query!("PRAGMA busy_timeout;") + let busy_timeout_db = sqlx::query!("PRAGMA busy_timeout;") .fetch_one(conn.as_mut()) .await?; diff --git a/nym-node-status-api/nym-node-status-api/src/db/models.rs b/nym-node-status-api/nym-node-status-api/src/db/models.rs index b74fdabde3..0fcd5adee4 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/models.rs @@ -38,11 +38,11 @@ pub(crate) struct GatewayInsertRecord { pub(crate) performance: u8, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub(crate) struct GatewayDto { pub(crate) gateway_identity_key: String, pub(crate) bonded: bool, - pub(crate) performance: i64, + pub(crate) performance: i32, pub(crate) self_described: Option, pub(crate) explorer_pretty_bond: Option, pub(crate) last_probe_result: Option, @@ -121,7 +121,7 @@ pub(crate) struct MixnodeRecord { pub(crate) is_dp_delegatee: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub(crate) struct MixnodeDto { pub(crate) mix_id: i64, pub(crate) bonded: bool, @@ -183,14 +183,14 @@ pub(crate) struct BondedStatusDto { } #[allow(unused)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, FromRow)] pub(crate) struct SummaryDto { pub(crate) key: String, pub(crate) value_json: String, pub(crate) last_updated_utc: i64, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, FromRow)] pub(crate) struct SummaryHistoryDto { #[allow(dead_code)] pub id: i64, @@ -287,11 +287,11 @@ pub(crate) mod gateway { } #[allow(dead_code)] // not dead code, this is SQL data model -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub struct TestRunDto { - pub id: i64, - pub gateway_id: i64, - pub status: i64, + pub id: i32, + pub gateway_id: i32, + pub status: i32, pub created_utc: i64, pub ip_address: String, pub log: String, @@ -313,9 +313,9 @@ pub struct GatewayIdentityDto { } #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros -#[derive(Debug, Clone)] +#[derive(Debug, Clone, FromRow)] pub struct GatewayInfoDto { - pub id: i64, + pub id: i32, pub gateway_identity_key: String, pub self_described: Option, pub explorer_pretty_bond: Option, @@ -379,6 +379,7 @@ impl ScrapeNodeKind { } } +#[derive(Clone)] pub(crate) struct ScraperNodeInfo { pub node_kind: ScrapeNodeKind, pub hosts: Vec, @@ -412,11 +413,11 @@ impl ScraperNodeInfo { #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros #[derive(FromRow, Debug)] pub(crate) struct NymNodeDto { - pub node_id: i64, + pub node_id: i32, pub ed25519_identity_pubkey: String, pub total_stake: i64, pub ip_addresses: serde_json::Value, - pub mix_port: i64, + pub mix_port: i32, pub x25519_sphinx_pubkey: String, pub node_role: serde_json::Value, pub supported_roles: serde_json::Value, @@ -429,11 +430,11 @@ pub(crate) struct NymNodeDto { #[allow(dead_code)] // it's not dead code but clippy doesn't detect usage in sqlx macros #[derive(Debug)] pub(crate) struct NymNodeInsertRecord { - pub node_id: i64, + pub node_id: i32, pub ed25519_identity_pubkey: String, pub total_stake: i64, pub ip_addresses: serde_json::Value, - pub mix_port: i64, + pub mix_port: i32, pub x25519_sphinx_pubkey: String, pub node_role: serde_json::Value, pub supported_roles: serde_json::Value, @@ -441,7 +442,7 @@ pub(crate) struct NymNodeInsertRecord { pub entry: Option, pub self_described: Option, pub bond_info: Option, - pub last_updated_utc: String, + pub last_updated_utc: i64, } impl NymNodeInsertRecord { @@ -450,7 +451,7 @@ impl NymNodeInsertRecord { bond_info: Option<&NymNodeDetails>, self_described: Option<&NymNodeDescription>, ) -> anyhow::Result { - let now = OffsetDateTime::now_utc().to_string(); + let now = OffsetDateTime::now_utc().unix_timestamp(); // if bond info is missing, set stake to 0 let total_stake = bond_info @@ -461,11 +462,11 @@ impl NymNodeInsertRecord { let self_described = serialize_opt_to_value!(self_described)?; let record = Self { - node_id: skimmed_node.node_id.into(), + node_id: skimmed_node.node_id as i32, ed25519_identity_pubkey: skimmed_node.ed25519_identity_pubkey.to_base58_string(), total_stake, ip_addresses: serde_json::to_value(&skimmed_node.ip_addresses)?, - mix_port: skimmed_node.mix_port as i64, + mix_port: skimmed_node.mix_port as i32, x25519_sphinx_pubkey: skimmed_node.x25519_sphinx_pubkey.to_base58_string(), node_role: serde_json::to_value(&skimmed_node.role)?, supported_roles: serde_json::to_value(skimmed_node.supported_roles)?, @@ -514,11 +515,11 @@ impl TryFrom for SkimmedNode { } } -#[derive(Debug, Serialize, Deserialize, sqlx::Decode)] +#[derive(Debug, Serialize, Deserialize, sqlx::Decode, FromRow)] pub struct NodeStats { - pub packets_received: i64, - pub packets_sent: i64, - pub packets_dropped: i64, + pub packets_received: i32, + pub packets_sent: i32, + pub packets_dropped: i32, } pub struct InsertStatsRecord { diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs index 961da755b2..4b0dc2c0f9 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways.rs @@ -3,32 +3,32 @@ use std::collections::HashSet; use crate::{ db::{ models::{GatewayDto, GatewayInsertRecord}, - DbPool, + DbConnection, DbPool, }, http::models::Gateway, node_scraper::helpers::NodeDescriptionResponse, }; use futures_util::TryStreamExt; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use tracing::error; pub(crate) async fn select_gateway_identity( - conn: &mut PoolConnection, - gateway_pk: i64, + conn: &mut DbConnection, + gateway_pk: i32, ) -> anyhow::Result { - let record = sqlx::query!( + let record = crate::db::query( r#"SELECT gateway_identity_key FROM gateways WHERE id = ?"#, - gateway_pk ) + .bind(gateway_pk) .fetch_one(conn.as_mut()) .await?; - Ok(record.gateway_identity_key) + Ok(record.try_get("gateway_identity_key")?) } pub(crate) async fn update_bonded_gateways( @@ -37,7 +37,7 @@ pub(crate) async fn update_bonded_gateways( ) -> anyhow::Result<()> { let mut tx = pool.begin().await?; - sqlx::query!( + crate::db::query( r#"UPDATE gateways SET @@ -48,7 +48,7 @@ pub(crate) async fn update_bonded_gateways( .await?; for record in gateways { - sqlx::query!( + crate::db::query( "INSERT INTO gateways (gateway_identity_key, bonded, self_described, explorer_pretty_bond, @@ -60,13 +60,13 @@ pub(crate) async fn update_bonded_gateways( explorer_pretty_bond=excluded.explorer_pretty_bond, last_updated_utc=excluded.last_updated_utc, performance = excluded.performance;", - record.identity_key, - record.bonded, - record.self_described, - record.explorer_pretty_bond, - record.last_updated_utc, - record.performance ) + .bind(record.identity_key) + .bind(record.bonded) + .bind(record.self_described) + .bind(record.explorer_pretty_bond) + .bind(record.last_updated_utc) + .bind(record.performance as i32) .execute(&mut *tx) .await?; } @@ -78,22 +78,21 @@ pub(crate) async fn update_bonded_gateways( pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query_as!( - GatewayDto, + let items = crate::db::query_as::( r#"SELECT - gw.gateway_identity_key as "gateway_identity_key!", - gw.bonded as "bonded: bool", - gw.performance as "performance!", - gw.self_described as "self_described?", - gw.explorer_pretty_bond as "explorer_pretty_bond?", - gw.last_probe_result as "last_probe_result?", - gw.last_probe_log as "last_probe_log?", - gw.last_testrun_utc as "last_testrun_utc?", - gw.last_updated_utc as "last_updated_utc!", - COALESCE(gd.moniker, "NA") as "moniker!", - COALESCE(gd.website, "NA") as "website!", - COALESCE(gd.security_contact, "NA") as "security_contact!", - COALESCE(gd.details, "NA") as "details!" + gw.gateway_identity_key, + gw.bonded, + gw.performance, + gw.self_described, + gw.explorer_pretty_bond, + gw.last_probe_result, + gw.last_probe_log, + gw.last_testrun_utc, + gw.last_updated_utc, + COALESCE(gd.moniker, 'NA') as moniker, + COALESCE(gd.website, 'NA') as website, + COALESCE(gd.security_contact, 'NA') as security_contact, + COALESCE(gd.details, 'NA') as details FROM gateways gw LEFT JOIN gateway_description gd ON gw.gateway_identity_key = gd.gateway_identity_key @@ -114,29 +113,29 @@ pub(crate) async fn get_all_gateways(pool: &DbPool) -> anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query!( + let items = crate::db::query( r#" SELECT gateway_identity_key FROM gateways WHERE bonded = true - "# + "#, ) .fetch_all(&mut *conn) .await? .into_iter() - .map(|record| record.gateway_identity_key) + .map(|record| record.try_get::("gateway_identity_key").unwrap()) .collect::>(); Ok(items) } pub(crate) async fn insert_gateway_description( - conn: &mut PoolConnection, - identity_key: &str, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + identity_key: String, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO gateway_description ( gateway_identity_key, @@ -153,15 +152,54 @@ pub(crate) async fn insert_gateway_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - identity_key, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(identity_key) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) .map_err(From::from) } + +pub(crate) async fn get_or_create_gateway( + conn: &mut DbConnection, + gateway_identity_key: &str, +) -> anyhow::Result { + // Try to find existing gateway + let existing = crate::db::query("SELECT id FROM gateways WHERE gateway_identity_key = ?") + .bind(gateway_identity_key.to_string()) + .fetch_optional(conn.as_mut()) + .await?; + + if let Some(row) = existing { + return Ok(row.try_get("id")?); + } + + // Create new gateway + tracing::info!("Creating new gateway record for {}", gateway_identity_key); + let now = crate::utils::now_utc().unix_timestamp(); + + let result = crate::db::query( + r#"INSERT INTO gateways ( + gateway_identity_key, + bonded, + performance, + self_described, + last_updated_utc + ) VALUES (?, ?, ?, ?, ?) + RETURNING id"#, + ) + .bind(gateway_identity_key.to_string()) + .bind(true) // Assume bonded since being tested + .bind(0) // Initial performance + .bind("null") + .bind(now) + .fetch_one(conn.as_mut()) + .await?; + + Ok(result.try_get("id")?) +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs index 8ba813ee1f..bf90bff06b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/gateways_stats.rs @@ -6,6 +6,7 @@ use futures_util::TryStreamExt; use time::Date; use tracing::error; +#[cfg(feature = "sqlite")] pub(crate) async fn insert_session_records( pool: &DbPool, records: Vec, @@ -36,6 +37,38 @@ pub(crate) async fn insert_session_records( Ok(()) } +#[cfg(feature = "pg")] +pub(crate) async fn insert_session_records( + pool: &DbPool, + records: Vec, +) -> anyhow::Result<()> { + let mut tx = pool.begin().await?; + for record in records { + sqlx::query!( + "INSERT INTO gateway_session_stats + (gateway_identity_key, node_id, day, + unique_active_clients, session_started, users_hashes, + vpn_sessions, mixnet_sessions, unknown_sessions) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + ON CONFLICT DO NOTHING", + record.gateway_identity_key, + record.node_id, + record.day, + record.unique_active_clients, + record.session_started, + record.users_hashes, + record.vpn_sessions, + record.mixnet_sessions, + record.unknown_sessions, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + + Ok(()) +} + pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; let items = sqlx::query_as( @@ -68,7 +101,8 @@ pub(crate) async fn get_sessions_stats(pool: &DbPool) -> anyhow::Result anyhow::Result<()> { let mut conn = pool.acquire().await?; - sqlx::query!("DELETE FROM gateway_session_stats WHERE day <= ?", cut_off) + crate::db::query("DELETE FROM gateway_session_stats WHERE day <= ?") + .bind(cut_off) .execute(&mut *conn) .await?; Ok(()) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs index fa56a9ef75..248b556c1d 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/misc.rs @@ -6,8 +6,8 @@ use crate::db::{models::NetworkSummary, DbPool}; /// `daily_summary` pub(crate) async fn insert_summaries( pool: &DbPool, - summaries: &[(&str, usize)], - summary: &NetworkSummary, + summaries: Vec<(String, usize)>, + summary: NetworkSummary, last_updated: UtcDateTime, ) -> anyhow::Result<()> { insert_summary(pool, summaries, last_updated).await?; @@ -19,7 +19,7 @@ pub(crate) async fn insert_summaries( async fn insert_summary( pool: &DbPool, - summaries: &[(&str, usize)], + summaries: Vec<(String, usize)>, last_updated: UtcDateTime, ) -> anyhow::Result<()> { let timestamp = last_updated.unix_timestamp(); @@ -27,17 +27,17 @@ async fn insert_summary( for (kind, value) in summaries { let value = value.to_string(); - sqlx::query!( + crate::db::query( "INSERT INTO summary (key, value_json, last_updated_utc) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value_json=excluded.value_json, last_updated_utc=excluded.last_updated_utc;", - kind, - value, - timestamp ) + .bind(kind.clone()) + .bind(value) + .bind(timestamp) .execute(&mut *tx) .await .map_err(|err| { @@ -60,7 +60,7 @@ async fn insert_summary( /// This is not aggregate data, it's a set of latest data points async fn insert_summary_history( pool: &DbPool, - summary: &NetworkSummary, + summary: NetworkSummary, last_updated: UtcDateTime, ) -> anyhow::Result<()> { let mut conn = pool.acquire().await?; @@ -70,17 +70,17 @@ async fn insert_summary_history( let date = datetime_to_only_date_str(last_updated); - sqlx::query!( + crate::db::query( "INSERT INTO summary_history (date, timestamp_utc, value_json) VALUES (?, ?, ?) ON CONFLICT(date) DO UPDATE SET timestamp_utc=excluded.timestamp_utc, value_json=excluded.value_json;", - date, - timestamp, - value_json ) + .bind(date) + .bind(timestamp) + .bind(value_json) .execute(&mut *conn) .await?; diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs index ec8cf5c36c..2f53e52857 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mixnodes.rs @@ -1,13 +1,13 @@ use std::collections::HashSet; use futures_util::TryStreamExt; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use tracing::error; use crate::{ db::{ models::{MixnodeDto, MixnodeRecord}, - DbPool, + DbConnection, DbPool, }, http::models::{DailyStats, Mixnode}, node_scraper::helpers::NodeDescriptionResponse, @@ -20,7 +20,7 @@ pub(crate) async fn update_mixnodes( let mut tx = pool.begin().await?; // mark all as unbonded - sqlx::query!( + crate::db::query( r#"UPDATE mixnodes SET @@ -31,9 +31,9 @@ pub(crate) async fn update_mixnodes( .await?; // existing nodes get updated on insert - for record in mixnodes.iter() { + for record in mixnodes.into_iter() { // https://www.sqlite.org/lang_upsert.html - sqlx::query!( + crate::db::query( "INSERT INTO mixnodes (mix_id, identity_key, bonded, total_stake, host, http_api_port, full_details, @@ -46,17 +46,17 @@ pub(crate) async fn update_mixnodes( full_details=excluded.full_details,self_described=excluded.self_described, last_updated_utc=excluded.last_updated_utc, is_dp_delegatee = excluded.is_dp_delegatee;", - record.mix_id, - record.identity_key, - record.bonded, - record.total_stake, - record.host, - record.http_port, - record.full_details, - record.self_described, - record.last_updated_utc, - record.is_dp_delegatee ) + .bind(record.mix_id as i64) + .bind(record.identity_key) + .bind(record.bonded) + .bind(record.total_stake) + .bind(record.host) + .bind(record.http_port as i32) + .bind(record.full_details) + .bind(record.self_described) + .bind(record.last_updated_utc) + .bind(record.is_dp_delegatee) .execute(&mut *tx) .await?; } @@ -78,10 +78,10 @@ pub(crate) async fn get_all_mixnodes(pool: &DbPool) -> anyhow::Result anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query!( + let items = crate::db::query( r#" SELECT mix_id FROM mixnodes WHERE bonded = true - "# + "#, ) .fetch_all(&mut *conn) .await? .into_iter() - .map(|record| record.mix_id) + .map(|record| record.try_get::("mix_id").unwrap()) .collect::>(); Ok(items) } pub(crate) async fn insert_mixnode_description( - conn: &mut PoolConnection, - mix_id: &i64, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + mix_id: i64, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO mixnode_description ( mix_id, moniker, website, security_contact, details, last_updated_utc @@ -178,13 +178,13 @@ pub(crate) async fn insert_mixnode_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - mix_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(mix_id) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs index 463a5c164d..012d32ae24 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/mod.rs @@ -9,7 +9,8 @@ mod summary; pub(crate) mod testruns; pub(crate) use gateways::{ - get_all_gateways, get_bonded_gateway_id_keys, select_gateway_identity, update_bonded_gateways, + get_all_gateways, get_bonded_gateway_id_keys, get_or_create_gateway, select_gateway_identity, + update_bonded_gateways, }; pub(crate) use gateways_stats::{delete_old_records, get_sessions_stats, insert_session_records}; pub(crate) use misc::insert_summaries; diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs index ec32557d8c..0a0afc1893 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/nym_nodes.rs @@ -4,14 +4,14 @@ use nym_validator_client::{ client::{NodeId, NymNodeDetails}, models::NymNodeDescription, }; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use std::collections::HashMap; use tracing::instrument; use crate::{ db::{ models::{NymNodeDto, NymNodeInsertRecord}, - DbPool, + DbConnection, DbPool, }, node_scraper::helpers::NodeDescriptionResponse, }; @@ -19,21 +19,20 @@ use crate::{ pub(crate) async fn get_all_nym_nodes(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query_as!( - NymNodeDto, + crate::db::query_as::( r#"SELECT node_id, ed25519_identity_pubkey, total_stake, - ip_addresses as "ip_addresses!: serde_json::Value", + ip_addresses, mix_port, x25519_sphinx_pubkey, - node_role as "node_role: serde_json::Value", - supported_roles as "supported_roles: serde_json::Value", - entry as "entry: serde_json::Value", + node_role, + supported_roles, + entry, performance, - self_described as "self_described: serde_json::Value", - bond_info as "bond_info: serde_json::Value" + self_described, + bond_info FROM nym_nodes ORDER BY @@ -56,21 +55,20 @@ pub(crate) async fn get_described_bonded_nym_nodes( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query_as!( - NymNodeDto, + crate::db::query_as::( r#"SELECT node_id, ed25519_identity_pubkey, total_stake, - ip_addresses as "ip_addresses!: serde_json::Value", + ip_addresses, mix_port, x25519_sphinx_pubkey, - node_role as "node_role: serde_json::Value", - supported_roles as "supported_roles: serde_json::Value", - entry as "entry: serde_json::Value", + node_role, + supported_roles, + entry, performance, - self_described as "self_described: serde_json::Value", - bond_info as "bond_info: serde_json::Value" + self_described, + bond_info FROM nym_nodes WHERE @@ -92,7 +90,7 @@ pub(crate) async fn update_nym_nodes( ) -> anyhow::Result { let mut tx = pool.begin().await?; - sqlx::query!( + crate::db::query( "UPDATE nym_nodes SET self_described = NULL, @@ -104,7 +102,7 @@ pub(crate) async fn update_nym_nodes( let inserted = node_records.len(); for record in node_records { // https://www.sqlite.org/lang_upsert.html - sqlx::query!( + crate::db::query( "INSERT INTO nym_nodes (node_id, ed25519_identity_pubkey, total_stake, @@ -129,20 +127,20 @@ pub(crate) async fn update_nym_nodes( performance=excluded.performance, last_updated_utc=excluded.last_updated_utc ;", - record.node_id, - record.ed25519_identity_pubkey, - record.total_stake, - record.ip_addresses, - record.mix_port, - record.x25519_sphinx_pubkey, - record.node_role, - record.supported_roles, - record.entry, - record.self_described, - record.bond_info, - record.performance, - record.last_updated_utc, ) + .bind(record.node_id) + .bind(record.ed25519_identity_pubkey) + .bind(record.total_stake) + .bind(record.ip_addresses) + .bind(record.mix_port) + .bind(record.x25519_sphinx_pubkey) + .bind(record.node_role) + .bind(record.supported_roles) + .bind(record.entry) + .bind(record.self_described) + .bind(record.bond_info) + .bind(record.performance) + .bind(record.last_updated_utc) .execute(&mut *tx) .await .map_err(|e| anyhow::anyhow!("Failed to INSERT node_id={}: {}", record.node_id, e))?; @@ -150,6 +148,10 @@ pub(crate) async fn update_nym_nodes( tx.commit().await?; + tracing::debug!( + "Successfully inserted/updated {} nym_nodes records", + inserted + ); Ok(inserted) } @@ -158,10 +160,10 @@ pub(crate) async fn get_described_node_bond_info( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT node_id, - bond_info as "bond_info: serde_json::Value" + bond_info FROM nym_nodes WHERE @@ -176,11 +178,11 @@ pub(crate) async fn get_described_node_bond_info( records .into_iter() .filter_map(|record| { - record - .bond_info - // only return details for nodes which have details stored - .and_then(|bond_info| serde_json::from_value::(bond_info).ok()) - .map(|res| (record.node_id as NodeId, res)) + let node_id: i32 = record.try_get("node_id").ok()?; + let bond_info: serde_json::Value = record.try_get("bond_info").ok()?; + serde_json::from_value::(bond_info) + .ok() + .map(|res| (node_id as i64 as NodeId, res)) }) .collect::>() }) @@ -192,10 +194,10 @@ pub(crate) async fn get_node_self_description( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT node_id, - self_described as "self_described: serde_json::Value" + self_described FROM nym_nodes WHERE @@ -210,13 +212,11 @@ pub(crate) async fn get_node_self_description( records .into_iter() .filter_map(|record| { - record - .self_described - // only return details for nodes which have details stored - .and_then(|description| { - serde_json::from_value::(description).ok() - }) - .map(|res| (record.node_id as NodeId, res)) + let node_id: i32 = record.try_get("node_id").ok()?; + let self_described: serde_json::Value = record.try_get("self_described").ok()?; + serde_json::from_value::(self_described) + .ok() + .map(|res| (node_id as i64 as NodeId, res)) }) .collect::>() }) @@ -228,7 +228,7 @@ pub(crate) async fn get_bonded_node_description( ) -> anyhow::Result> { let mut conn = pool.acquire().await?; - sqlx::query!( + crate::db::query( r#"SELECT nd.node_id, moniker, @@ -238,7 +238,7 @@ pub(crate) async fn get_bonded_node_description( FROM nym_node_descriptions nd INNER JOIN - nym_nodes + nym_nodes nn on nd.node_id = nn.node_id WHERE bond_info IS NOT NULL "#, @@ -249,14 +249,15 @@ pub(crate) async fn get_bonded_node_description( records .into_iter() .map(|elem| { - let node_id: NodeId = elem.node_id.try_into().unwrap_or_default(); + let node_id: i64 = elem.try_get("node_id").unwrap_or(0); + let node_id: NodeId = node_id.try_into().unwrap_or_default(); ( node_id, NodeDescription { - moniker: elem.moniker.unwrap_or_default(), - website: elem.website.unwrap_or_default(), - security_contact: elem.security_contact.unwrap_or_default(), - details: elem.details.unwrap_or_default(), + moniker: elem.try_get("moniker").unwrap_or_default(), + website: elem.try_get("website").unwrap_or_default(), + security_contact: elem.try_get("security_contact").unwrap_or_default(), + details: elem.try_get("details").unwrap_or_default(), }, ) }) @@ -266,12 +267,12 @@ pub(crate) async fn get_bonded_node_description( } pub(crate) async fn insert_nym_node_description( - conn: &mut PoolConnection, - node_id: &i64, - description: &NodeDescriptionResponse, + conn: &mut DbConnection, + node_id: i64, + description: NodeDescriptionResponse, timestamp: i64, ) -> anyhow::Result<()> { - sqlx::query!( + crate::db::query( r#" INSERT INTO nym_node_descriptions ( node_id, moniker, website, security_contact, details, last_updated_utc @@ -283,13 +284,13 @@ pub(crate) async fn insert_nym_node_description( details = excluded.details, last_updated_utc = excluded.last_updated_utc "#, - node_id, - description.moniker, - description.website, - description.security_contact, - description.details, - timestamp, ) + .bind(node_id) + .bind(description.moniker) + .bind(description.website) + .bind(description.security_contact) + .bind(description.details) + .bind(timestamp) .execute(conn.as_mut()) .await .map(drop) diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs index e436fef8b4..fed7f8dc49 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/packet_stats.rs @@ -59,7 +59,8 @@ pub(crate) async fn batch_store_packet_stats( .map_err(|err| anyhow::anyhow!("Failed to commit: {err}")) } -async fn insert_node_packet_stats_uncommitted( +#[cfg(feature = "sqlite")] +pub(crate) async fn insert_node_packet_stats_uncommitted( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, stats: &NodeStats, @@ -67,35 +68,35 @@ async fn insert_node_packet_stats_uncommitted( ) -> Result<()> { match node_kind { ScrapeNodeKind::LegacyMixnode { mix_id } => { - sqlx::query!( + sqlx::query( r#" INSERT INTO mixnode_packet_stats_raw ( mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped ) VALUES (?, ?, ?, ?, ?) "#, - mix_id, - timestamp_utc, - stats.packets_received, - stats.packets_sent, - stats.packets_dropped, ) + .bind(mix_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) .execute(tx.as_mut()) .await?; } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - sqlx::query!( + sqlx::query( r#" INSERT INTO nym_nodes_packet_stats_raw ( node_id, timestamp_utc, packets_received, packets_sent, packets_dropped ) VALUES (?, ?, ?, ?, ?) "#, - node_id, - timestamp_utc, - stats.packets_received, - stats.packets_sent, - stats.packets_dropped, ) + .bind(node_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) .execute(tx.as_mut()) .await?; } @@ -104,6 +105,53 @@ async fn insert_node_packet_stats_uncommitted( Ok(()) } +#[cfg(feature = "pg")] +pub(crate) async fn insert_node_packet_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + stats: &NodeStats, + timestamp_utc: i64, +) -> Result<()> { + match node_kind { + ScrapeNodeKind::LegacyMixnode { mix_id } => { + sqlx::query( + r#" + INSERT INTO mixnode_packet_stats_raw ( + mix_id, timestamp_utc, packets_received, packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(mix_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + sqlx::query( + r#" + INSERT INTO nym_nodes_packet_stats_raw ( + node_id, timestamp_utc, packets_received, packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5) + "#, + ) + .bind(node_id) + .bind(timestamp_utc) + .bind(stats.packets_received) + .bind(stats.packets_sent) + .bind(stats.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + } + + Ok(()) +} + +#[cfg(feature = "sqlite")] pub(crate) async fn get_raw_node_stats( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, @@ -112,39 +160,37 @@ pub(crate) async fn get_raw_node_stats( // if no packets are found, it's fine to assume 0 because that's also // SQL default value if none provided ScrapeNodeKind::LegacyMixnode { mix_id } => { - sqlx::query_as!( - NodeStats, + sqlx::query_as::<_, NodeStats>( r#" SELECT - COALESCE(packets_received, 0) as "packets_received!: _", - COALESCE(packets_sent, 0) as "packets_sent!: _", - COALESCE(packets_dropped, 0) as "packets_dropped!: _" + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped FROM mixnode_packet_stats_raw WHERE mix_id = ? ORDER BY timestamp_utc DESC LIMIT 1 OFFSET 1 "#, - mix_id ) + .bind(mix_id) .fetch_optional(tx.as_mut()) .await? } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - sqlx::query_as!( - NodeStats, + sqlx::query_as::<_, NodeStats>( r#" SELECT - COALESCE(packets_received, 0) as "packets_received!: _", - COALESCE(packets_sent, 0) as "packets_sent!: _", - COALESCE(packets_dropped, 0) as "packets_dropped!: _" + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped FROM nym_nodes_packet_stats_raw WHERE node_id = ? ORDER BY timestamp_utc DESC LIMIT 1 OFFSET 1 "#, - node_id ) + .bind(node_id) .fetch_optional(tx.as_mut()) .await? } @@ -153,6 +199,55 @@ pub(crate) async fn get_raw_node_stats( Ok(packets) } +#[cfg(feature = "pg")] +pub(crate) async fn get_raw_node_stats( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, +) -> Result> { + let packets = match node_kind { + // if no packets are found, it's fine to assume 0 because that's also + // SQL default value if none provided + ScrapeNodeKind::LegacyMixnode { mix_id } => { + sqlx::query_as::<_, NodeStats>( + r#" + SELECT + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped + FROM mixnode_packet_stats_raw + WHERE mix_id = $1 + ORDER BY timestamp_utc DESC + LIMIT 1 OFFSET 1 + "#, + ) + .bind(mix_id) + .fetch_optional(tx.as_mut()) + .await? + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + sqlx::query_as::<_, NodeStats>( + r#" + SELECT + COALESCE(packets_received, 0) as packets_received, + COALESCE(packets_sent, 0) as packets_sent, + COALESCE(packets_dropped, 0) as packets_dropped + FROM nym_nodes_packet_stats_raw + WHERE node_id = $1 + ORDER BY timestamp_utc DESC + LIMIT 1 OFFSET 1 + "#, + ) + .bind(node_id) + .fetch_optional(tx.as_mut()) + .await? + } + }; + + Ok(packets) +} + +#[cfg(feature = "sqlite")] pub(crate) async fn insert_daily_node_stats_uncommitted( tx: &mut Transaction<'static, sqlx::Sqlite>, node_kind: &ScrapeNodeKind, @@ -161,19 +256,19 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( ) -> Result<()> { match node_kind { ScrapeNodeKind::LegacyMixnode { mix_id } => { - let total_stake = sqlx::query_scalar!( + let total_stake = sqlx::query_scalar::<_, i64>( r#" SELECT total_stake FROM mixnodes WHERE mix_id = ? "#, - mix_id ) + .bind(mix_id) .fetch_one(tx.as_mut()) .await?; - sqlx::query!( + sqlx::query( r#" INSERT INTO mixnode_daily_stats ( mix_id, date_utc, @@ -186,31 +281,31 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent, packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped "#, - mix_id, - date_utc, - total_stake, - packets.packets_received, - packets.packets_sent, - packets.packets_dropped, ) + .bind(mix_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) .execute(tx.as_mut()) .await?; } ScrapeNodeKind::MixingNymNode { node_id } | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { - let total_stake = sqlx::query_scalar!( + let total_stake = sqlx::query_scalar::<_, i64>( r#" SELECT total_stake FROM nym_nodes WHERE node_id = ? "#, - node_id ) + .bind(node_id) .fetch_one(tx.as_mut()) .await?; - sqlx::query!( + sqlx::query( r#" INSERT INTO nym_node_daily_mixing_stats ( node_id, date_utc, @@ -223,13 +318,99 @@ pub(crate) async fn insert_daily_node_stats_uncommitted( packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent, packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped "#, - node_id, - date_utc, - total_stake, - packets.packets_received, - packets.packets_sent, - packets.packets_dropped, ) + .bind(node_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + } + + Ok(()) +} + +#[cfg(feature = "pg")] +pub(crate) async fn insert_daily_node_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + date_utc: &str, + packets: NodeStats, +) -> Result<()> { + match node_kind { + ScrapeNodeKind::LegacyMixnode { mix_id } => { + let total_stake = sqlx::query_scalar::<_, i64>( + r#" + SELECT + total_stake + FROM mixnodes + WHERE mix_id = $1 + "#, + ) + .bind(mix_id) + .fetch_one(tx.as_mut()) + .await?; + + sqlx::query( + r#" + INSERT INTO mixnode_daily_stats ( + mix_id, date_utc, + total_stake, packets_received, + packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(mix_id, date_utc) DO UPDATE SET + total_stake = excluded.total_stake, + packets_received = mixnode_daily_stats.packets_received + excluded.packets_received, + packets_sent = mixnode_daily_stats.packets_sent + excluded.packets_sent, + packets_dropped = mixnode_daily_stats.packets_dropped + excluded.packets_dropped + "#, + ) + .bind(mix_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) + .execute(tx.as_mut()) + .await?; + } + ScrapeNodeKind::MixingNymNode { node_id } + | ScrapeNodeKind::EntryExitNymNode { node_id, .. } => { + let total_stake = sqlx::query_scalar::<_, i64>( + r#" + SELECT + total_stake + FROM nym_nodes + WHERE node_id = $1 + "#, + ) + .bind(node_id) + .fetch_one(tx.as_mut()) + .await?; + + sqlx::query( + r#" + INSERT INTO nym_node_daily_mixing_stats ( + node_id, date_utc, + total_stake, packets_received, + packets_sent, packets_dropped + ) VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(node_id, date_utc) DO UPDATE SET + total_stake = excluded.total_stake, + packets_received = nym_node_daily_mixing_stats.packets_received + excluded.packets_received, + packets_sent = nym_node_daily_mixing_stats.packets_sent + excluded.packets_sent, + packets_dropped = nym_node_daily_mixing_stats.packets_dropped + excluded.packets_dropped + "#, + ) + .bind(node_id) + .bind(date_utc) + .bind(total_stake) + .bind(packets.packets_received) + .bind(packets.packets_sent) + .bind(packets.packets_dropped) .execute(tx.as_mut()) .await?; } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs index 9dbb5d4484..efecd3c83b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/scraper.rs @@ -12,6 +12,7 @@ use crate::{ }; use anyhow::Result; use nym_validator_client::nym_api::SkimmedNode; +use sqlx::Row; pub(crate) async fn get_nodes_for_scraping(pool: &DbPool) -> Result> { let mut nodes_to_scrape = Vec::new(); @@ -68,12 +69,12 @@ pub(crate) async fn get_nodes_for_scraping(pool: &DbPool) -> Result Result Result Result<()> { let timestamp = now_utc().unix_timestamp(); let mut conn = pool.acquire().await?; @@ -138,7 +141,7 @@ pub(crate) async fn insert_scraped_node_description( node_id, identity_key, } => { - insert_nym_node_description(&mut conn, node_id, description, timestamp).await?; + insert_nym_node_description(&mut conn, node_id, description.clone(), timestamp).await?; // for historic reasons (/gateways API), store this info into gateways table as well insert_gateway_description(&mut conn, identity_key, description, timestamp).await?; } diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs index 79a32ee5bb..3695c4481b 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/summary.rs @@ -23,13 +23,12 @@ use crate::{ pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result> { let mut conn = pool.acquire().await?; - let items = sqlx::query_as!( - SummaryHistoryDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - date as "date!", - timestamp_utc as "timestamp_utc!", - value_json as "value_json!" + id, + date, + timestamp_utc, + value_json FROM summary_history ORDER BY date DESC LIMIT 30"#, @@ -51,13 +50,12 @@ pub(crate) async fn get_summary_history(pool: &DbPool) -> anyhow::Result anyhow::Result> { let mut conn = pool.acquire().await?; - Ok(sqlx::query_as!( - SummaryDto, + Ok(crate::db::query_as::( r#"SELECT - key as "key!", - value_json as "value_json!", - last_updated_utc as "last_updated_utc!" - FROM summary"# + key, + value_json, + last_updated_utc + FROM summary"#, ) .fetch(&mut *conn) .try_collect::>() diff --git a/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs b/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs index 469442296c..80dafc070d 100644 --- a/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs +++ b/nym-node-status-api/nym-node-status-api/src/db/queries/testruns.rs @@ -1,40 +1,38 @@ use crate::db::models::{TestRunDto, TestRunStatus}; +use crate::db::DbConnection; use crate::db::DbPool; use crate::http::models::TestrunAssignment; use crate::utils::now_utc; -use sqlx::{pool::PoolConnection, Sqlite}; +use sqlx::Row; use time::Duration; -pub(crate) async fn count_testruns_in_progress( - conn: &mut PoolConnection, -) -> anyhow::Result { - sqlx::query_scalar!( - r#"SELECT - COUNT(id) as "count: i64" - FROM testruns - WHERE - status = ? - "#, - TestRunStatus::InProgress as i64, - ) - .fetch_one(conn.as_mut()) - .await - .map_err(anyhow::Error::from) +pub(crate) async fn count_testruns_in_progress(conn: &mut DbConnection) -> anyhow::Result { + #[cfg(feature = "sqlite")] + let sql = "SELECT COUNT(id) FROM testruns WHERE status = ?"; + + #[cfg(feature = "pg")] + let sql = "SELECT COUNT(id) FROM testruns WHERE status = $1"; + + let count: i64 = sqlx::query_scalar(sql) + .bind(TestRunStatus::InProgress as i32) + .fetch_one(conn.as_mut()) + .await?; + + Ok(count) } pub(crate) async fn get_in_progress_testrun_by_id( - conn: &mut PoolConnection, - testrun_id: i64, + conn: &mut DbConnection, + testrun_id: i32, ) -> anyhow::Result { - sqlx::query_as!( - TestRunDto, + crate::db::query_as::( r#"SELECT - id as "id!", - gateway_id as "gateway_id!", - status as "status!", - created_utc as "created_utc!", - ip_address as "ip_address!", - log as "log!", + id, + gateway_id, + status, + created_utc, + ip_address, + log, last_assigned_utc FROM testruns WHERE @@ -43,12 +41,12 @@ pub(crate) async fn get_in_progress_testrun_by_id( status = ? ORDER BY created_utc LIMIT 1"#, - testrun_id, - TestRunStatus::InProgress as i64, ) + .bind(testrun_id) + .bind(TestRunStatus::InProgress as i32) .fetch_one(conn.as_mut()) .await - .map_err(|e| anyhow::anyhow!("Couldn't retrieve testrun {testrun_id}: {e}")) + .map_err(|e| anyhow::anyhow!("Failed to retrieve in-progress testrun {testrun_id}: {e}")) } pub(crate) async fn update_testruns_assigned_before( @@ -59,7 +57,7 @@ pub(crate) async fn update_testruns_assigned_before( let previous_run = now_utc() - max_age; let cutoff_timestamp = previous_run.unix_timestamp(); - let res = sqlx::query!( + let res = crate::db::query( r#"UPDATE testruns SET @@ -69,10 +67,10 @@ pub(crate) async fn update_testruns_assigned_before( AND last_assigned_utc < ? "#, - TestRunStatus::Queued as i64, - TestRunStatus::InProgress as i64, - cutoff_timestamp ) + .bind(TestRunStatus::Queued as i32) + .bind(TestRunStatus::InProgress as i32) + .bind(cutoff_timestamp) .execute(conn.as_mut()) .await?; @@ -89,36 +87,36 @@ pub(crate) async fn update_testruns_assigned_before( } pub(crate) async fn assign_oldest_testrun( - conn: &mut PoolConnection, + conn: &mut DbConnection, ) -> anyhow::Result> { let now = now_utc().unix_timestamp(); // find & mark as "In progress" in the same transaction to avoid race conditions - let returning = sqlx::query!( + let returning = crate::db::query( r#"UPDATE testruns SET status = ?, last_assigned_utc = ? - WHERE rowid = + WHERE id = ( - SELECT rowid + SELECT id FROM testruns WHERE status = ? ORDER BY created_utc asc LIMIT 1 ) RETURNING - id as "id!", + id, gateway_id "#, - TestRunStatus::InProgress as i64, - now, - TestRunStatus::Queued as i64, ) + .bind(TestRunStatus::InProgress as i32) + .bind(now) + .bind(TestRunStatus::Queued as i32) .fetch_optional(conn.as_mut()) .await?; if let Some(testrun) = returning { - let gw_identity = sqlx::query!( + let gw_identity = crate::db::query( r#" SELECT id, @@ -126,14 +124,14 @@ pub(crate) async fn assign_oldest_testrun( FROM gateways WHERE id = ? LIMIT 1"#, - testrun.gateway_id ) + .bind(testrun.try_get::("gateway_id")?) .fetch_one(conn.as_mut()) .await?; Ok(Some(TestrunAssignment { - testrun_id: testrun.id, - gateway_identity_key: gw_identity.gateway_identity_key, + testrun_id: testrun.try_get("id")?, + gateway_identity_key: gw_identity.try_get("gateway_identity_key")?, assigned_at_utc: now, })) } else { @@ -142,67 +140,146 @@ pub(crate) async fn assign_oldest_testrun( } pub(crate) async fn update_testrun_status( - conn: &mut PoolConnection, - testrun_id: i64, + conn: &mut DbConnection, + testrun_id: i32, status: TestRunStatus, ) -> anyhow::Result<()> { - let status = status as u32; - sqlx::query!( - "UPDATE testruns SET status = ? WHERE id = ?", - status, - testrun_id - ) - .execute(conn.as_mut()) - .await?; + let status = status as i32; + crate::db::query("UPDATE testruns SET status = ? WHERE id = ?") + .bind(status) + .bind(testrun_id) + .execute(conn.as_mut()) + .await?; Ok(()) } pub(crate) async fn update_gateway_last_probe_log( - conn: &mut PoolConnection, - gateway_pk: i64, - log: &str, + conn: &mut DbConnection, + gateway_pk: i32, + log: String, ) -> anyhow::Result<()> { - sqlx::query!( - "UPDATE gateways SET last_probe_log = ? WHERE id = ?", - log, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_probe_log = ? WHERE id = ?") + .bind(log) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(|e| { + anyhow::anyhow!( + "Failed to update probe log for gateway {}: {}", + gateway_pk, + e + ) + }) } pub(crate) async fn update_gateway_last_probe_result( - conn: &mut PoolConnection, - gateway_pk: i64, - result: &str, + conn: &mut DbConnection, + gateway_pk: i32, + result: String, ) -> anyhow::Result<()> { - sqlx::query!( - "UPDATE gateways SET last_probe_result = ? WHERE id = ?", - result, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_probe_result = ? WHERE id = ?") + .bind(result) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(|e| { + anyhow::anyhow!( + "Failed to update probe result for gateway {}: {}", + gateway_pk, + e + ) + }) } pub(crate) async fn update_gateway_score( - conn: &mut PoolConnection, - gateway_pk: i64, + conn: &mut DbConnection, + gateway_pk: i32, ) -> anyhow::Result<()> { let now = now_utc().unix_timestamp(); - sqlx::query!( - "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?", - now, - now, - gateway_pk - ) - .execute(conn.as_mut()) - .await - .map(drop) - .map_err(From::from) + crate::db::query("UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?") + .bind(now) + .bind(now) + .bind(gateway_pk) + .execute(conn.as_mut()) + .await + .map(drop) + .map_err(From::from) +} + +pub(crate) async fn get_testrun_by_id( + conn: &mut DbConnection, + testrun_id: i32, +) -> anyhow::Result { + crate::db::query_as::( + r#"SELECT + id, + gateway_id, + status, + created_utc, + ip_address, + log, + last_assigned_utc + FROM testruns + WHERE id = ?"#, + ) + .bind(testrun_id) + .fetch_one(conn.as_mut()) + .await + .map_err(|e| anyhow::anyhow!("Testrun {} not found: {}", testrun_id, e)) +} + +pub(crate) async fn insert_external_testrun( + conn: &mut DbConnection, + testrun_id: i32, + gateway_id: i32, + assigned_at_utc: i64, +) -> anyhow::Result<()> { + let now = crate::utils::now_utc().unix_timestamp(); + + crate::db::query( + r#"INSERT INTO testruns ( + id, + gateway_id, + status, + created_utc, + last_assigned_utc, + ip_address, + log + ) VALUES (?, ?, ?, ?, ?, ?, ?)"#, + ) + .bind(testrun_id) + .bind(gateway_id) + .bind(TestRunStatus::InProgress as i32) + .bind(now) + .bind(assigned_at_utc) + .bind("external") // Marker for external origin + .bind("") // Empty initial log + .execute(conn.as_mut()) + .await?; + + tracing::debug!( + "Created external testrun {} for gateway {}", + testrun_id, + gateway_id + ); + Ok(()) +} + +pub(crate) async fn update_testrun_status_by_gateway( + conn: &mut DbConnection, + gateway_id: i32, + status: TestRunStatus, +) -> anyhow::Result<()> { + let status = status as i32; + crate::db::query("UPDATE testruns SET status = ? WHERE gateway_id = ? AND status = ?") + .bind(status) + .bind(gateway_id) + .bind(TestRunStatus::InProgress as i32) + .execute(conn.as_mut()) + .await?; + + Ok(()) } diff --git a/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs b/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs new file mode 100644 index 0000000000..e061d654a1 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/query_wrapper.rs @@ -0,0 +1,251 @@ +use sqlx::Database; + +/// Converts SQLite-style ? placeholders to PostgreSQL $N format +#[cfg(feature = "pg")] +fn convert_placeholders(query: &str) -> String { + let mut result = String::with_capacity(query.len() + 10); + let mut placeholder_count = 0; + let mut chars = query.chars(); + let mut in_string: Option = None; + let mut escape_next = false; + + #[allow(clippy::while_let_on_iterator)] + while let Some(ch) = chars.next() { + if escape_next { + result.push(ch); + escape_next = false; + continue; + } + + if let Some(quote_char) = in_string { + result.push(ch); + if ch == quote_char { + in_string = None; + } else if ch == '\\' { + escape_next = true; + } + continue; + } + + match ch { + '\\' => { + result.push(ch); + escape_next = true; + } + '\'' | '"' => { + result.push(ch); + in_string = Some(ch); + } + '?' => { + placeholder_count += 1; + result.push_str(&format!("${placeholder_count}")); + } + _ => { + result.push(ch); + } + } + } + + result +} + +/// Creates a query that automatically handles placeholder conversion +#[cfg(feature = "sqlite")] +pub fn query( + sql: &str, +) -> sqlx::query::Query<'_, sqlx::Sqlite, ::Arguments<'_>> { + sqlx::query(sql) +} + +#[cfg(feature = "pg")] +pub fn query( + sql: &str, +) -> sqlx::query::Query<'static, sqlx::Postgres, ::Arguments<'static>> { + let converted = convert_placeholders(sql); + sqlx::query(Box::leak(converted.into_boxed_str())) +} + +/// Creates a query_as that automatically handles placeholder conversion +#[cfg(feature = "sqlite")] +pub fn query_as( + sql: &str, +) -> sqlx::query::QueryAs<'_, sqlx::Sqlite, O, ::Arguments<'_>> +where + O: for<'r> sqlx::FromRow<'r, ::Row>, +{ + sqlx::query_as(sql) +} + +#[cfg(feature = "pg")] +pub fn query_as( + sql: &str, +) -> sqlx::query::QueryAs< + 'static, + sqlx::Postgres, + O, + ::Arguments<'static>, +> +where + O: for<'r> sqlx::FromRow<'r, ::Row>, +{ + let converted = convert_placeholders(sql); + sqlx::query_as(Box::leak(converted.into_boxed_str())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(feature = "pg")] + fn test_convert_placeholders() { + // Basic conversion + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE id = ?"), + r"SELECT * FROM table WHERE id = $1" + ); + + // Multiple placeholders + assert_eq!( + convert_placeholders(r"INSERT INTO table (a, b, c) VALUES (?, ?, ?)"), + r"INSERT INTO table (a, b, c) VALUES ($1, $2, $3)" + ); + + // Placeholder inside string literal should be ignored + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE name = 'test?' AND id = ?"), + r"SELECT * FROM table WHERE name = 'test?' AND id = $1" + ); + + // Update statement + assert_eq!( + convert_placeholders(r"UPDATE table SET a = ?, b = ? WHERE id = ?"), + r"UPDATE table SET a = $1, b = $2 WHERE id = $3" + ); + + // Test with 10 placeholders (like in update_mixnodes) + assert_eq!( + convert_placeholders(r"INSERT INTO t VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), + r"INSERT INTO t VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)" + ); + + // No placeholders + assert_eq!( + convert_placeholders(r"SELECT * FROM table"), + r"SELECT * FROM table" + ); + + // Placeholder at the beginning + assert_eq!(convert_placeholders(r"? AND ?"), r"$1 AND $2"); + + // Placeholder at the end + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE id = ?"), + r"SELECT * FROM table WHERE id = $1" + ); + + // Adjacent placeholders + assert_eq!( + convert_placeholders(r"VALUES(?,? ,?)"), + r"VALUES($1,$2 ,$3)" + ); + + // Escaped single quote + assert_eq!( + convert_placeholders(r"SELECT * FROM foo WHERE bar = 'it\'s a test' AND baz = ?"), + r"SELECT * FROM foo WHERE bar = 'it\'s a test' AND baz = $1" + ); + + // Double quotes + assert_eq!( + convert_placeholders(r#"SELECT * FROM "table" WHERE "column" = ? AND name = "test?""#), + r#"SELECT * FROM "table" WHERE "column" = $1 AND name = "test?""# + ); + + // Mixed quotes + assert_eq!( + convert_placeholders( + r#"SELECT * FROM table WHERE a = 'single?' AND b = "double?" AND c = ?"# + ), + r#"SELECT * FROM table WHERE a = 'single?' AND b = "double?" AND c = $1"# + ); + + // Escaped backslash before quote + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE path = 'C:\\?' AND id = ?"), + r"SELECT * FROM table WHERE path = 'C:\\?' AND id = $1" + ); + + // Multiple escaped quotes + assert_eq!( + convert_placeholders( + r#"INSERT INTO table (msg) VALUES ('it\'s "complex" test') WHERE id = ?"# + ), + r#"INSERT INTO table (msg) VALUES ('it\'s "complex" test') WHERE id = $1"# + ); + + // Very long query with many placeholders + let long_query = r"INSERT INTO very_long_table_name (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + let expected = r"INSERT INTO very_long_table_name (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)"; + assert_eq!(convert_placeholders(long_query), expected); + + // Query with comments (question marks in comments are also converted) + assert_eq!( + convert_placeholders( + r"-- This is a comment with ? + SELECT * FROM table WHERE id = ? -- another comment ?" + ), + r"-- This is a comment with $1 + SELECT * FROM table WHERE id = $2 -- another comment $3" + ); + + // Multiline strings + assert_eq!( + convert_placeholders( + r"SELECT * FROM table + WHERE description = 'This is a + multiline string with ?' + AND id = ?" + ), + r"SELECT * FROM table + WHERE description = 'This is a + multiline string with ?' + AND id = $1" + ); + + // Complex nested quotes + assert_eq!( + convert_placeholders( + r#"SELECT json_extract(data, '$.items[?(@.name=="test?")]') FROM table WHERE id = ?"# + ), + r#"SELECT json_extract(data, '$.items[?(@.name=="test?")]') FROM table WHERE id = $1"# + ); + + // Empty string + assert_eq!(convert_placeholders(""), ""); + + // Only placeholders + assert_eq!(convert_placeholders("???"), "$1$2$3"); + + // Unicode in strings + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE name = '测试?' AND id = ?"), + r"SELECT * FROM table WHERE name = '测试?' AND id = $1" + ); + + // Test case with backslash at end of string + assert_eq!( + convert_placeholders(r"SELECT * FROM table WHERE path LIKE '%\\' AND id = ?"), + r"SELECT * FROM table WHERE path LIKE '%\\' AND id = $1" + ); + + // Mismatched quotes + assert_eq!( + convert_placeholders(r#"SELECT * FROM foo WHERE bar = "'" AND baz = ?"#), + r#"SELECT * FROM foo WHERE bar = "'" AND baz = $1"# + ); + + // Unmatched quote + assert_eq!(convert_placeholders(r"SELECT 'oops?"), r"SELECT 'oops?"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/db/tests.rs b/nym-node-status-api/nym-node-status-api/src/db/tests.rs new file mode 100644 index 0000000000..a176e6d340 --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/db/tests.rs @@ -0,0 +1,461 @@ +#[cfg(test)] +mod db_tests { + + #[test] + fn test_gateway_dto_try_from() { + let gateway_dto = crate::db::models::GatewayDto { + gateway_identity_key: "test_identity".to_string(), + bonded: true, + performance: 100, + self_described: Some("{\"key\":\"value\"}".to_string()), + explorer_pretty_bond: Some("{\"key\":\"value\"}".to_string()), + last_probe_result: Some("{\"key\":\"value\"}".to_string()), + last_probe_log: Some("log".to_string()), + last_testrun_utc: Some(1672531200), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + website: "website".to_string(), + }; + + let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap(); + + assert_eq!(http_gateway.gateway_identity_key, "test_identity"); + assert!(http_gateway.bonded); + assert_eq!(http_gateway.performance, 100); + assert!(http_gateway.self_described.is_some()); + assert!(http_gateway.explorer_pretty_bond.is_some()); + assert!(http_gateway.last_probe_result.is_some()); + assert_eq!(http_gateway.last_probe_log, Some("log".to_string())); + assert!(http_gateway.last_testrun_utc.is_some()); + assert!(!http_gateway.last_updated_utc.is_empty()); + assert_eq!(http_gateway.description.moniker, "moniker"); + assert_eq!(http_gateway.description.website, "website"); + assert_eq!(http_gateway.description.security_contact, "contact"); + assert_eq!(http_gateway.description.details, "details"); + } + + #[test] + fn test_mixnode_dto_try_from() { + let mixnode_dto = crate::db::models::MixnodeDto { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1000000, + full_details: "{\"key\":\"value\"}".to_string(), + self_described: Some("{\"key\":\"value\"}".to_string()), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + website: "website".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + }; + + let http_mixnode: crate::http::models::Mixnode = mixnode_dto.try_into().unwrap(); + + assert_eq!(http_mixnode.mix_id, 1); + assert!(http_mixnode.bonded); + assert!(!http_mixnode.is_dp_delegatee); + assert_eq!(http_mixnode.total_stake, 1000000); + assert!(http_mixnode.full_details.is_some()); + assert!(http_mixnode.self_described.is_some()); + assert!(!http_mixnode.last_updated_utc.is_empty()); + assert_eq!(http_mixnode.description.moniker, "moniker"); + assert_eq!(http_mixnode.description.website, "website"); + assert_eq!(http_mixnode.description.security_contact, "contact"); + assert_eq!(http_mixnode.description.details, "details"); + } + + #[test] + fn test_summary_history_dto_try_from() { + let summary_history_dto = crate::db::models::SummaryHistoryDto { + id: 1, + date: "2023-01-01".to_string(), + value_json: "{\"key\":\"value\"}".to_string(), + timestamp_utc: 1672531200, + }; + + let summary_history: crate::http::models::SummaryHistory = + summary_history_dto.try_into().unwrap(); + + assert_eq!(summary_history.date, "2023-01-01"); + assert!(summary_history.value_json.is_object()); + assert!(!summary_history.timestamp_utc.is_empty()); + } + + #[test] + fn test_gateway_sessions_record_try_from() { + let gateway_sessions_record = crate::db::models::GatewaySessionsRecord { + gateway_identity_key: "test_identity".to_string(), + node_id: 1, + day: time::macros::date!(2023 - 01 - 01), + unique_active_clients: 10, + session_started: 100, + users_hashes: Some("{\"key\":\"value\"}".to_string()), + vpn_sessions: Some("{\"key\":\"value\"}".to_string()), + mixnet_sessions: Some("{\"key\":\"value\"}".to_string()), + unknown_sessions: Some("{\"key\":\"value\"}".to_string()), + }; + + let session_stats: crate::http::models::SessionStats = + gateway_sessions_record.try_into().unwrap(); + + assert_eq!(session_stats.gateway_identity_key, "test_identity"); + assert_eq!(session_stats.node_id, 1); + assert_eq!(session_stats.day, time::macros::date!(2023 - 01 - 01)); + assert_eq!(session_stats.unique_active_clients, 10); + assert_eq!(session_stats.session_started, 100); + assert!(session_stats.users_hashes.is_some()); + assert!(session_stats.vpn_sessions.is_some()); + assert!(session_stats.mixnet_sessions.is_some()); + assert!(session_stats.unknown_sessions.is_some()); + } + + #[test] + fn test_nym_node_dto_try_from() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk.to_base58_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk.to_base58_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "1.0".to_string(), + self_described: None, + bond_info: None, + }; + + let skimmed_node: nym_validator_client::nym_api::SkimmedNode = + nym_node_dto.try_into().unwrap(); + + assert_eq!(skimmed_node.node_id, 1); + assert_eq!(skimmed_node.ed25519_identity_pubkey, ed25519_pk); + assert_eq!( + skimmed_node.ip_addresses, + vec!["1.1.1.1".parse::().unwrap()] + ); + assert_eq!(skimmed_node.mix_port, 1789); + assert_eq!(skimmed_node.x25519_sphinx_pubkey, x25519_pk); + + match skimmed_node.role { + nym_validator_client::nym_nodes::NodeRole::Mixnode { layer } => assert_eq!(layer, 1), + _ => panic!("Unexpected node role"), + } + assert!(!skimmed_node.supported_roles.entry); + assert!(skimmed_node.supported_roles.mixnode); + assert!(!skimmed_node.supported_roles.exit_nr); + assert!(!skimmed_node.supported_roles.exit_ipr); + assert!(skimmed_node.entry.is_none()); + assert_eq!( + skimmed_node.performance, + nym_contracts_common::Percent::from_percentage_value(100).unwrap() + ); + } +} + +#[test] +fn test_nym_node_insert_record_new() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let skimmed_node = nym_validator_client::nym_api::SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["1.1.1.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }, + supported_roles: nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }, + entry: None, + performance: nym_contracts_common::Percent::from_percentage_value(100).unwrap(), + }; + + let record = crate::db::models::NymNodeInsertRecord::new(skimmed_node, None, None).unwrap(); + + assert_eq!(record.node_id, 1); + assert_eq!( + record.ed25519_identity_pubkey, + ed25519_pk.to_base58_string() + ); + assert_eq!(record.total_stake, 0); + assert_eq!(record.ip_addresses, serde_json::json!(["1.1.1.1"])); + assert_eq!(record.mix_port, 1789); + assert_eq!(record.x25519_sphinx_pubkey, x25519_pk.to_base58_string()); + assert_eq!( + record.node_role, + serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { layer: 1 }) + ); + assert_eq!( + record.supported_roles, + serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }) + ); + assert_eq!(record.performance, "1"); + assert!(record.entry.is_none()); + assert!(record.self_described.is_none()); + assert!(record.bond_info.is_none()); +} + +#[test] +fn test_nym_node_insert_record_with_entry() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let skimmed_node = nym_validator_client::nym_api::SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["1.1.1.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: nym_validator_client::nym_nodes::NodeRole::EntryGateway, + supported_roles: nym_validator_client::models::DeclaredRoles { + entry: true, + mixnode: false, + exit_nr: true, + exit_ipr: false, + }, + entry: Some(nym_validator_client::nym_nodes::BasicEntryInformation { + hostname: Some("gateway.example.com".to_string()), + ws_port: 9001, + wss_port: Some(9002), + }), + performance: nym_contracts_common::Percent::from_percentage_value(99).unwrap(), + }; + + let record = crate::db::models::NymNodeInsertRecord::new(skimmed_node, None, None).unwrap(); + + assert_eq!(record.node_id, 1); + assert_eq!(record.total_stake, 0); // No bond info provided + assert!(record.entry.is_some()); + assert!(record.self_described.is_none()); + assert!(record.bond_info.is_none()); + assert!(record.last_updated_utc > 0); +} + +#[test] +fn test_gateway_dto_with_null_values() { + let gateway_dto = crate::db::models::GatewayDto { + gateway_identity_key: "test_identity".to_string(), + bonded: false, + performance: 0, + self_described: None, + explorer_pretty_bond: None, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: 0, + moniker: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + website: "".to_string(), + }; + + let http_gateway: crate::http::models::Gateway = gateway_dto.try_into().unwrap(); + + assert_eq!(http_gateway.gateway_identity_key, "test_identity"); + assert!(!http_gateway.bonded); + assert_eq!(http_gateway.performance, 0); + assert!(http_gateway.self_described.is_none()); + assert!(http_gateway.explorer_pretty_bond.is_none()); + assert!(http_gateway.last_probe_result.is_none()); + assert!(http_gateway.last_probe_log.is_none()); + assert!(http_gateway.last_testrun_utc.is_none()); + assert_eq!(http_gateway.last_updated_utc, "1970-01-01T00:00:00Z"); +} + +#[test] +fn test_mixnode_dto_with_invalid_json() { + let mixnode_dto = crate::db::models::MixnodeDto { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1000000, + full_details: "invalid json".to_string(), + self_described: Some("also invalid".to_string()), + last_updated_utc: 1672531200, + moniker: "moniker".to_string(), + website: "website".to_string(), + security_contact: "contact".to_string(), + details: "details".to_string(), + }; + + let http_mixnode: crate::http::models::Mixnode = mixnode_dto.try_into().unwrap(); + + // Invalid JSON should result in None + assert!(http_mixnode.full_details.is_none()); + assert_eq!(http_mixnode.self_described, Some(serde_json::Value::Null)); +} + +#[test] +fn test_summary_history_dto_with_invalid_json() { + let summary_history_dto = crate::db::models::SummaryHistoryDto { + id: 1, + date: "2023-01-01".to_string(), + value_json: "not valid json".to_string(), + timestamp_utc: 1672531200, + }; + + let summary_history: crate::http::models::SummaryHistory = + summary_history_dto.try_into().unwrap(); + + assert_eq!(summary_history.date, "2023-01-01"); + // Invalid JSON should result in default (null) + assert!(summary_history.value_json.is_null()); +} + +#[test] +fn test_gateway_sessions_record_with_all_none() { + let gateway_sessions_record = crate::db::models::GatewaySessionsRecord { + gateway_identity_key: "test_identity".to_string(), + node_id: 1, + day: time::macros::date!(2023 - 01 - 01), + unique_active_clients: 0, + session_started: 0, + users_hashes: None, + vpn_sessions: None, + mixnet_sessions: None, + unknown_sessions: None, + }; + + let session_stats: crate::http::models::SessionStats = + gateway_sessions_record.try_into().unwrap(); + + assert_eq!(session_stats.gateway_identity_key, "test_identity"); + assert_eq!(session_stats.node_id, 1); + assert_eq!(session_stats.unique_active_clients, 0); + assert_eq!(session_stats.session_started, 0); + assert!(session_stats.users_hashes.is_none()); + assert!(session_stats.vpn_sessions.is_none()); + assert!(session_stats.mixnet_sessions.is_none()); + assert!(session_stats.unknown_sessions.is_none()); +} + +#[test] +fn test_scraper_node_info_contact_addresses() { + use crate::db::models::{ScrapeNodeKind, ScraperNodeInfo}; + + let node_info = ScraperNodeInfo { + node_kind: ScrapeNodeKind::MixingNymNode { node_id: 123 }, + hosts: vec!["1.1.1.1".to_string(), "example.com".to_string()], + http_api_port: 8080, + }; + + let addresses = node_info.contact_addresses(); + + // Should generate multiple URLs for each host + // Custom port (8080) should be inserted at the beginning + assert!(addresses.contains(&"http://1.1.1.1:8080".to_string())); + assert!(addresses.contains(&"http://example.com:8080".to_string())); + assert!(addresses.contains(&"http://1.1.1.1:8000".to_string())); + assert!(addresses.contains(&"https://1.1.1.1".to_string())); + assert!(addresses.contains(&"http://example.com:8000".to_string())); + // Check that URLs follow the expected pattern + assert!(addresses.len() >= 8); // At least 4 URLs per host +} + +#[test] +fn test_scrape_node_kind_node_id() { + use crate::db::models::ScrapeNodeKind; + + let legacy = ScrapeNodeKind::LegacyMixnode { mix_id: 42 }; + assert_eq!(*legacy.node_id(), 42); + + let mixing = ScrapeNodeKind::MixingNymNode { node_id: 123 }; + assert_eq!(*mixing.node_id(), 123); + + let entry_exit = ScrapeNodeKind::EntryExitNymNode { + node_id: 456, + identity_key: "key123".to_string(), + }; + assert_eq!(*entry_exit.node_id(), 456); +} + +#[test] +fn test_nym_node_dto_with_invalid_keys() { + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: "invalid_base58".to_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: "also_invalid".to_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "1.0".to_string(), + self_described: None, + bond_info: None, + }; + + let result: Result = nym_node_dto.try_into(); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("ed25519_identity_pubkey")); +} + +#[test] +fn test_nym_node_dto_with_invalid_performance() { + let ed25519_pk = nym_crypto::asymmetric::ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = nym_crypto::asymmetric::x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + let nym_node_dto = crate::db::models::NymNodeDto { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk.to_base58_string(), + total_stake: 1000000, + ip_addresses: serde_json::json!(["1.1.1.1"]), + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk.to_base58_string(), + node_role: serde_json::json!(nym_validator_client::nym_nodes::NodeRole::Mixnode { + layer: 1 + }), + supported_roles: serde_json::json!(nym_validator_client::models::DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }), + entry: None, + performance: "invalid_percent".to_string(), + self_described: None, + bond_info: None, + }; + + let result: Result = nym_node_dto.try_into(); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("can't parse Percent")); +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs index 1d8cd93694..5f0f4cbe19 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/dvpn/mod.rs @@ -65,3 +65,36 @@ pub async fn dvpn_gateways( .await, )) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } + + #[test] + fn test_min_node_version_query_deserialization() { + // Test with version + let json = r#"{"min_node_version": "1.2.3"}"#; + let query: MinNodeVersionQuery = serde_json::from_str(json).unwrap(); + assert_eq!(query.min_node_version, Some("1.2.3".to_string())); + + // Test without version + let json_empty = r#"{}"#; + let query_empty: MinNodeVersionQuery = serde_json::from_str(json_empty).unwrap(); + assert_eq!(query_empty.min_node_version, None); + } + + #[test] + fn test_min_supported_version() { + // Test that the lazy static initializes correctly + assert_eq!(MIN_SUPPORTED_VERSION.major, 1); + assert_eq!(MIN_SUPPORTED_VERSION.minor, 6); + assert_eq!(MIN_SUPPORTED_VERSION.patch, 2); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs b/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs index 80e870f198..904bcce05f 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/gateways.rs @@ -57,21 +57,7 @@ async fn gateways_skinny( ) -> HttpResult>> { let db = state.db_pool(); let res = state.cache().get_gateway_list(db).await; - let res: Vec = res - .iter() - .filter(|g| g.bonded) - .map(|g| GatewaySkinny { - gateway_identity_key: g.gateway_identity_key.clone(), - self_described: g.self_described.clone(), - performance: g.performance, - explorer_pretty_bond: g.explorer_pretty_bond.clone(), - last_probe_result: g.last_probe_result.clone(), - last_testrun_utc: g.last_testrun_utc.clone(), - last_updated_utc: g.last_updated_utc.clone(), - routing_score: g.routing_score, - config_score: g.config_score, - }) - .collect(); + let res: Vec = filter_bonded_gateways_to_skinny(res); Ok(Json(PagedResult::paginate(pagination, res))) } @@ -108,3 +94,126 @@ async fn get_gateway( None => Err(HttpError::invalid_input(identity_key)), } } + +// Extract filtering logic for testing +fn filter_bonded_gateways_to_skinny(gateways: Vec) -> Vec { + gateways + .iter() + .filter(|g| g.bonded) + .map(|g| GatewaySkinny { + gateway_identity_key: g.gateway_identity_key.clone(), + self_described: g.self_described.clone(), + performance: g.performance, + explorer_pretty_bond: g.explorer_pretty_bond.clone(), + last_probe_result: g.last_probe_result.clone(), + last_testrun_utc: g.last_testrun_utc.clone(), + last_updated_utc: g.last_updated_utc.clone(), + routing_score: g.routing_score, + config_score: g.config_score, + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::Gateway; + use nym_node_requests::api::v1::node::models::NodeDescription; + + fn create_test_gateway(identity_key: &str, bonded: bool, performance: u8) -> Gateway { + Gateway { + gateway_identity_key: identity_key.to_string(), + bonded, + performance, + self_described: Some(serde_json::json!({"test": "data"})), + explorer_pretty_bond: Some(serde_json::json!({"bond": "info"})), + description: NodeDescription { + moniker: "Test Gateway".to_string(), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_probe_result: Some(serde_json::json!({"result": "ok"})), + last_probe_log: None, + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + } + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_empty_list() { + let gateways = vec![]; + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_all_bonded() { + let gateways = vec![ + create_test_gateway("gw1", true, 90), + create_test_gateway("gw2", true, 95), + create_test_gateway("gw3", true, 85), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 3); + assert_eq!(result[0].gateway_identity_key, "gw1"); + assert_eq!(result[1].gateway_identity_key, "gw2"); + assert_eq!(result[2].gateway_identity_key, "gw3"); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_mixed() { + let gateways = vec![ + create_test_gateway("gw1", true, 90), + create_test_gateway("gw2", false, 95), + create_test_gateway("gw3", true, 85), + create_test_gateway("gw4", false, 100), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 2); + assert_eq!(result[0].gateway_identity_key, "gw1"); + assert_eq!(result[1].gateway_identity_key, "gw3"); + } + + #[test] + fn test_filter_bonded_gateways_to_skinny_none_bonded() { + let gateways = vec![ + create_test_gateway("gw1", false, 90), + create_test_gateway("gw2", false, 95), + ]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_gateway_to_skinny_conversion() { + let gateway = create_test_gateway("test_gw", true, 98); + let gateways = vec![gateway.clone()]; + + let result = filter_bonded_gateways_to_skinny(gateways); + assert_eq!(result.len(), 1); + + let skinny = &result[0]; + assert_eq!(skinny.gateway_identity_key, gateway.gateway_identity_key); + assert_eq!(skinny.performance, gateway.performance); + assert_eq!(skinny.self_described, gateway.self_described); + assert_eq!(skinny.explorer_pretty_bond, gateway.explorer_pretty_bond); + assert_eq!(skinny.last_probe_result, gateway.last_probe_result); + assert_eq!(skinny.last_testrun_utc, gateway.last_testrun_utc); + assert_eq!(skinny.last_updated_utc, gateway.last_updated_utc); + assert_eq!(skinny.routing_score, gateway.routing_score); + assert_eq!(skinny.config_score, gateway.config_score); + } + + #[test] + fn test_identity_key_param_deserialization() { + let json = r#"{"identity_key": "test_key_123"}"#; + let param: IdentityKeyParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.identity_key, "test_key_123"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs index 8703f92830..a3da492dcb 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/metrics/mod.rs @@ -8,3 +8,15 @@ pub(crate) fn routes() -> Router { Router::new().nest("/sessions", sessions::routes()) //eventually add other metrics type } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs index cbcfe1fb85..ed18bcdb6a 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mixnodes.rs @@ -64,17 +64,11 @@ async fn get_mixnodes( Path(MixIdParam { mix_id }): Path, State(state): State, ) -> HttpResult> { - match mix_id.parse::() { - Ok(parsed_mix_id) => { - let res = state.cache().get_mixnodes_list(state.db_pool()).await; - - match res.iter().find(|item| item.mix_id == parsed_mix_id) { - Some(res) => Ok(Json(res.clone())), - None => Err(HttpError::invalid_input(mix_id)), - } - } - Err(_e) => Err(HttpError::invalid_input(mix_id)), - } + find_mixnode_by_id( + &mix_id, + state.cache().get_mixnodes_list(state.db_pool()).await, + ) + .map(Json) } #[derive(Deserialize, IntoParams)] @@ -99,10 +93,7 @@ async fn get_stats( Query(MixStatsQueryParams { offset }): Query, State(state): State, ) -> HttpResult>> { - let offset: usize = offset - .unwrap_or(0) - .try_into() - .map_err(|_| HttpError::invalid_input("Offset must be non-negative"))?; + let offset = validate_offset(offset)?; let last_30_days = state .cache() .get_mixnode_stats(state.db_pool(), offset) @@ -110,3 +101,146 @@ async fn get_stats( Ok(Json(last_30_days)) } + +// Extract business logic for testing +fn find_mixnode_by_id(mix_id: &str, mixnodes: Vec) -> HttpResult { + match mix_id.parse::() { + Ok(parsed_mix_id) => mixnodes + .into_iter() + .find(|item| item.mix_id == parsed_mix_id) + .ok_or_else(|| HttpError::invalid_input(mix_id)), + Err(_e) => Err(HttpError::invalid_input(mix_id)), + } +} + +fn validate_offset(offset: Option) -> HttpResult { + offset + .unwrap_or(0) + .try_into() + .map_err(|_| HttpError::invalid_input("Offset must be non-negative")) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::{DailyStats, Mixnode}; + use nym_node_requests::api::v1::node::models::NodeDescription; + + fn create_test_mixnode(mix_id: u32, is_dp_delegatee: bool) -> Mixnode { + Mixnode { + mix_id, + bonded: true, + is_dp_delegatee, + total_stake: 100000, + full_details: Some(serde_json::json!({"test": "data"})), + self_described: Some(serde_json::json!({"version": "1.0"})), + description: NodeDescription { + moniker: format!("Mixnode {mix_id}"), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + } + } + + #[test] + fn test_routes_construction() { + let router = routes(); + // Just verify the router builds without panic + // Actual route testing would require integration tests + let _routes = router; + } + + #[test] + fn test_find_mixnode_by_id_success() { + let mixnodes = vec![ + create_test_mixnode(1, false), + create_test_mixnode(42, true), + create_test_mixnode(100, false), + ]; + + let result = find_mixnode_by_id("42", mixnodes).unwrap(); + assert_eq!(result.mix_id, 42); + assert!(result.is_dp_delegatee); + } + + #[test] + fn test_find_mixnode_by_id_not_found() { + let mixnodes = vec![create_test_mixnode(1, false), create_test_mixnode(2, false)]; + + let result = find_mixnode_by_id("99", mixnodes); + assert!(result.is_err()); + } + + #[test] + fn test_find_mixnode_by_id_invalid_format() { + let mixnodes = vec![create_test_mixnode(1, false)]; + + // Test various invalid formats + assert!(find_mixnode_by_id("abc", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("12.34", mixnodes.clone()).is_err()); + assert!(find_mixnode_by_id("-1", mixnodes).is_err()); + } + + #[test] + fn test_find_mixnode_by_id_edge_cases() { + let mixnodes = vec![ + create_test_mixnode(0, false), + create_test_mixnode(u32::MAX, false), + ]; + + assert!(find_mixnode_by_id("0", mixnodes.clone()).is_ok()); + assert!(find_mixnode_by_id(&u32::MAX.to_string(), mixnodes).is_ok()); + } + + #[test] + fn test_validate_offset_valid() { + assert_eq!(validate_offset(None).unwrap(), 0); + assert_eq!(validate_offset(Some(0)).unwrap(), 0); + assert_eq!(validate_offset(Some(10)).unwrap(), 10); + assert_eq!(validate_offset(Some(1000)).unwrap(), 1000); + } + + #[test] + fn test_validate_offset_invalid() { + assert!(validate_offset(Some(-1)).is_err()); + assert!(validate_offset(Some(-100)).is_err()); + assert!(validate_offset(Some(i64::MIN)).is_err()); + } + + #[test] + fn test_mix_id_param_deserialization() { + let json = r#"{"mix_id": "123"}"#; + let param: MixIdParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.mix_id, "123"); + } + + #[test] + fn test_mix_stats_query_params_deserialization() { + let json = r#"{"offset": 50}"#; + let params: MixStatsQueryParams = serde_json::from_str(json).unwrap(); + assert_eq!(params.offset, Some(50)); + + let json_empty = r#"{}"#; + let params_empty: MixStatsQueryParams = serde_json::from_str(json_empty).unwrap(); + assert_eq!(params_empty.offset, None); + } + + #[test] + fn test_daily_stats_creation() { + let stats = DailyStats { + date_utc: "2024-01-20".to_string(), + total_packets_received: 1000000, + total_packets_sent: 999000, + total_packets_dropped: 1000, + total_stake: 5000000, + }; + + assert_eq!(stats.total_packets_received, 1000000); + assert_eq!(stats.total_packets_sent, 999000); + assert_eq!(stats.total_packets_dropped, 1000); + assert_eq!(stats.total_stake, 5000000); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs index bd4300275b..bc0a37d34e 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/mod.rs @@ -101,3 +101,38 @@ fn setup_cors() -> CorsLayer { .allow_headers(tower_http::cors::Any) .allow_credentials(false) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_cors_configuration() { + let cors = setup_cors(); + + // Test that CORS is configured (this tests that the function returns a valid CorsLayer) + // The actual CORS behavior would need integration tests + let _layer = cors; // This ensures the cors layer is valid + } + + #[test] + fn test_router_builder_creates_routes() { + let router_builder = RouterBuilder::with_default_routes(); + + // Test that the router builder has the expected structure + // The router itself is private, but we can test that the builder is created + let unfinished_router = router_builder.unfinished_router; + + // Convert to a testable format - this will compile only if routes are properly configured + let _test_router = unfinished_router; + } + + #[test] + fn test_router_builder_finalize() { + let router_builder = RouterBuilder::with_default_routes(); + let finalized = router_builder.finalize_routes(); + + // This tests that finalize_routes produces a valid Router + let _router = finalized; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs index eebff95caf..f829082ed4 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/nym_nodes.rs @@ -86,3 +86,33 @@ async fn node_delegations( .ok_or_else(|| HttpError::no_delegations_for_node(node_id)) .map(Json) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } + + #[test] + fn test_node_id_param_deserialization() { + // Test valid node ID + let json = r#"{"node_id": 42}"#; + let param: NodeIdParam = serde_json::from_str(json).unwrap(); + assert_eq!(param.node_id, 42); + + // Test zero node ID + let json_zero = r#"{"node_id": 0}"#; + let param_zero: NodeIdParam = serde_json::from_str(json_zero).unwrap(); + assert_eq!(param_zero.node_id, 0); + + // Test max node ID + let json_max = format!(r#"{{"node_id": {}}}"#, u32::MAX); + let param_max: NodeIdParam = serde_json::from_str(&json_max).unwrap(); + assert_eq!(param_max.node_id, u32::MAX); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs index caefc6489d..6169784765 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/services/json_path.rs @@ -56,3 +56,87 @@ impl ParsedDetails { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::Gateway; + use nym_node_requests::api::v1::node::models::NodeDescription; + use serde_json::json; + + fn create_mock_gateway(self_described: Option) -> Gateway { + Gateway { + gateway_identity_key: "mock_identity".to_string(), + bonded: true, + performance: 100, + self_described, + explorer_pretty_bond: None, + description: NodeDescription { + moniker: "mock_moniker".to_string(), + website: "https://nymtech.net".to_string(), + security_contact: "security@nymtech.net".to_string(), + details: "mock_details".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2025-01-01T12:00:00Z".to_string(), + routing_score: 1.0, + config_score: 100, + } + } + + #[test] + fn test_parse_json_paths() { + let paths = ParseJsonPaths::new().unwrap(); + assert_eq!( + paths.path_ip_address.to_string(), + "$.host_information.ip_address[0]" + ); + assert_eq!( + paths.path_hostname.to_string(), + "$.host_information.hostname" + ); + assert_eq!( + paths.path_service_provider_client_id.to_string(), + "$.network_requester.address" + ); + } + + #[test] + fn test_parsed_details() { + let paths = ParseJsonPaths::new().unwrap(); + + // Test with full data + let gateway1 = create_mock_gateway(Some(json!({ + "host_information": { + "ip_address": ["1.1.1.1"], + "hostname": "nymtech.net" + }, + "network_requester": { + "address": "client_address.sP" + } + }))); + let details1 = ParsedDetails::new(&paths, &gateway1); + assert_eq!(details1.ip_address, Some("1.1.1.1".to_string())); + assert_eq!(details1.hostname, Some("nymtech.net".to_string())); + assert_eq!( + details1.service_provider_client_id, + Some("client_address.sP".to_string()) + ); + + // Test with missing data + let gateway2 = create_mock_gateway(Some(json!({}))); + let details2 = ParsedDetails::new(&paths, &gateway2); + assert_eq!(details2.ip_address, None); + assert_eq!(details2.hostname, None); + assert_eq!(details2.service_provider_client_id, None); + + // Test with no self_described field + let gateway3 = create_mock_gateway(None); + let details3 = ParsedDetails::new(&paths, &gateway3); + assert_eq!(details3.ip_address, None); + assert_eq!(details3.hostname, None); + assert_eq!(details3.service_provider_client_id, None); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs index 56d2f0c1b2..7d5b5d012c 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/services/mod.rs @@ -40,31 +40,39 @@ pub(crate) struct ServicesQueryParams { )] #[instrument(level = tracing::Level::DEBUG, skip(state))] async fn mixnodes( - Query(ServicesQueryParams { - size, - page, - wss, - hostname, - entry, - }): Query, + Query(params): Query, State(state): State, ) -> HttpResult>> { let db = state.db_pool(); let cache = state.cache(); - let show_only_wss = wss.unwrap_or(false); - let show_only_with_hostname = hostname.unwrap_or(false); - let show_entry_gateways_only = entry.unwrap_or(false); - let paths = ParseJsonPaths::new().map_err(|e| { tracing::error!("Invalidly configured ParseJsonPaths: {e}"); HttpError::internal() })?; let res = cache.get_gateway_list(db).await; - let res: Vec = res + let services = gateway_list_to_services(&paths, res, params.clone()); + + Ok(Json(PagedResult::paginate( + Pagination::new(params.size, params.page), + services, + ))) +} + +// Extract the conversion and filtering logic for testing +fn gateway_list_to_services( + paths: &ParseJsonPaths, + gateways: Vec, + params: ServicesQueryParams, +) -> Vec { + let show_only_wss = params.wss.unwrap_or(false); + let show_only_with_hostname = params.hostname.unwrap_or(false); + let show_entry_gateways_only = params.entry.unwrap_or(false); + + gateways .iter() .map(|g| { - let details = ParsedDetails::new(&paths, g); + let details = ParsedDetails::new(paths, g); let s = Service { gateway_identity_key: g.gateway_identity_key.clone(), @@ -86,28 +94,38 @@ async fn mixnodes( (s, f) }) .filter(|(_, f)| { - let mut keep = f.has_network_requester_sp; - - if show_entry_gateways_only { - keep = true; - } - - if show_only_wss { - keep &= f.has_wss; - } - if show_only_with_hostname { - keep &= f.has_hostname; - } - - keep + apply_service_filters( + f, + show_only_wss, + show_only_with_hostname, + show_entry_gateways_only, + ) }) .map(|(s, _)| s) - .collect(); + .collect() +} - Ok(Json(PagedResult::paginate( - Pagination::new(size, page), - res, - ))) +// Extract filter application logic +fn apply_service_filters( + filter: &ServiceFilter, + show_only_wss: bool, + show_only_with_hostname: bool, + show_entry_gateways_only: bool, +) -> bool { + let mut keep = filter.has_network_requester_sp; + + if show_entry_gateways_only { + keep = true; + } + + if show_only_wss { + keep &= filter.has_wss; + } + if show_only_with_hostname { + keep &= filter.has_hostname; + } + + keep } struct ServiceFilter { @@ -135,3 +153,253 @@ impl ServiceFilter { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::models::{Gateway, Service}; + use nym_node_requests::api::v1::node::models::NodeDescription; + use serde_json::json; + + fn create_test_gateway(key: &str, has_wss: bool, has_network_requester: bool) -> Gateway { + let mut self_described = json!({}); + if has_wss { + self_described["mixnet_websockets"] = json!({ "wss_port": 1234 }); + } + if has_network_requester { + // ParsedDetails looks for these specific paths + self_described["host_information"] = json!({ + "ip_address": ["192.168.1.1"], + "hostname": "test.nymtech.net" + }); + self_described["network_requester"] = json!({ + "address": "client123" + }); + } + + Gateway { + gateway_identity_key: key.to_string(), + bonded: true, + performance: 95, + self_described: Some(self_described), + explorer_pretty_bond: None, + description: NodeDescription { + moniker: "Test Gateway".to_string(), + website: "".to_string(), + security_contact: "".to_string(), + details: "".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + } + } + + #[test] + fn test_service_filter() { + // Test with all fields + let service1 = Service { + gateway_identity_key: "1".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: Some("client_id".to_string()), + ip_address: Some("1.1.1.1".to_string()), + hostname: Some("nymtech.net".to_string()), + mixnet_websockets: Some(json!({ "wss_port": 1234 })), + last_successful_ping_utc: None, + }; + let filter1 = ServiceFilter::new(&service1); + assert!(filter1.has_wss); + assert!(filter1.has_network_requester_sp); + assert!(filter1.has_hostname); + + // Test with no fields + let service2 = Service { + gateway_identity_key: "2".to_string(), + last_updated_utc: "".to_string(), + routing_score: 0.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: None, + last_successful_ping_utc: None, + }; + let filter2 = ServiceFilter::new(&service2); + assert!(!filter2.has_wss); + assert!(!filter2.has_network_requester_sp); + assert!(!filter2.has_hostname); + + // Test with some fields + let service3 = Service { + gateway_identity_key: "3".to_string(), + last_updated_utc: "".to_string(), + routing_score: 0.5, + service_provider_client_id: Some("".to_string()), + ip_address: None, + hostname: Some("nymtech.net".to_string()), + mixnet_websockets: Some(json!({})), + last_successful_ping_utc: None, + }; + let filter3 = ServiceFilter::new(&service3); + assert!(!filter3.has_wss); + assert!(!filter3.has_network_requester_sp); + assert!(filter3.has_hostname); + } + + #[test] + fn test_apply_service_filters() { + let filter_all = ServiceFilter { + has_wss: true, + has_network_requester_sp: true, + has_hostname: true, + }; + + let filter_none = ServiceFilter { + has_wss: false, + has_network_requester_sp: false, + has_hostname: false, + }; + + // Test default behavior (requires network_requester_sp) + assert!(apply_service_filters(&filter_all, false, false, false)); + assert!(!apply_service_filters(&filter_none, false, false, false)); + + // Test entry gateway mode (accepts all) + assert!(apply_service_filters(&filter_all, false, false, true)); + assert!(apply_service_filters(&filter_none, false, false, true)); + + // Test wss filter + assert!(apply_service_filters(&filter_all, true, false, false)); + assert!(!apply_service_filters(&filter_none, true, false, false)); + + // Test hostname filter + assert!(apply_service_filters(&filter_all, false, true, false)); + assert!(!apply_service_filters(&filter_none, false, true, false)); + + // Test combined filters + assert!(apply_service_filters(&filter_all, true, true, false)); + assert!(!apply_service_filters(&filter_none, true, true, false)); + + // Test entry mode does NOT override other filters - it just sets initial keep=true + // But wss and hostname filters can still exclude items + assert!(!apply_service_filters(&filter_none, true, true, true)); + } + + #[test] + fn test_gateway_list_to_services() { + let paths = ParseJsonPaths::new().unwrap(); + let gateways = vec![ + create_test_gateway("gw1", true, true), + create_test_gateway("gw2", false, true), + create_test_gateway("gw3", true, false), + create_test_gateway("gw4", false, false), + ]; + + // Test no filters - only gateways with network_requester pass + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: None, + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw2 have network_requester + assert!(services.iter().any(|s| s.gateway_identity_key == "gw1")); + assert!(services.iter().any(|s| s.gateway_identity_key == "gw2")); + + // Test entry mode (accepts all) + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 4); + + // Test wss filter with entry mode + let params = ServicesQueryParams { + size: None, + page: None, + wss: Some(true), + hostname: None, + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw3 have wss + + // Test hostname filter with entry mode + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: Some(true), + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways.clone(), params); + assert_eq!(services.len(), 2); // gw1 and gw2 have hostname + + // Test combined filters + let params = ServicesQueryParams { + size: None, + page: None, + wss: Some(true), + hostname: Some(true), + entry: Some(true), + }; + let services = gateway_list_to_services(&paths, gateways, params); + assert_eq!(services.len(), 1); // Only gw1 has both + assert_eq!(services[0].gateway_identity_key, "gw1"); + } + + #[test] + fn test_services_query_params_defaults() { + let params = ServicesQueryParams { + size: None, + page: None, + wss: None, + hostname: None, + entry: None, + }; + + assert!(!params.wss.unwrap_or(false)); + assert!(!params.hostname.unwrap_or(false)); + assert!(!params.entry.unwrap_or(false)); + } + + #[test] + fn test_service_filter_edge_cases() { + // Test with null wss_port value + let service = Service { + gateway_identity_key: "test".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: Some("client".to_string()), + ip_address: None, + hostname: None, + mixnet_websockets: Some(json!({ "wss_port": null })), + last_successful_ping_utc: None, + }; + let filter = ServiceFilter::new(&service); + assert!(!filter.has_wss); // null port should be treated as no wss + + // Test with wss_port = 0 + let service2 = Service { + gateway_identity_key: "test2".to_string(), + last_updated_utc: "".to_string(), + routing_score: 1.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: Some(json!({ "wss_port": 0 })), + last_successful_ping_utc: None, + }; + let filter2 = ServiceFilter::new(&service2); + assert!(filter2.has_wss); // Port 0 is still considered as having wss + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/status.rs b/nym-node-status-api/nym-node-status-api/src/http/api/status.rs index 306a7f9c2b..a269272e16 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/status.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/status.rs @@ -44,3 +44,15 @@ async fn build_information( async fn health(State(state): State) -> HttpResult> { Ok(Json(state.health())) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_routes_construction() { + let router = routes(); + // Verify the router builds without panic + let _routes = router; + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs b/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs index 729141509c..3a01ee00d2 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/api/summary.rs @@ -41,3 +41,15 @@ async fn summary_history(State(state): State) -> HttpResult Router { Router::new() .route("/", axum::routing::get(request_testrun)) .route("/:testrun_id", axum::routing::post(submit_testrun)) + .route("/:testrun_id/v2", axum::routing::post(submit_testrun_v2)) .layer(DefaultBodyLimit::max(1024 * 1024 * 5)) } @@ -81,7 +83,7 @@ async fn request_testrun( #[tracing::instrument(level = "debug", skip_all)] async fn submit_testrun( - Path(submitted_testrun_id): Path, + Path(submitted_testrun_id): Path, State(state): State, Json(submitted_result): Json, ) -> HttpResult { @@ -91,7 +93,10 @@ async fn submit_testrun( let mut conn = db .acquire() .await - .map_err(HttpError::internal_with_logging)?; + .map_err(|e| { + tracing::error!(testrun_id = %submitted_testrun_id, error = %e, "Failed to acquire database connection for testrun submission"); + HttpError::internal_with_logging(e) + })?; let assigned_testrun = queries::testruns::get_in_progress_testrun_by_id(&mut conn, submitted_testrun_id) @@ -102,7 +107,10 @@ async fn submit_testrun( submitted_testrun_id, err ); - HttpError::invalid_input("Invalid testrun submitted") + HttpError::invalid_input(format!( + "Testrun {submitted_testrun_id} not found in progress state (may be already completed or expired)" + + )) })?; if Some(submitted_result.payload.assigned_at_utc) != assigned_testrun.last_assigned_utc { tracing::warn!( @@ -110,7 +118,12 @@ async fn submit_testrun( submitted_result.payload.assigned_at_utc, assigned_testrun.last_assigned_utc ); - return Err(HttpError::invalid_input("Invalid testrun submitted")); + return Err(HttpError::invalid_input(format!( + "Testrun {} timestamp mismatch: expected {:?}, got {}", + submitted_testrun_id, + assigned_testrun.last_assigned_utc, + submitted_result.payload.assigned_at_utc + ))); } let gw_identity = db::queries::select_gateway_identity(&mut conn, assigned_testrun.gateway_id) @@ -138,7 +151,7 @@ async fn submit_testrun( queries::testruns::update_gateway_last_probe_log( &mut conn, assigned_testrun.gateway_id, - &submitted_result.payload.probe_result, + submitted_result.payload.probe_result.clone(), ) .await .map_err(HttpError::internal_with_logging)?; @@ -146,7 +159,7 @@ async fn submit_testrun( queries::testruns::update_gateway_last_probe_result( &mut conn, assigned_testrun.gateway_id, - &result, + result, ) .await .map_err(HttpError::internal_with_logging)?; @@ -170,6 +183,72 @@ async fn submit_testrun( Ok(StatusCode::CREATED) } +#[tracing::instrument(level = "debug", skip_all)] +async fn submit_testrun_v2( + Path(submitted_testrun_id): Path, + State(state): State, + Json(submission): Json, +) -> HttpResult { + authenticate(&submission, &state)?; + is_fresh(&submission.payload.assigned_at_utc)?; + + let db = state.db_pool(); + let mut conn = db + .acquire() + .await + .map_err(HttpError::internal_with_logging)?; + + // Try to find existing testrun + match queries::testruns::get_testrun_by_id(&mut conn, submitted_testrun_id).await { + Ok(testrun) => { + // Validate it matches the submission + let gw_identity = queries::select_gateway_identity(&mut conn, testrun.gateway_id) + .await + .map_err(HttpError::internal_with_logging)?; + + if gw_identity != submission.payload.gateway_identity_key { + tracing::warn!( + "Gateway mismatch for testrun {}: expected {}, got {}", + submitted_testrun_id, + gw_identity, + submission.payload.gateway_identity_key + ); + return Err(HttpError::invalid_input("Gateway identity mismatch")); + } + + // Process normally using existing testrun + process_testrun_submission(testrun, submission.payload, &mut conn).await + } + Err(_) => { + // External testrun - create records + tracing::info!( + "Creating external testrun {} for gateway {}", + submitted_testrun_id, + submission.payload.gateway_identity_key + ); + + // Get or create gateway + let gateway_id = + queries::get_or_create_gateway(&mut conn, &submission.payload.gateway_identity_key) + .await + .map_err(HttpError::internal_with_logging)?; + + // Create testrun + queries::testruns::insert_external_testrun( + &mut conn, + submitted_testrun_id, + gateway_id, + submission.payload.assigned_at_utc, + ) + .await + .map_err(HttpError::internal_with_logging)?; + + // Process submission + process_testrun_submission_by_gateway(gateway_id, submission.payload, &mut conn).await + } + } +} + // TODO dz this should be middleware #[tracing::instrument(level = "debug", skip_all)] fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResult<()> { @@ -186,7 +265,7 @@ fn authenticate(request: &impl VerifiableRequest, state: &AppState) -> HttpResul Ok(()) } -static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(1); +static FRESHNESS_CUTOFF: time::Duration = time::Duration::minutes(2); fn is_fresh(request_time: &i64) -> HttpResult<()> { // if a request took longer than N minutes to reach NS API, something is very wrong @@ -197,18 +276,95 @@ fn is_fresh(request_time: &i64) -> HttpResult<()> { let cutoff_timestamp = now_utc() - FRESHNESS_CUTOFF; if request_time < cutoff_timestamp { - warn!("Request older than {}s, rejecting", cutoff_timestamp); + warn!( + "Request time {} is older than cutoff {} ({}s ago), rejecting", + request_time, + cutoff_timestamp, + FRESHNESS_CUTOFF.whole_seconds() + ); return Err(HttpError::unauthorized()); } Ok(()) } fn get_result_from_log(log: &str) -> String { - let re = regex::Regex::new(r"\n\{\s").unwrap(); - let result: Vec<_> = re.splitn(log, 2).collect(); + static RE: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(r"\n\{\s").expect("Invalid regex pattern")); + + let result: Vec<_> = RE.splitn(log, 2).collect(); if result.len() == 2 { let res = format!("{} {}", "{", result[1]).to_string(); return res; } "".to_string() } + +async fn process_testrun_submission( + testrun: TestRunDto, + payload: submit_results_v2::Payload, + conn: &mut DbConnection, +) -> HttpResult { + // Validate timestamp matches + if Some(payload.assigned_at_utc) != testrun.last_assigned_utc { + tracing::warn!( + "Submitted testrun timestamp mismatch: {} != {:?}, rejecting", + payload.assigned_at_utc, + testrun.last_assigned_utc + ); + return Err(HttpError::invalid_input(format!( + "Testrun timestamp mismatch: expected {:?}, got {}", + testrun.last_assigned_utc, payload.assigned_at_utc + ))); + } + + // Process the submission + process_testrun_submission_by_gateway(testrun.gateway_id, payload, conn).await +} + +async fn process_testrun_submission_by_gateway( + gateway_id: i32, + payload: submit_results_v2::Payload, + conn: &mut DbConnection, +) -> HttpResult { + let gw_identity = &payload.gateway_identity_key; + + tracing::debug!( + "Processing testrun submission for gateway {} ({} bytes)", + gw_identity, + payload.probe_result.len(), + ); + + // Update testrun status to complete + queries::testruns::update_testrun_status_by_gateway(conn, gateway_id, TestRunStatus::Complete) + .await + .map_err(HttpError::internal_with_logging)?; + + // Update gateway with results + queries::testruns::update_gateway_last_probe_log( + conn, + gateway_id, + payload.probe_result.clone(), + ) + .await + .map_err(HttpError::internal_with_logging)?; + + let result = get_result_from_log(&payload.probe_result); + queries::testruns::update_gateway_last_probe_result(conn, gateway_id, result) + .await + .map_err(HttpError::internal_with_logging)?; + + queries::testruns::update_gateway_score(conn, gateway_id) + .await + .map_err(HttpError::internal_with_logging)?; + + let assigned_at = unix_timestamp_to_utc_rfc3339(payload.assigned_at_utc); + let now = now_utc(); + tracing::info!( + "✅ Testrun for gateway {} complete (assigned at {}, current time {})", + gw_identity, + assigned_at, + now + ); + + Ok(StatusCode::CREATED) +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/error.rs b/nym-node-status-api/nym-node-status-api/src/http/error.rs index 0282958054..a58a6341e3 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/error.rs @@ -3,6 +3,7 @@ use std::fmt::Display; pub(crate) type HttpResult = Result; +#[derive(Debug)] pub(crate) struct HttpError { message: String, status: axum::http::StatusCode, @@ -55,3 +56,103 @@ impl axum::response::IntoResponse for HttpError { (self.status, self.message).into_response() } } + +#[cfg(test)] +mod tests { + use super::*; + use axum::http::StatusCode; + use axum::response::IntoResponse; + + #[test] + fn test_invalid_input_error() { + let error = HttpError::invalid_input("Invalid request data"); + assert_eq!(error.message, "Invalid request data"); + assert_eq!(error.status, StatusCode::BAD_REQUEST); + + // Test with different input types + let error2 = HttpError::invalid_input(42); + assert_eq!(error2.message, "42"); + + let error3 = HttpError::invalid_input(String::from("Dynamic string")); + assert_eq!(error3.message, "Dynamic string"); + } + + #[test] + fn test_unauthorized_error() { + let error = HttpError::unauthorized(); + assert_eq!( + error.message, + "Make sure your public key is registered with NS API" + ); + assert_eq!(error.status, StatusCode::UNAUTHORIZED); + } + + #[test] + fn test_internal_error() { + let error = HttpError::internal(); + assert_eq!(error.message, "Internal server error"); + assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_internal_with_logging() { + // This would log to error but we can still test the result + let error = HttpError::internal_with_logging("Database connection failed"); + assert_eq!(error.message, "Internal server error"); + assert_eq!(error.status, StatusCode::INTERNAL_SERVER_ERROR); + } + + #[test] + fn test_no_testruns_available() { + let error = HttpError::no_testruns_available(); + assert_eq!(error.message, "No testruns available"); + assert_eq!(error.status, StatusCode::SERVICE_UNAVAILABLE); + } + + #[test] + fn test_no_delegations_for_node() { + let node_id: NodeId = 42; + let error = HttpError::no_delegations_for_node(node_id); + assert_eq!(error.message, "No delegation data for node_id=42"); + assert_eq!(error.status, StatusCode::NOT_FOUND); + + let node_id_2: NodeId = 999; + let error2 = HttpError::no_delegations_for_node(node_id_2); + assert_eq!(error2.message, "No delegation data for node_id=999"); + } + + #[test] + fn test_into_response() { + let error = HttpError::invalid_input("Test error"); + let response = error.into_response(); + + // Extract status from response + let status = response.status(); + assert_eq!(status, StatusCode::BAD_REQUEST); + } + + #[test] + fn test_different_error_types_into_response() { + // Test each error type converts to response properly + let errors = vec![ + HttpError::invalid_input("test"), + HttpError::unauthorized(), + HttpError::internal(), + HttpError::no_testruns_available(), + HttpError::no_delegations_for_node(1), + ]; + + let expected_statuses = vec![ + StatusCode::BAD_REQUEST, + StatusCode::UNAUTHORIZED, + StatusCode::INTERNAL_SERVER_ERROR, + StatusCode::SERVICE_UNAVAILABLE, + StatusCode::NOT_FOUND, + ]; + + for (error, expected_status) in errors.into_iter().zip(expected_statuses) { + let response = error.into_response(); + assert_eq!(response.status(), expected_status); + } + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/mod.rs b/nym-node-status-api/nym-node-status-api/src/http/mod.rs index 758278641a..7ea53a11d7 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/mod.rs @@ -67,3 +67,176 @@ impl Pagination { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + // Use a simple type for testing instead of a custom struct + type TestItem = String; + + #[test] + fn test_pagination_default() { + let pagination = Pagination::default(); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_DEFAULT); + assert_eq!(page, PAGE_DEFAULT); + } + + #[test] + fn test_pagination_new() { + let pagination = Pagination::new(Some(50), Some(3)); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, 50); + assert_eq!(page, 3); + } + + #[test] + fn test_pagination_max_size_limit() { + let pagination = Pagination::new(Some(1000), Some(0)); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_MAX); + assert_eq!(page, 0); + } + + #[test] + fn test_pagination_none_values() { + let pagination = Pagination::new(None, None); + let (size, page) = pagination.into_inner_values(); + assert_eq!(size, SIZE_DEFAULT); + assert_eq!(page, PAGE_DEFAULT); + } + + #[test] + fn test_paged_result_empty_list() { + let items: Vec = vec![]; + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 0); + assert_eq!(result.items.len(), 0); + } + + #[test] + fn test_paged_result_single_page() { + let items: Vec = (0..5).map(|i| format!("Item {i}")).collect(); + + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items.clone()); + + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 5); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 0"); + assert_eq!(result.items[4], "Item 4"); + } + + #[test] + fn test_paged_result_multiple_pages() { + let items: Vec = (0..25).map(|i| format!("Item {i}")).collect(); + + // First page + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items.clone()); + assert_eq!(result.page, 0); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 10); + assert_eq!(result.items[0], "Item 0"); + assert_eq!(result.items[9], "Item 9"); + + // Second page + let pagination = Pagination::new(Some(10), Some(1)); + let result = PagedResult::paginate(pagination, items.clone()); + assert_eq!(result.page, 1); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 10); + assert_eq!(result.items[0], "Item 10"); + assert_eq!(result.items[9], "Item 19"); + + // Last page (partial) + let pagination = Pagination::new(Some(10), Some(2)); + let result = PagedResult::paginate(pagination, items); + assert_eq!(result.page, 2); + assert_eq!(result.size, 10); + assert_eq!(result.total, 25); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 20"); + assert_eq!(result.items[4], "Item 24"); + } + + #[test] + fn test_paged_result_page_out_of_bounds() { + let items: Vec = (0..15).map(|i| format!("Item {i}")).collect(); + + // Page way out of bounds + let pagination = Pagination::new(Some(10), Some(10)); + let result = PagedResult::paginate(pagination, items); + + // Should adjust to last valid page + assert_eq!(result.page, 1); // 15 items / 10 per page = 1 (0-indexed) + assert_eq!(result.size, 10); + assert_eq!(result.total, 15); + assert_eq!(result.items.len(), 5); + assert_eq!(result.items[0], "Item 10"); + } + + #[test] + fn test_paged_result_exact_page_boundary() { + let items: Vec = (0..20).map(|i| format!("Item {i}")).collect(); + + // Exactly 2 pages of 10 items each + let pagination = Pagination::new(Some(10), Some(1)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 1); + assert_eq!(result.size, 10); + assert_eq!(result.total, 20); + assert_eq!(result.items.len(), 10); + } + + #[test] + fn test_paged_result_single_item_per_page() { + let items: Vec = (0..5).map(|i| format!("Item {i}")).collect(); + + let pagination = Pagination::new(Some(1), Some(3)); + let result = PagedResult::paginate(pagination, items); + + assert_eq!(result.page, 3); + assert_eq!(result.size, 1); + assert_eq!(result.total, 5); + assert_eq!(result.items.len(), 1); + assert_eq!(result.items[0], "Item 3"); + } + + #[test] + fn test_pagination_serialization() { + let pagination = Pagination::new(Some(25), Some(2)); + let json = serde_json::to_string(&pagination).unwrap(); + assert!(json.contains("\"size\":25")); + assert!(json.contains("\"page\":2")); + + let deserialized: Pagination = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.size, Some(25)); + assert_eq!(deserialized.page, Some(2)); + } + + #[test] + fn test_paged_result_serialization() { + let items = vec!["First".to_string(), "Second".to_string()]; + let pagination = Pagination::new(Some(10), Some(0)); + let result = PagedResult::paginate(pagination, items); + + let json = serde_json::to_string(&result).unwrap(); + assert!(json.contains("\"page\":0")); + assert!(json.contains("\"size\":10")); + assert!(json.contains("\"total\":2")); + assert!(json.contains("\"items\":")); + assert!(json.contains("First")); + assert!(json.contains("Second")); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/http/models.rs b/nym-node-status-api/nym-node-status-api/src/http/models.rs index d99da2c698..f665067814 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/models.rs @@ -236,6 +236,7 @@ fn to_percent(performance: u8) -> String { #[cfg(test)] mod test { use super::*; + use std::str::FromStr; #[test] fn to_percent_should_work() { @@ -246,6 +247,241 @@ mod test { assert_eq!(expected, to_percent(starting)); } } + + #[test] + fn to_percent_edge_cases() { + // Test edge cases + assert_eq!("0.00", to_percent(0)); + assert_eq!("1.00", to_percent(100)); + assert_eq!("2.55", to_percent(255)); // Over 100% + } + + #[test] + fn node_delegation_from_conversion() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: 42, + amount: Coin { + denom: "unym".to_string(), + amount: Uint128::new(1000000), + }, + cumulative_reward_ratio: Decimal::from_str("1.23456789").unwrap(), + height: 12345, + owner: Addr::unchecked("owner1"), + proxy: Some(Addr::unchecked("proxy1")), + }; + + let node_delegation: NodeDelegation = delegation.clone().into(); + + assert_eq!(node_delegation.amount.denom, "unym"); + assert_eq!(node_delegation.amount.amount, Uint128::new(1000000)); + assert_eq!(node_delegation.cumulative_reward_ratio, "1.23456789"); + assert_eq!(node_delegation.block_height, 12345); + assert_eq!(node_delegation.owner, Addr::unchecked("owner1")); + assert_eq!(node_delegation.proxy, Some(Addr::unchecked("proxy1"))); + } + + #[test] + fn node_delegation_from_conversion_no_proxy() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: 0, + amount: Coin { + denom: "uatom".to_string(), + amount: Uint128::new(0), + }, + cumulative_reward_ratio: Decimal::zero(), + height: 0, + owner: Addr::unchecked("owner2"), + proxy: None, + }; + + let node_delegation: NodeDelegation = delegation.into(); + + assert_eq!(node_delegation.amount.denom, "uatom"); + assert_eq!(node_delegation.amount.amount, Uint128::new(0)); + assert_eq!(node_delegation.cumulative_reward_ratio, "0"); + assert_eq!(node_delegation.block_height, 0); + assert_eq!(node_delegation.owner, Addr::unchecked("owner2")); + assert_eq!(node_delegation.proxy, None); + } + + #[test] + fn node_delegation_from_conversion_max_values() { + use cosmwasm_std::Uint128; + + let delegation = nym_mixnet_contract_common::Delegation { + node_id: u32::MAX, + amount: Coin { + denom: "test".to_string(), + amount: Uint128::MAX, + }, + cumulative_reward_ratio: Decimal::from_str("999999999.999999999").unwrap(), + height: u64::MAX, + owner: Addr::unchecked("owner3"), + proxy: Some(Addr::unchecked("proxy3")), + }; + + let node_delegation: NodeDelegation = delegation.into(); + + assert_eq!(node_delegation.amount.amount, Uint128::MAX); + assert_eq!( + node_delegation.cumulative_reward_ratio, + "999999999.999999999" + ); + assert_eq!(node_delegation.block_height, u64::MAX); + } + + #[test] + fn location_struct_creation() { + let location = Location { + two_letter_iso_country_code: "US".to_string(), + latitude: 40.7128, + longitude: -74.0060, + }; + + assert_eq!(location.two_letter_iso_country_code, "US"); + assert_eq!(location.latitude, 40.7128); + assert_eq!(location.longitude, -74.0060); + } + + #[test] + fn location_extreme_coordinates() { + // Test extreme coordinates + let north_pole = Location { + two_letter_iso_country_code: "XX".to_string(), + latitude: 90.0, + longitude: 0.0, + }; + + let south_pole = Location { + two_letter_iso_country_code: "AQ".to_string(), + latitude: -90.0, + longitude: 0.0, + }; + + let date_line = Location { + two_letter_iso_country_code: "FJ".to_string(), + latitude: -17.0, + longitude: 180.0, + }; + + assert_eq!(north_pole.latitude, 90.0); + assert_eq!(south_pole.latitude, -90.0); + assert_eq!(date_line.longitude, 180.0); + } + + #[test] + fn build_information_creation() { + let build_info = BuildInformation { + build_version: "1.2.3".to_string(), + commit_branch: "main".to_string(), + commit_sha: "abcdef123456".to_string(), + }; + + assert_eq!(build_info.build_version, "1.2.3"); + assert_eq!(build_info.commit_branch, "main"); + assert_eq!(build_info.commit_sha, "abcdef123456"); + } + + #[test] + fn daily_stats_creation() { + let stats = DailyStats { + date_utc: "2024-01-20".to_string(), + total_packets_received: 1_000_000, + total_packets_sent: 999_000, + total_packets_dropped: 1_000, + total_stake: 5_000_000, + }; + + assert_eq!(stats.date_utc, "2024-01-20"); + assert_eq!(stats.total_packets_received, 1_000_000); + assert_eq!(stats.total_packets_sent, 999_000); + assert_eq!(stats.total_packets_dropped, 1_000); + assert_eq!(stats.total_stake, 5_000_000); + } + + #[test] + fn daily_stats_negative_values() { + // Test with edge case values + let stats = DailyStats { + date_utc: "".to_string(), + total_packets_received: i64::MAX, + total_packets_sent: 0, + total_packets_dropped: -1, // Should this be allowed? + total_stake: i64::MIN, + }; + + assert_eq!(stats.date_utc, ""); + assert_eq!(stats.total_packets_received, i64::MAX); + assert_eq!(stats.total_packets_sent, 0); + assert_eq!(stats.total_packets_dropped, -1); + assert_eq!(stats.total_stake, i64::MIN); + } + + #[test] + fn gateway_skinny_creation() { + let gateway = GatewaySkinny { + gateway_identity_key: "gateway123".to_string(), + self_described: Some(serde_json::json!({"test": "value"})), + explorer_pretty_bond: None, + last_probe_result: Some(serde_json::json!({"status": "ok"})), + last_testrun_utc: Some("2024-01-20T10:00:00Z".to_string()), + last_updated_utc: "2024-01-20T11:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + performance: 98, + }; + + assert_eq!(gateway.gateway_identity_key, "gateway123"); + assert!(gateway.self_described.is_some()); + assert!(gateway.explorer_pretty_bond.is_none()); + assert_eq!(gateway.performance, 98); + assert_eq!(gateway.routing_score, 0.95); + } + + #[test] + fn service_creation_with_all_fields() { + let service = Service { + gateway_identity_key: "gw123".to_string(), + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + routing_score: 0.85, + service_provider_client_id: Some("client123".to_string()), + ip_address: Some("192.168.1.1".to_string()), + hostname: Some("gateway.example.com".to_string()), + mixnet_websockets: Some(serde_json::json!({"port": 8080})), + last_successful_ping_utc: Some("2024-01-20T09:55:00Z".to_string()), + }; + + assert_eq!(service.gateway_identity_key, "gw123"); + assert_eq!(service.routing_score, 0.85); + assert_eq!(service.ip_address, Some("192.168.1.1".to_string())); + assert_eq!(service.hostname, Some("gateway.example.com".to_string())); + } + + #[test] + fn service_creation_minimal() { + let service = Service { + gateway_identity_key: "gw456".to_string(), + last_updated_utc: "2024-01-20T10:00:00Z".to_string(), + routing_score: 0.0, + service_provider_client_id: None, + ip_address: None, + hostname: None, + mixnet_websockets: None, + last_successful_ping_utc: None, + }; + + assert_eq!(service.gateway_identity_key, "gw456"); + assert_eq!(service.routing_score, 0.0); + assert!(service.service_provider_client_id.is_none()); + assert!(service.ip_address.is_none()); + assert!(service.hostname.is_none()); + assert!(service.mixnet_websockets.is_none()); + assert!(service.last_successful_ping_utc.is_none()); + } } #[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] diff --git a/nym-node-status-api/nym-node-status-api/src/http/state.rs b/nym-node-status-api/nym-node-status-api/src/http/state.rs index 8d197f9a07..03f48cb0d6 100644 --- a/nym-node-status-api/nym-node-status-api/src/http/state.rs +++ b/nym-node-status-api/nym-node-status-api/src/http/state.rs @@ -7,7 +7,7 @@ use nym_crypto::asymmetric::ed25519::PublicKey; use nym_mixnet_contract_common::NodeId; use nym_validator_client::nym_api::SkimmedNode; use semver::Version; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{collections::HashMap, sync::Arc, time::Duration}; use time::UtcDateTime; use tokio::sync::RwLock; @@ -181,14 +181,24 @@ impl HttpCache { // the key is missing so populate it tracing::trace!("No gateways in cache, refreshing cache from DB..."); - let gateways = crate::db::queries::get_all_gateways(db) - .await - .unwrap_or_default(); + let gateways = match crate::db::queries::get_all_gateways(db).await { + Ok(gws) => { + tracing::info!("Successfully fetched {} gateways from database", gws.len()); + if !gws.is_empty() { + self.upsert_gateway_list(gws.clone()).await; + } + gws + } + Err(err) => { + tracing::error!("CRITICAL: Failed to fetch gateways from database: {err}"); + panic!( + "Cannot read gateways table - this should never happen! Error: {err}" + ); + } + }; if gateways.is_empty() { tracing::warn!("Database: gateway list is empty"); - } else { - self.upsert_gateway_list(gateways.clone()).await; } gateways @@ -227,60 +237,64 @@ impl HttpCache { read_lock.clone() } None => { - tracing::trace!("No gateways (dVPN) in cache, refreshing from DB..."); + tracing::info!("No gateways (dVPN) in cache, refreshing from DB..."); let gateways = self.get_gateway_list(db).await; + tracing::info!("Found {} gateways in database", gateways.len()); let started_with = gateways.len(); - let Ok(skimmed_nodes) = crate::db::queries::get_described_bonded_nym_nodes(db) + let skimmed_nodes = match crate::db::queries::get_described_bonded_nym_nodes(db) .await - .map(|records| { - records - .into_iter() - .filter_map(|dto| { - SkimmedNode::try_from(dto) - .inspect_err(|err| { - error!("Failed to read SkimmedNode from DB: {err}") - }) - .ok() - }) - .map(|skimmed_node| { - ( - skimmed_node.ed25519_identity_pubkey.to_base58_string(), - skimmed_node, - ) - }) - .collect::>() - }) - .inspect_err(|err| { - // this would fail only in case of internal error: log and return gracefully - error!("Failed to get nym_nodes from DB: {err}"); - }) - else { - return Vec::new(); + { + Ok(records) => { + let mut nodes = HashMap::new(); + for dto in records { + match SkimmedNode::try_from(dto) { + Ok(skimmed_node) => { + let key = + skimmed_node.ed25519_identity_pubkey.to_base58_string(); + nodes.insert(key, skimmed_node); + } + Err(err) => { + error!("CRITICAL: Failed to convert NymNodeDto to SkimmedNode: {err}"); + panic!("Cannot convert database record to SkimmedNode - this should never happen! Error: {err}"); + } + } + } + nodes + } + Err(err) => { + error!("CRITICAL: Failed to query nym_nodes from database: {err}"); + panic!( + "Cannot read nym_nodes table - database connection issue? Error: {err}" + ); + } }; let res_gws = gateways - .into_iter() + .iter() .filter(|gw| gw.bonded) .filter_map(|gw| match skimmed_nodes.get(&gw.gateway_identity_key) { Some(skimmed_node) => Some((gw, skimmed_node)), None => { - warn!( - "No SkimmedNode data found for GW, identity_key={}", + error!( + "CRITICAL: Gateway {} exists in gateways table but not in nym_nodes table! This should not happen.", gw.gateway_identity_key ); None } }) .filter_map( - |(gw, skimmed_node)| match DVpnGateway::new(gw, skimmed_node) { + |(gw, skimmed_node)| match DVpnGateway::new(gw.clone(), skimmed_node) { Ok(gw) => Some(gw), Err(err) => { error!( - "Failed to convert GW node_id={} to dVPN form: {}", - skimmed_node.node_id, err + "CRITICAL: Failed to create DVpnGateway for node_id={}, identity_key={}: {}", + skimmed_node.node_id, + skimmed_node.ed25519_identity_pubkey.to_base58_string(), + err ); + // Don't panic here as this might be due to missing fields, but log it loudly None } }, @@ -315,9 +329,26 @@ impl HttpCache { }) .collect::>(); + let bonded_count = gateways.iter().filter(|gw| gw.bonded).count(); + tracing::info!( + "DVpn gateway filtering: {} total gateways, {} bonded, {} nym_nodes, {} final DVpn gateways", + started_with, + bonded_count, + skimmed_nodes.len(), + res_gws.len() + ); + if res_gws.is_empty() && started_with > 0 { - tracing::warn!("Started with {}, got 0 gateways", started_with); + tracing::error!( + "CRITICAL: Started with {} gateways but got 0 DVpn gateways! Min version: {}", + started_with, + min_node_version + ); } else { + tracing::info!( + "Successfully loaded {} DVpn gateways into cache", + res_gws.len() + ); self.upsert_dvpn_gateway_list(res_gws.clone()).await; } @@ -663,7 +694,7 @@ impl BinaryInfo { } } -#[derive(Serialize, ToSchema)] +#[derive(Serialize, ToSchema, Deserialize)] pub(crate) struct HealthInfo { - uptime: i64, + pub(crate) uptime: i64, } diff --git a/nym-node-status-api/nym-node-status-api/src/main.rs b/nym-node-status-api/nym-node-status-api/src/main.rs index 5abe8712cf..60ebb3bb06 100644 --- a/nym-node-status-api/nym-node-status-api/src/main.rs +++ b/nym-node-status-api/nym-node-status-api/src/main.rs @@ -5,6 +5,12 @@ use nym_task::signal::wait_for_signal; use nym_validator_client::nyxd::NyxdClient; use std::sync::Arc; +#[cfg(all(feature = "sqlite", feature = "pg"))] +compile_error!("Features 'sqlite' and 'pg' are mutually exclusive"); + +#[cfg(not(any(feature = "sqlite", feature = "pg")))] +compile_error!("Either 'sqlite' or 'pg' feature must be enabled"); + mod cli; mod db; mod http; diff --git a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs index 42f96647e3..99cff49a06 100644 --- a/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs +++ b/nym-node-status-api/nym-node-status-api/src/metrics_scraper/error.rs @@ -16,3 +16,124 @@ pub enum NodeScraperError { #[error("node {node_id} with host '{host}' doesn't seem to expose its declared http port nor any of the standard API ports, i.e.: 80, 443 or {}", DEFAULT_NYM_NODE_HTTP_PORT)] NoHttpPortsAvailable { host: String, node_id: NodeId }, } + +#[cfg(test)] +mod tests { + use super::*; + use std::error::Error; + + #[test] + fn test_malformed_host_error() { + // Create a generic error to test with + let source_error = NymNodeApiClientError::GenericRequestFailure("Invalid URL".to_string()); + let error = NodeScraperError::MalformedHost { + host: "invalid-host:abc".to_string(), + node_id: 42, + source: source_error, + }; + + // Test error message formatting + let error_msg = error.to_string(); + assert!(error_msg.contains("node 42")); + assert!(error_msg.contains("invalid-host:abc")); + assert!(error_msg.contains("malformed host information")); + + // Test that source error is accessible + assert!(error.source().is_some()); + } + + #[test] + fn test_malformed_host_error_edge_cases() { + // Test with empty host + let error = NodeScraperError::MalformedHost { + host: "".to_string(), + node_id: 0, + source: NymNodeApiClientError::NotFound, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("node 0")); + + // Test with very long host + let long_host = "x".repeat(1000); + let error = NodeScraperError::MalformedHost { + host: long_host.clone(), + node_id: u32::MAX, + source: NymNodeApiClientError::GenericRequestFailure("Too long".to_string()), + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains(&format!("node {}", u32::MAX))); + assert!(error_msg.contains(&long_host)); + } + + #[test] + fn test_no_http_ports_available_error() { + let error = NodeScraperError::NoHttpPortsAvailable { + host: "example.com".to_string(), + node_id: 123, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("node 123")); + assert!(error_msg.contains("example.com")); + assert!(error_msg.contains("doesn't seem to expose its declared http port")); + assert!(error_msg.contains("80, 443 or")); + assert!(error_msg.contains(&DEFAULT_NYM_NODE_HTTP_PORT.to_string())); + + // This error type has no source + assert!(error.source().is_none()); + } + + #[test] + fn test_no_http_ports_special_characters() { + // Test with host containing special characters + let error = NodeScraperError::NoHttpPortsAvailable { + host: "test-node_123.example.com:8080".to_string(), + node_id: 999, + }; + + let error_msg = error.to_string(); + assert!(error_msg.contains("test-node_123.example.com:8080")); + } + + #[test] + fn test_error_different_sources() { + // Test with different NymNodeApiClientError variants + let not_found_error = NymNodeApiClientError::NotFound; + let error1 = NodeScraperError::MalformedHost { + host: "host1".to_string(), + node_id: 1, + source: not_found_error, + }; + + let generic_error = NymNodeApiClientError::GenericRequestFailure("404 error".to_string()); + let error2 = NodeScraperError::MalformedHost { + host: "host2".to_string(), + node_id: 2, + source: generic_error, + }; + + // Both should format differently based on their source + assert!(error1.to_string().contains("host1")); + assert!(error2.to_string().contains("host2")); + } + + #[test] + fn test_error_trait_implementation() { + // Test that NodeScraperError implements std::error::Error properly + let error = NodeScraperError::NoHttpPortsAvailable { + host: "test.com".to_string(), + node_id: 42, + }; + + // Can be used as dyn Error + let _error_ref: &dyn std::error::Error = &error; + + // Display trait is implemented + let _display = format!("{error}"); + + // Debug trait is implemented + let _debug = format!("{error:?}"); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs index 0711299088..2bd1745a68 100644 --- a/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs +++ b/nym-node-status-api/nym-node-status-api/src/monitor/mod.rs @@ -252,17 +252,23 @@ impl Monitor { // let nodes_summary = vec![ - (NYMNODE_COUNT, nym_node_count), - (ASSIGNED_MIXING_COUNT, assigned_mixing_count), - (MIXNODES_LEGACY_COUNT, count_legacy_mixnodes), - (NYMNODES_DESCRIBED_COUNT, described_nodes.len()), - (GATEWAYS_BONDED_COUNT, count_bonded_gateways), - (ASSIGNED_ENTRY_COUNT, assigned_entry_count), - (ASSIGNED_EXIT_COUNT, assigned_exit_count), + (NYMNODE_COUNT.to_string(), nym_node_count), + (ASSIGNED_MIXING_COUNT.to_string(), assigned_mixing_count), + (MIXNODES_LEGACY_COUNT.to_string(), count_legacy_mixnodes), + (NYMNODES_DESCRIBED_COUNT.to_string(), described_nodes.len()), + (GATEWAYS_BONDED_COUNT.to_string(), count_bonded_gateways), + (ASSIGNED_ENTRY_COUNT.to_string(), assigned_entry_count), + (ASSIGNED_EXIT_COUNT.to_string(), assigned_exit_count), // TODO dz doesn't make sense, could make sense with historical Nym // Nodes if we really need this data - (MIXNODES_HISTORICAL_COUNT, all_historical_mixnodes), - (GATEWAYS_HISTORICAL_COUNT, all_historical_gateways), + ( + MIXNODES_HISTORICAL_COUNT.to_string(), + all_historical_mixnodes, + ), + ( + GATEWAYS_HISTORICAL_COUNT.to_string(), + all_historical_gateways, + ), ]; let last_updated = now_utc(); @@ -295,7 +301,8 @@ impl Monitor { }, }; - queries::insert_summaries(&pool, &nodes_summary, &network_summary, last_updated).await?; + queries::insert_summaries(&pool, nodes_summary.clone(), network_summary, last_updated) + .await?; let mut log_lines: Vec = vec![]; for (key, value) in nodes_summary.iter() { @@ -495,15 +502,31 @@ impl Monitor { async fn historical_count(pool: &DbPool) -> anyhow::Result<(usize, usize)> { let mut conn = pool.acquire().await?; + #[cfg(feature = "sqlite")] let all_historical_gateways = sqlx::query_scalar!(r#"SELECT count(id) FROM gateways"#) .fetch_one(&mut *conn) .await? .cast_checked()?; + #[cfg(feature = "pg")] + let all_historical_gateways = sqlx::query_scalar!(r#"SELECT count(id) FROM gateways"#) + .fetch_one(&mut *conn) + .await? + .unwrap_or(0) + .cast_checked()?; + + #[cfg(feature = "sqlite")] let all_historical_mixnodes = sqlx::query_scalar!(r#"SELECT count(id) FROM mixnodes"#) .fetch_one(&mut *conn) .await? .cast_checked()?; + #[cfg(feature = "pg")] + let all_historical_mixnodes = sqlx::query_scalar!(r#"SELECT count(id) FROM mixnodes"#) + .fetch_one(&mut *conn) + .await? + .unwrap_or(0) + .cast_checked()?; + Ok((all_historical_gateways, all_historical_mixnodes)) } diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs index 7a482f7ca3..aa4096de1f 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/description.rs @@ -1,6 +1,6 @@ use super::helpers::scrape_and_store_description; +use crate::db::DbPool; use anyhow::Result; -use sqlx::SqlitePool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -17,12 +17,12 @@ static TASK_COUNTER: AtomicUsize = AtomicUsize::new(0); static TASK_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); pub struct DescriptionScraper { - pool: SqlitePool, + pool: DbPool, description_queue: Arc>>, } impl DescriptionScraper { - pub fn new(pool: SqlitePool) -> Self { + pub fn new(pool: DbPool) -> Self { Self { pool, description_queue: Arc::new(Mutex::new(Vec::new())), @@ -50,7 +50,7 @@ impl DescriptionScraper { #[instrument(level = "info", name = "description_scraper", skip_all)] async fn run_description_scraper( - pool: &SqlitePool, + pool: &DbPool, queue: Arc>>, ) -> Result<()> { let nodes = get_nodes_for_scraping(pool).await?; @@ -65,7 +65,7 @@ impl DescriptionScraper { Ok(()) } - async fn process_description_queue(pool: &SqlitePool, queue: Arc>>) { + async fn process_description_queue(pool: &DbPool, queue: Arc>>) { loop { let running_tasks = TASK_COUNTER.load(Ordering::Relaxed); @@ -88,7 +88,7 @@ impl DescriptionScraper { let pool = pool.clone(); tokio::spawn(async move { - match scrape_and_store_description(&pool, &node).await { + match scrape_and_store_description(&pool, node.clone()).await { Ok(_) => debug!( "📝 ✅ Description task #{} for node {} complete", task_id, diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs index 6a1045fd34..9b861cd4c3 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/helpers.rs @@ -1,21 +1,19 @@ use crate::{ db::{ models::{InsertStatsRecord, NodeStats, ScrapeNodeKind, ScraperNodeInfo}, - queries::{ - get_raw_node_stats, insert_daily_node_stats_uncommitted, - insert_scraped_node_description, - }, + queries::insert_scraped_node_description, + DbPool, }, utils::{generate_node_name, now_utc}, }; use ammonia::Builder; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; -use sqlx::{SqlitePool, Transaction}; +use sqlx::Transaction; use std::time::Duration; use time::UtcDateTime; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct NodeDescriptionResponse { pub moniker: Option, pub website: Option, @@ -28,10 +26,11 @@ const DESCRIPTION_URL: &str = "/description"; const PACKET_STATS_URL: &str = "/stats"; // We need this as some of the mixnodes respond with float values for the packet statistics (?????) -pub fn get_packet_value(response: &serde_json::Value, key: &str) -> Option { +pub fn get_packet_value(response: &serde_json::Value, key: &str) -> Option { response .get(key) .and_then(|value| value.as_i64().or_else(|| value.as_f64().map(|f| f as i64))) + .map(|v| v as i32) } pub fn parse_mixnet_stats(response: serde_json::Value) -> Option { @@ -64,7 +63,7 @@ pub fn parse_mixnet_stats(response: serde_json::Value) -> Option { None } -pub fn calculate_packet_difference(current: i64, previous: i64) -> i64 { +pub fn calculate_packet_difference(current: i32, previous: i32) -> i32 { if current >= previous { current - previous } else { @@ -94,10 +93,10 @@ pub fn sanitize_description( const UNKNOWN: &str = "N/A"; let sanitize_field = |opt: Option| -> Option { - Some( - opt.filter(|s| !s.trim().is_empty()) - .map_or_else(|| UNKNOWN.to_string(), |s| sanitizer.clean(&s).to_string()), - ) + Some(opt.filter(|s| !s.trim().is_empty()).map_or_else( + || UNKNOWN.to_string(), + |s| sanitizer.clean(s.trim()).to_string().trim().to_string(), + )) }; let mut moniker = sanitize_field(description.moniker); @@ -115,7 +114,7 @@ pub fn sanitize_description( } } -pub async fn scrape_and_store_description(pool: &SqlitePool, node: &ScraperNodeInfo) -> Result<()> { +pub async fn scrape_and_store_description(pool: &DbPool, node: ScraperNodeInfo) -> Result<()> { let client = build_client()?; let urls = node.contact_addresses(); @@ -150,7 +149,7 @@ pub async fn scrape_and_store_description(pool: &SqlitePool, node: &ScraperNodeI })?; let sanitized_description = sanitize_description(description, *node.node_id()); - insert_scraped_node_description(pool, &node.node_kind, &sanitized_description).await?; + insert_scraped_node_description(pool, node.node_kind.clone(), sanitized_description).await?; Ok(()) } @@ -195,12 +194,15 @@ pub async fn scrape_packet_stats(node: &ScraperNodeInfo) -> Result, node_kind: &ScrapeNodeKind, timestamp: UtcDateTime, current_stats: &NodeStats, ) -> Result<()> { + use crate::db::queries::{get_raw_node_stats, insert_daily_node_stats_uncommitted}; + let date_utc = format!( "{:04}-{:02}-{:02}", timestamp.year(), @@ -235,3 +237,123 @@ pub async fn update_daily_stats_uncommitted( Ok(()) } + +#[cfg(feature = "pg")] +pub async fn update_daily_stats_uncommitted( + tx: &mut Transaction<'static, sqlx::Postgres>, + node_kind: &ScrapeNodeKind, + timestamp: UtcDateTime, + current_stats: &NodeStats, +) -> Result<()> { + use crate::db::queries::{get_raw_node_stats, insert_daily_node_stats_uncommitted}; + + let date_utc = format!( + "{:04}-{:02}-{:02}", + timestamp.year(), + timestamp.month() as u8, + timestamp.day() + ); + + // Get previous stats + let previous_stats = get_raw_node_stats(tx, node_kind).await?; + + let (diff_received, diff_sent, diff_dropped) = if let Some(prev) = previous_stats { + ( + calculate_packet_difference(current_stats.packets_received, prev.packets_received), + calculate_packet_difference(current_stats.packets_sent, prev.packets_sent), + calculate_packet_difference(current_stats.packets_dropped, prev.packets_dropped), + ) + } else { + (0, 0, 0) // No previous stats available + }; + + insert_daily_node_stats_uncommitted( + tx, + node_kind, + &date_utc, + NodeStats { + packets_received: diff_received, + packets_sent: diff_sent, + packets_dropped: diff_dropped, + }, + ) + .await?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_get_packet_value() { + let json = json!({ "packets": 100, "dropped": 50.5 }); + assert_eq!(get_packet_value(&json, "packets"), Some(100)); + assert_eq!(get_packet_value(&json, "dropped"), Some(50)); + assert_eq!(get_packet_value(&json, "non_existent"), None); + } + + #[test] + fn test_parse_mixnet_stats() { + let old_format = json!({ + "packets_explicitly_dropped_since_startup": 10, + "packets_sent_since_startup": 100, + "packets_received_since_startup": 200 + }); + let new_format = json!({ + "dropped_since_startup": 20, + "sent_since_startup": 150, + "received_since_startup": 250 + }); + let invalid_format = json!({ "foo": "bar" }); + + let stats1 = parse_mixnet_stats(old_format).unwrap(); + assert_eq!(stats1.packets_dropped, 10); + assert_eq!(stats1.packets_sent, 100); + assert_eq!(stats1.packets_received, 200); + + let stats2 = parse_mixnet_stats(new_format).unwrap(); + assert_eq!(stats2.packets_dropped, 20); + assert_eq!(stats2.packets_sent, 150); + assert_eq!(stats2.packets_received, 250); + + assert!(parse_mixnet_stats(invalid_format).is_none()); + } + + #[test] + fn test_calculate_packet_difference() { + assert_eq!(calculate_packet_difference(100, 50), 50); + assert_eq!(calculate_packet_difference(50, 100), 50); + assert_eq!(calculate_packet_difference(100, 100), 0); + } + + #[test] + fn test_sanitize_description() { + let desc = NodeDescriptionResponse { + moniker: Some(" Moniker ".to_string()), + website: Some("https://example.com".to_string()), + security_contact: Some("".to_string()), + details: None, + }; + + let sanitized = sanitize_description(desc, 123); + assert_eq!(sanitized.moniker, Some("Moniker".to_string())); + assert_eq!(sanitized.website, Some("https://example.com".to_string())); + assert_eq!(sanitized.security_contact, Some("N/A".to_string())); + assert_eq!(sanitized.details, Some("N/A".to_string())); + + let desc_empty = NodeDescriptionResponse { + moniker: None, + website: None, + security_contact: None, + details: None, + }; + let sanitized_empty = sanitize_description(desc_empty, 123); + assert_ne!(sanitized_empty.moniker, Some("N/A".to_string())); // should generate a name + assert_eq!(sanitized_empty.website, Some("N/A".to_string())); + assert_eq!(sanitized_empty.security_contact, Some("N/A".to_string())); + assert_eq!(sanitized_empty.details, Some("N/A".to_string())); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs b/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs index e76244a18f..e8a53d7ca4 100644 --- a/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs +++ b/nym-node-status-api/nym-node-status-api/src/node_scraper/packet_stats.rs @@ -1,5 +1,5 @@ use super::helpers::scrape_packet_stats; -use sqlx::SqlitePool; +use crate::db::DbPool; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -17,12 +17,12 @@ static TASK_COUNTER: AtomicUsize = AtomicUsize::new(0); static TASK_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); pub struct PacketScraper { - pool: SqlitePool, + pool: DbPool, max_concurrent_tasks: usize, } impl PacketScraper { - pub fn new(pool: SqlitePool, max_concurrent_tasks: usize) -> Self { + pub fn new(pool: DbPool, max_concurrent_tasks: usize) -> Self { Self { pool, max_concurrent_tasks, @@ -50,10 +50,7 @@ impl PacketScraper { } #[instrument(level = "info", name = "packet_scraper", skip_all)] - async fn run_packet_scraper( - pool: &SqlitePool, - max_concurrent_tasks: usize, - ) -> anyhow::Result<()> { + async fn run_packet_scraper(pool: &DbPool, max_concurrent_tasks: usize) -> anyhow::Result<()> { let queue = queries::get_nodes_for_scraping(pool).await?; tracing::info!("Adding {} nodes to the queue", queue.len(),); diff --git a/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs b/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs new file mode 100644 index 0000000000..ee201d555d --- /dev/null +++ b/nym-node-status-api/nym-node-status-api/src/test_helpers/mod.rs @@ -0,0 +1,176 @@ +#[cfg(test)] +pub mod builders { + use crate::http::models::*; + use nym_validator_client::nym_api::SkimmedNode; + use nym_validator_client::nym_nodes::NodeRole; + use nym_validator_client::models::DeclaredRoles; + use nym_contracts_common::Percent; + use nym_crypto::asymmetric::{ed25519, x25519}; + + /// Builder for creating test Gateway instances + pub struct GatewayBuilder { + gateway: Gateway, + } + + impl GatewayBuilder { + pub fn new() -> Self { + Self { + gateway: Gateway { + gateway_identity_key: "test_gateway".to_string(), + bonded: true, + performance: 95, + self_described: Some(serde_json::json!({})), + explorer_pretty_bond: Some(serde_json::json!({})), + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "Test Gateway".to_string(), + website: "https://example.com".to_string(), + security_contact: "admin@example.com".to_string(), + details: "Test gateway node".to_string(), + }, + last_probe_result: None, + last_probe_log: None, + last_testrun_utc: None, + last_updated_utc: "2024-01-01T00:00:00Z".to_string(), + routing_score: 0.95, + config_score: 100, + }, + } + } + + pub fn with_identity(mut self, key: &str) -> Self { + self.gateway.gateway_identity_key = key.to_string(); + self + } + + pub fn with_performance(mut self, performance: u8) -> Self { + self.gateway.performance = performance; + self + } + + pub fn unbonded(mut self) -> Self { + self.gateway.bonded = false; + self + } + + pub fn with_last_probe_result(mut self, result: serde_json::Value) -> Self { + self.gateway.last_probe_result = Some(result); + self + } + + pub fn build(self) -> Gateway { + self.gateway + } + } + + /// Builder for creating test SkimmedNode instances + pub struct SkimmedNodeBuilder { + node: SkimmedNode, + } + + impl SkimmedNodeBuilder { + pub fn new() -> Self { + let ed25519_pk = ed25519::PublicKey::from_bytes(&[1; 32]).unwrap(); + let x25519_pk = x25519::PublicKey::from_bytes(&[2; 32]).unwrap(); + + Self { + node: SkimmedNode { + node_id: 1, + ed25519_identity_pubkey: ed25519_pk, + ip_addresses: vec!["127.0.0.1".parse().unwrap()], + mix_port: 1789, + x25519_sphinx_pubkey: x25519_pk, + role: NodeRole::Mixnode { layer: 1 }, + supported_roles: DeclaredRoles { + entry: false, + mixnode: true, + exit_nr: false, + exit_ipr: false, + }, + entry: None, + performance: Percent::from_percentage_value(95).unwrap(), + }, + } + } + + pub fn with_node_id(mut self, id: u32) -> Self { + self.node.node_id = id; + self + } + + pub fn with_role(mut self, role: NodeRole) -> Self { + self.node.role = role; + self + } + + pub fn as_gateway(mut self) -> Self { + self.node.role = NodeRole::EntryGateway; + self.node.supported_roles = DeclaredRoles { + entry: true, + mixnode: false, + exit_nr: true, + exit_ipr: false, + }; + self + } + + pub fn with_performance(mut self, perf: u8) -> Self { + self.node.performance = Percent::from_percentage_value(perf as u64).unwrap(); + self + } + + pub fn build(self) -> SkimmedNode { + self.node + } + } + + /// Builder for creating test Mixnode instances + pub struct MixnodeBuilder { + mixnode: Mixnode, + } + + impl MixnodeBuilder { + pub fn new() -> Self { + Self { + mixnode: Mixnode { + mix_id: 1, + bonded: true, + is_dp_delegatee: false, + total_stake: 1_000_000, + full_details: Some(serde_json::json!({})), + self_described: Some(serde_json::json!({})), + description: nym_node_requests::api::v1::node::models::NodeDescription { + moniker: "Test Mixnode".to_string(), + website: "https://example.com".to_string(), + security_contact: "admin@example.com".to_string(), + details: "Test mixnode".to_string(), + }, + last_updated_utc: "2024-01-01T00:00:00Z".to_string(), + }, + } + } + + pub fn with_mix_id(mut self, id: u32) -> Self { + self.mixnode.mix_id = id; + self + } + + pub fn with_stake(mut self, stake: i64) -> Self { + self.mixnode.total_stake = stake; + self + } + + pub fn as_dp_delegatee(mut self) -> Self { + self.mixnode.is_dp_delegatee = true; + self + } + + pub fn unbonded(mut self) -> Self { + self.mixnode.bonded = false; + self + } + + pub fn build(self) -> Mixnode { + self.mixnode + } + } +} \ No newline at end of file diff --git a/nym-node-status-api/nym-node-status-api/src/testruns/models.rs b/nym-node-status-api/nym-node-status-api/src/testruns/models.rs index fe4b33384c..309cb8aea8 100644 --- a/nym-node-status-api/nym-node-status-api/src/testruns/models.rs +++ b/nym-node-status-api/nym-node-status-api/src/testruns/models.rs @@ -9,8 +9,165 @@ pub struct GatewayIdentityDto { #[derive(Debug, Clone, Deserialize, Serialize, utoipa::ToSchema)] pub struct TestRun { - pub id: u32, + pub id: i32, pub identity_key: String, pub status: String, pub log: String, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn gateway_identity_dto_creation() { + let dto = GatewayIdentityDto { + gateway_identity_key: "gateway123".to_string(), + bonded: true, + }; + + assert_eq!(dto.gateway_identity_key, "gateway123"); + assert!(dto.bonded); + } + + #[test] + fn gateway_identity_dto_unbonded() { + let dto = GatewayIdentityDto { + gateway_identity_key: "gateway456".to_string(), + bonded: false, + }; + + assert_eq!(dto.gateway_identity_key, "gateway456"); + assert!(!dto.bonded); + } + + #[test] + fn gateway_identity_dto_clone() { + let original = GatewayIdentityDto { + gateway_identity_key: "gateway789".to_string(), + bonded: true, + }; + + let cloned = original.clone(); + + assert_eq!(cloned.gateway_identity_key, original.gateway_identity_key); + assert_eq!(cloned.bonded, original.bonded); + } + + #[test] + fn test_run_creation() { + let test_run = TestRun { + id: 1, + identity_key: "test_gateway_123".to_string(), + status: "success".to_string(), + log: "Test completed successfully".to_string(), + }; + + assert_eq!(test_run.id, 1); + assert_eq!(test_run.identity_key, "test_gateway_123"); + assert_eq!(test_run.status, "success"); + assert_eq!(test_run.log, "Test completed successfully"); + } + + #[test] + fn test_run_with_error_status() { + let test_run = TestRun { + id: 42, + identity_key: "error_gateway".to_string(), + status: "error".to_string(), + log: "Connection timeout: failed to reach gateway".to_string(), + }; + + assert_eq!(test_run.id, 42); + assert_eq!(test_run.status, "error"); + assert!(test_run.log.contains("Connection timeout")); + } + + #[test] + fn test_run_serialization() { + let test_run = TestRun { + id: 123, + identity_key: "serialization_test".to_string(), + status: "pending".to_string(), + log: "".to_string(), + }; + + // Test that it can be serialized + let serialized = serde_json::to_string(&test_run).unwrap(); + assert!(serialized.contains("\"id\":123")); + assert!(serialized.contains("\"identity_key\":\"serialization_test\"")); + assert!(serialized.contains("\"status\":\"pending\"")); + assert!(serialized.contains("\"log\":\"\"")); + + // Test deserialization + let deserialized: TestRun = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized.id, test_run.id); + assert_eq!(deserialized.identity_key, test_run.identity_key); + assert_eq!(deserialized.status, test_run.status); + assert_eq!(deserialized.log, test_run.log); + } + + #[test] + fn test_run_with_long_log() { + let long_log = "Error: ".to_string() + &"x".repeat(1000); + let test_run = TestRun { + id: i32::MAX, + identity_key: "long_log_test".to_string(), + status: "failed".to_string(), + log: long_log.clone(), + }; + + assert_eq!(test_run.id, i32::MAX); + assert_eq!(test_run.log.len(), 1007); // "Error: " + 1000 x's + assert_eq!(test_run.log, long_log); + } + + #[test] + fn test_run_with_special_characters() { + let test_run = TestRun { + id: 0, + identity_key: "special_chars_∞_√_π".to_string(), + status: "unknown".to_string(), + log: "Test with\nnewlines\ttabs\rand \"quotes\"".to_string(), + }; + + assert_eq!(test_run.id, 0); + assert!(test_run.identity_key.contains('∞')); + assert!(test_run.log.contains('\n')); + assert!(test_run.log.contains('\t')); + assert!(test_run.log.contains('"')); + } + + #[test] + fn test_run_clone() { + let original = TestRun { + id: 999, + identity_key: "clone_test".to_string(), + status: "running".to_string(), + log: "In progress...".to_string(), + }; + + let cloned = original.clone(); + + assert_eq!(cloned.id, original.id); + assert_eq!(cloned.identity_key, original.identity_key); + assert_eq!(cloned.status, original.status); + assert_eq!(cloned.log, original.log); + } + + #[test] + fn test_run_edge_cases() { + // Test with negative ID (edge case) + let test_run = TestRun { + id: -1, + identity_key: "".to_string(), + status: "".to_string(), + log: "".to_string(), + }; + + assert_eq!(test_run.id, -1); + assert!(test_run.identity_key.is_empty()); + assert!(test_run.status.is_empty()); + assert!(test_run.log.is_empty()); + } +} diff --git a/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs b/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs index e36dc548f8..8ed220c6a3 100644 --- a/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs +++ b/nym-node-status-api/nym-node-status-api/src/testruns/queue.rs @@ -1,13 +1,12 @@ use crate::db::models::{GatewayInfoDto, TestRunDto, TestRunStatus}; +use crate::db::DbConnection; use crate::testruns::models::TestRun; use crate::utils::now_utc; use anyhow::anyhow; use futures_util::TryStreamExt; -use sqlx::pool::PoolConnection; -use sqlx::Sqlite; pub(crate) async fn try_queue_testrun( - conn: &mut PoolConnection, + conn: &mut DbConnection, identity_key: String, ip_address: String, ) -> anyhow::Result { @@ -15,20 +14,19 @@ pub(crate) async fn try_queue_testrun( let timestamp = now.unix_timestamp(); let timestamp_pretty = now.to_string(); - let items = sqlx::query_as!( - GatewayInfoDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - gateway_identity_key as "gateway_identity_key!", - self_described as "self_described?", - explorer_pretty_bond as "explorer_pretty_bond?" + id, + gateway_identity_key, + self_described, + explorer_pretty_bond FROM gateways WHERE gateway_identity_key = ? AND bonded = true ORDER BY gateway_identity_key LIMIT 1"#, - identity_key, ) + .bind(identity_key.clone()) // TODO dz should call .fetch_one // TODO dz replace this in other queries as well .fetch(conn.as_mut()) @@ -48,22 +46,21 @@ pub(crate) async fn try_queue_testrun( // // check if there is already a test run for this gateway // - let items = sqlx::query_as!( - TestRunDto, + let items = crate::db::query_as::( r#"SELECT - id as "id!", - gateway_id as "gateway_id!", - status as "status!", - created_utc as "created_utc!", - ip_address as "ip_address!", - log as "log!", + id, + gateway_id, + status, + created_utc, + ip_address, + log, last_assigned_utc FROM testruns WHERE gateway_id = ? AND status != 2 ORDER BY id DESC LIMIT 1"#, - gateway_id, ) + .bind(gateway_id) .fetch(conn.as_mut()) .try_collect::>() .await?; @@ -71,8 +68,8 @@ pub(crate) async fn try_queue_testrun( if !items.is_empty() { let testrun = items.first().unwrap(); return Ok(TestRun { - id: testrun.id as u32, - identity_key, + id: testrun.id as i32, + identity_key: identity_key.clone(), status: format!( "{}", TestRunStatus::from_repr(testrun.status as u8).unwrap() @@ -84,9 +81,10 @@ pub(crate) async fn try_queue_testrun( // // save test run // - let status = TestRunStatus::Queued as u32; + let status = TestRunStatus::Queued as i32; let log = format!("Test for {identity_key} requested at {timestamp_pretty} UTC\n\n"); + #[cfg(feature = "sqlite")] let id = sqlx::query!( "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES (?, ?, ?, ?, ?)", gateway_id, @@ -95,13 +93,29 @@ pub(crate) async fn try_queue_testrun( timestamp, log, ) - .execute(conn.as_mut()) - .await? - .last_insert_rowid(); + .execute(conn.as_mut()) + .await? + .last_insert_rowid(); + + #[cfg(feature = "pg")] + let id = { + let record = sqlx::query!( + "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES ($1, $2, $3, $4, $5) RETURNING id", + gateway_id as i32, + status, + ip_address, + timestamp, + log, + ) + .fetch_one(conn.as_mut()) + .await?; + record.id as i64 + }; Ok(TestRun { - id: id as u32, - identity_key, + #[allow(clippy::useless_conversion)] + id: id.try_into().unwrap(), + identity_key: identity_key.clone(), status: format!("{}", TestRunStatus::Queued), log, }) diff --git a/nym-node-status-api/nym-node-status-api/src/utils.rs b/nym-node-status-api/nym-node-status-api/src/utils.rs index 9a91d0ce4b..044aee6c9d 100644 --- a/nym-node-status-api/nym-node-status-api/src/utils.rs +++ b/nym-node-status-api/nym-node-status-api/src/utils.rs @@ -22,9 +22,9 @@ pub(crate) fn generate_node_name(node_id: i64) -> String { #[allow(clippy::items_after_test_module)] #[cfg(test)] mod test { - use rand::Rng; - use super::*; + use rand::Rng; + use std::str::FromStr; #[test] fn generate_node_name_should_be_deterministic() { @@ -40,6 +40,53 @@ mod test { let node_name_same = generate_node_name(node_id); assert_eq!(node_name, node_name_same); } + + #[test] + fn test_decimal_to_i64() { + // Test with a simple decimal + let dec1 = Decimal::from_str("123.456").unwrap(); + assert_eq!(decimal_to_i64(dec1), 123); + + // Test with a decimal that has more than 6 decimal places + let dec2 = Decimal::from_str("123.456789").unwrap(); + assert_eq!(decimal_to_i64(dec2), 123); + + // Test with a decimal that rounds up + let dec3 = Decimal::from_str("123.9999999").unwrap(); + assert_eq!(decimal_to_i64(dec3), 124); + + // Test with a zero decimal + let dec4 = Decimal::zero(); + assert_eq!(decimal_to_i64(dec4), 0); + + // Test with a large decimal + let dec5 = Decimal::from_str("1234567890.123456").unwrap(); + assert_eq!(decimal_to_i64(dec5), 1234567890); + } + + #[test] + fn test_unix_timestamp_to_utc_rfc3339() { + // Test with a known timestamp + let ts1 = 1672531199; // 2022-12-31 23:59:59 UTC + assert_eq!(unix_timestamp_to_utc_rfc3339(ts1), "2022-12-31T23:59:59Z"); + + // Test with the Unix epoch + let ts2 = 0; + assert_eq!(unix_timestamp_to_utc_rfc3339(ts2), "1970-01-01T00:00:00Z"); + } + + #[test] + fn test_numerical_checked_cast() { + // Test successful cast + let val1: u32 = 123; + let res1: anyhow::Result = val1.cast_checked(); + assert_eq!(res1.unwrap(), 123u64); + + // Test failing cast + let val2: i64 = -1; + let res2: anyhow::Result = val2.cast_checked(); + assert!(res2.is_err()); + } } pub(crate) fn now_utc() -> time::UtcDateTime { diff --git a/nym-node-status-api/nym-node-status-client/Cargo.toml b/nym-node-status-api/nym-node-status-client/Cargo.toml index 2bda647607..ef56a68c8b 100644 --- a/nym-node-status-api/nym-node-status-client/Cargo.toml +++ b/nym-node-status-api/nym-node-status-client/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node-status-client" -version = "0.1.1" +version = "0.1.2" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -16,8 +16,11 @@ readme.workspace = true [dependencies] anyhow = { workspace = true } bincode = { workspace = true } -nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] } -nym-http-api-client = { path = "../../common/http-api-client" } +nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [ + "asymmetric", + "serde", +] } +nym-http-api-client = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar" } reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/nym-node-status-api/nym-node-status-client/src/api.rs b/nym-node-status-api/nym-node-status-client/src/api.rs index 46d2fb7cd4..c41b25bf89 100644 --- a/nym-node-status-api/nym-node-status-client/src/api.rs +++ b/nym-node-status-api/nym-node-status-client/src/api.rs @@ -15,4 +15,11 @@ impl ApiPaths { pub(super) fn submit_results(&self, testrun_id: impl Display) -> String { format!("{}/internal/testruns/{}", self.server_address, testrun_id) } + + pub(super) fn submit_results_v2(&self, testrun_id: impl Display) -> String { + format!( + "{}/internal/testruns/{}/v2", + self.server_address, testrun_id + ) + } } diff --git a/nym-node-status-api/nym-node-status-client/src/lib.rs b/nym-node-status-api/nym-node-status-client/src/lib.rs index 00e260e6b5..6451b88c4a 100644 --- a/nym-node-status-api/nym-node-status-client/src/lib.rs +++ b/nym-node-status-api/nym-node-status-client/src/lib.rs @@ -1,4 +1,4 @@ -use crate::models::{get_testrun, submit_results, TestrunAssignment}; +use crate::models::{get_testrun, submit_results, submit_results_v2, TestrunAssignment}; use anyhow::bail; use api::ApiPaths; use nym_crypto::asymmetric::ed25519::{PrivateKey, Signature}; @@ -94,6 +94,37 @@ impl NsApiClient { Ok(()) } + #[instrument(level = "debug", skip(self, probe_result))] + pub async fn submit_results_with_context( + &self, + testrun_id: i32, + probe_result: String, + assigned_at_utc: i64, + gateway_identity_key: String, + ) -> anyhow::Result<()> { + let target_url = self.api.submit_results_v2(testrun_id); + + let payload = submit_results_v2::Payload { + probe_result, + agent_public_key: self.auth_key.public_key(), + assigned_at_utc, + gateway_identity_key, + }; + let signature = self.sign_message(&payload)?; + let submit_results = submit_results_v2::SubmitResultsV2 { payload, signature }; + + let res = self + .client + .post(target_url) + .json(&submit_results) + .send() + .await + .and_then(|response| response.error_for_status())?; + + tracing::debug!("Submitted results with context: {})", res.status()); + Ok(()) + } + fn sign_message(&self, message: &T) -> anyhow::Result where T: serde::Serialize, diff --git a/nym-node-status-api/nym-node-status-client/src/models.rs b/nym-node-status-api/nym-node-status-client/src/models.rs index 05860259ba..c828c5371c 100644 --- a/nym-node-status-api/nym-node-status-client/src/models.rs +++ b/nym-node-status-api/nym-node-status-client/src/models.rs @@ -36,7 +36,7 @@ pub mod get_testrun { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct TestrunAssignment { - pub testrun_id: i64, + pub testrun_id: i32, pub assigned_at_utc: i64, pub gateway_identity_key: String, } @@ -74,3 +74,38 @@ pub mod submit_results { } } } + +pub mod submit_results_v2 { + use crate::auth::SignedRequest; + + use super::*; + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct Payload { + pub probe_result: String, + pub agent_public_key: PublicKey, + pub assigned_at_utc: i64, + pub gateway_identity_key: String, + } + + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct SubmitResultsV2 { + pub payload: Payload, + pub signature: Signature, + } + + impl SignedRequest for SubmitResultsV2 { + type Payload = Payload; + + fn public_key(&self) -> &PublicKey { + &self.payload.agent_public_key + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn payload(&self) -> &Self::Payload { + &self.payload + } + } +} diff --git a/nym-node-status-api/nym-node-status-ui/.gitignore b/nym-node-status-api/nym-node-status-ui/.gitignore new file mode 100644 index 0000000000..5ef6a52078 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/nym-node-status-api/nym-node-status-ui/README.md b/nym-node-status-api/nym-node-status-ui/README.md new file mode 100644 index 0000000000..eb516bb158 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/README.md @@ -0,0 +1,18 @@ +# Node Status UI + +## Build + +```bash +pnpm build +``` + +## Local development + +First, run the development server: + +```bash +pnpm i +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. diff --git a/nym-node-status-api/nym-node-status-ui/biome.json b/nym-node-status-api/nym-node-status-ui/biome.json new file mode 100644 index 0000000000..4c51b66ca6 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/biome.json @@ -0,0 +1,63 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + ".next" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "bracketSpacing": true, + "indentWidth": 2 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "error", + "useExhaustiveDependencies": "warn" + }, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "warn" + }, + "style": { + "useTemplate": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "overrides": [ + { + "include": ["client/**"], + "linter": { + "rules": { + "style": { + "noUselessElse": "off", + "noParameterAssign": "off", + "noNonNullAssertion": "off" + }, + "complexity": { + "noUselessSwitchCase": "off", + "noForEach": "off" + } + } + } + } + ] +} diff --git a/nym-node-status-api/nym-node-status-ui/next.config.ts b/nym-node-status-api/nym-node-status-ui/next.config.ts new file mode 100644 index 0000000000..e9ffa3083a --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/nym-node-status-api/nym-node-status-ui/package.json b/nym-node-status-api/nym-node-status-ui/package.json new file mode 100644 index 0000000000..610a288446 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/package.json @@ -0,0 +1,38 @@ +{ + "name": "nym-node-status-ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "biome check ./src", + "lint:fix": "biome check ./src --fix", + "generate:openapi-client": "npx @hey-api/openapi-ts -o src/client -p '@tanstack/react-query' -i https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.2.0", + "@mui/material": "^7.2.0", + "@mui/material-nextjs": "^7.2.0", + "@mui/x-charts": "^8.8.0", + "@mui/x-date-pickers": "^7.29.4", + "@tanstack/react-query": "^5.83.0", + "dayjs": "^1.11.13", + "material-react-table": "^3.2.1", + "next": "^15.4.1", + "openapi-typescript": "^7.5.2", + "react": "^19.0.0", + "react-country-flag": "^3.1.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@tanstack/react-query-devtools": "^5.83.0", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "^5" + } +} diff --git a/nym-node-status-api/nym-node-status-ui/pnpm-lock.yaml b/nym-node-status-api/nym-node-status-ui/pnpm-lock.yaml new file mode 100644 index 0000000000..ab4a48acac --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/pnpm-lock.yaml @@ -0,0 +1,2185 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': + specifier: ^11.14.1 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/icons-material': + specifier: ^7.2.0 + version: 7.2.0(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/material': + specifier: ^7.2.0 + version: 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/material-nextjs': + specifier: ^7.2.0 + version: 7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + '@mui/x-charts': + specifier: ^8.8.0 + version: 8.8.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/x-date-pickers': + specifier: ^7.29.4 + version: 7.29.4(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(dayjs@1.11.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/react-query': + specifier: ^5.83.0 + version: 5.83.0(react@19.0.0) + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + material-react-table: + specifier: ^3.2.1 + version: 3.2.1(e4be24f02074e47c0e16001efb50991b) + next: + specifier: ^15.4.1 + version: 15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + openapi-typescript: + specifier: ^7.5.2 + version: 7.5.2(typescript@5.7.3) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-country-flag: + specifier: ^3.1.0 + version: 3.1.0(react@19.0.0) + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@tanstack/react-query-devtools': + specifier: ^5.83.0 + version: 5.83.0(@tanstack/react-query@5.83.0(react@19.0.0))(react@19.0.0) + '@types/node': + specifier: ^20 + version: 20.17.14 + '@types/react': + specifier: ^19 + version: 19.0.7 + '@types/react-dom': + specifier: ^19 + version: 19.0.3(@types/react@19.0.7) + typescript: + specifier: ^5 + version: 5.7.3 + +packages: + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.5': + resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.5': + resolution: {integrity: sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.5': + resolution: {integrity: sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.5': + resolution: {integrity: sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@emnapi/runtime@1.4.4': + resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==} + + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.3.1': + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.1': + resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + + '@img/sharp-darwin-arm64@0.34.3': + resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.3': + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.0': + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.0': + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.0': + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.0': + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.0': + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.0': + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.0': + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.3': + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.3': + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.3': + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.3': + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.3': + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.3': + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.3': + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.3': + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.3': + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.3': + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@mui/core-downloads-tracker@7.2.0': + resolution: {integrity: sha512-d49s7kEgI5iX40xb2YPazANvo7Bx0BLg/MNRwv+7BVpZUzXj1DaVCKlQTDex3gy/0jsCb4w7AY2uH4t4AJvSog==} + + '@mui/icons-material@7.2.0': + resolution: {integrity: sha512-gRCspp3pfjHQyTmSOmYw7kUQTd9Udpdan4R8EnZvqPeoAtHnPzkvjBrBqzKaoAbbBp5bGF7BcD18zZJh4nwu0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^7.2.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material-nextjs@7.2.0': + resolution: {integrity: sha512-/W2iKkjeOdaYBu5xNYi/w5HUX2C4HHefSMW7UgCvTKl90yy1puE7kmAgv/gxBghqhEE27cNWdevRrnvVhNRaUA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/cache': ^11.11.0 + '@emotion/react': ^11.11.4 + '@emotion/server': ^11.11.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + next: ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/cache': + optional: true + '@emotion/server': + optional: true + '@types/react': + optional: true + + '@mui/material@7.2.0': + resolution: {integrity: sha512-NTuyFNen5Z2QY+I242MDZzXnFIVIR6ERxo7vntFi9K1wCgSwvIl0HcAO2OOydKqqKApE6omRiYhpny1ZhGuH7Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^7.2.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@7.2.0': + resolution: {integrity: sha512-y6N1Yt3T5RMxVFnCh6+zeSWBuQdNDm5/UlM0EAYZzZR/1u+XKJWYQmbpx4e+F+1EpkYi3Nk8KhPiQDi83M3zIw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@7.2.0': + resolution: {integrity: sha512-yq08xynbrNYcB1nBcW9Fn8/h/iniM3ewRguGJXPIAbHvxEF7Pz95kbEEOAAhwzxMX4okhzvHmk0DFuC5ayvgIQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@7.2.0': + resolution: {integrity: sha512-PG7cm/WluU6RAs+gNND2R9vDwNh+ERWxPkqTaiXQJGIFAyJ+VxhyKfzpdZNk0z0XdmBxxi9KhFOpgxjehf/O0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.4.4': + resolution: {integrity: sha512-p63yhbX52MO/ajXC7hDHJA5yjzJekvWD3q4YDLl1rSg+OXLczMYPvTuSuviPRCgRX8+E42RXz1D/dz9SxPSlWg==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@7.2.0': + resolution: {integrity: sha512-O0i1GQL6MDzhKdy9iAu5Yr0Sz1wZjROH1o3aoztuivdCXqEeQYnEjTDiRLGuFxI9zrUbTHBwobMyQH5sNtyacw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/x-charts-vendor@8.5.3': + resolution: {integrity: sha512-H05cb0c2qfRhWLPcwtiIU8BOcKTrMNvhgmRAvJJXpmlirOA1km8dUlR71VeUvJiCthhVIHKyFkPPzFYKgHAfng==} + + '@mui/x-charts@8.8.0': + resolution: {integrity: sha512-tqdwKoUpo8u+KZdEWO4C21Q0P3HOL/DadAZSMmTdtO1LDCO/m4S8UtHFpj2B0pZuikdiBJ5bz49I+nfBfK1Xng==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0 + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/x-date-pickers@7.29.4': + resolution: {integrity: sha512-wJ3tsqk/y6dp+mXGtT9czciAMEO5Zr3IIAHg9x6IL0Eqanqy0N3chbmQQZv3iq0m2qUpQDLvZ4utZBUTJdjNzw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0 + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0 + date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0 + dayjs: ^1.10.7 + luxon: ^3.0.2 + moment: ^2.29.4 + moment-hijri: ^2.1.2 || ^3.0.0 + moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + date-fns: + optional: true + date-fns-jalali: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + moment-hijri: + optional: true + moment-jalaali: + optional: true + + '@mui/x-internal-gestures@0.2.1': + resolution: {integrity: sha512-7Po6F4/RdUrFyRwiwvh5ZNeY/bi8wavTCUe+stKAyMliKpgcYiEtH7ywTgroOEq0o56fIpyPzwC4+bbGwYFnvA==} + + '@mui/x-internals@7.29.0': + resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-internals@8.8.0': + resolution: {integrity: sha512-qTRK5oINkAjZ7sIHpSnESLNq1xtQUmmfmGscYUSEP0uHoYh6pKkNWH9+7yzggRHuTv+4011VBwN9s+efrk+xZg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@next/env@15.4.1': + resolution: {integrity: sha512-DXQwFGAE2VH+f2TJsKepRXpODPU+scf5fDbKOME8MMyeyswe4XwgRdiiIYmBfkXU+2ssliLYznajTrOQdnLR5A==} + + '@next/swc-darwin-arm64@15.4.1': + resolution: {integrity: sha512-L+81yMsiHq82VRXS2RVq6OgDwjvA4kDksGU8hfiDHEXP+ncKIUhUsadAVB+MRIp2FErs/5hpXR0u2eluWPAhig==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.4.1': + resolution: {integrity: sha512-jfz1RXu6SzL14lFl05/MNkcN35lTLMJWPbqt7Xaj35+ZWAX342aePIJrN6xBdGeKl6jPXJm0Yqo3Xvh3Gpo3Uw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.4.1': + resolution: {integrity: sha512-k0tOFn3dsnkaGfs6iQz8Ms6f1CyQe4GacXF979sL8PNQxjYS1swx9VsOyUQYaPoGV8nAZ7OX8cYaeiXGq9ahPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.4.1': + resolution: {integrity: sha512-4ogGQ/3qDzbbK3IwV88ltihHFbQVq6Qr+uEapzXHXBH1KsVBZOB50sn6BWHPcFjwSoMX2Tj9eH/fZvQnSIgc3g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.4.1': + resolution: {integrity: sha512-Jj0Rfw3wIgp+eahMz/tOGwlcYYEFjlBPKU7NqoOkTX0LY45i5W0WcDpgiDWSLrN8KFQq/LW7fZq46gxGCiOYlQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.4.1': + resolution: {integrity: sha512-9WlEZfnw1vFqkWsTMzZDgNL7AUI1aiBHi0S2m8jvycPyCq/fbZjtE/nDkhJRYbSjXbtRHYLDBlmP95kpjEmJbw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.4.1': + resolution: {integrity: sha512-WodRbZ9g6CQLRZsG3gtrA9w7Qfa9BwDzhFVdlI6sV0OCPq9JrOrJSp9/ioLsezbV8w9RCJ8v55uzJuJ5RgWLZg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.4.1': + resolution: {integrity: sha512-y+wTBxelk2xiNofmDOVU7O5WxTHcvOoL3srOM0kxTzKDjQ57kPU0tpnPJ/BWrRnsOwXEv0+3QSbGR7hY4n9LkQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.20.1': + resolution: {integrity: sha512-TYiTDtuItiv95YMsrRxyCs1HKLrDPtTvpaD3+kDKXBnFDeJuYKZ+eHXpCr6YeN4inxfVBs7DLhHsQcs9srddyQ==} + + '@redocly/openapi-core@1.27.2': + resolution: {integrity: sha512-qVrDc27DHpeO2NRCMeRdb4299nijKQE3BY0wrA+WUHlOLScorIi/y7JzammLk22IaTvjR9Mv9aTAdjE1aUwJnA==} + engines: {node: '>=14.19.0', npm: '>=7.0.0'} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tanstack/match-sorter-utils@8.19.4': + resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.83.0': + resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} + + '@tanstack/query-devtools@5.81.2': + resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} + + '@tanstack/react-query-devtools@5.83.0': + resolution: {integrity: sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==} + peerDependencies: + '@tanstack/react-query': ^5.83.0 + react: ^18 || ^19 + + '@tanstack/react-query@5.83.0': + resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-table@8.20.6': + resolution: {integrity: sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/react-virtual@3.11.2': + resolution: {integrity: sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/table-core@8.20.5': + resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==} + engines: {node: '>=12'} + + '@tanstack/virtual-core@3.11.2': + resolution: {integrity: sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/node@20.17.14': + resolution: {integrity: sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@19.0.3': + resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + + '@types/react@19.0.7': + resolution: {integrity: sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bezier-easing@2.1.0: + resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001695: + resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} + + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + highlight-words@2.0.0: + resolution: {integrity: sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==} + engines: {node: '>= 20', npm: '>= 9'} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + index-to-position@0.1.2: + resolution: {integrity: sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==} + engines: {node: '>=18'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + material-react-table@3.2.1: + resolution: {integrity: sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==} + engines: {node: '>=16'} + peerDependencies: + '@emotion/react': '>=11.13' + '@emotion/styled': '>=11.13' + '@mui/icons-material': '>=6' + '@mui/material': '>=6' + '@mui/x-date-pickers': '>=7.15' + react: '>=18.0' + react-dom: '>=18.0' + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@15.4.1: + resolution: {integrity: sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + openapi-typescript@7.5.2: + resolution: {integrity: sha512-W/QXuQz0Fa3bGY6LKoqTCgrSX+xI/ST+E5RXo2WBmp3WwgXCWKDJPHv5GZmElF4yLCccnqYsakBDOJikHZYGRw==} + hasBin: true + peerDependencies: + typescript: ^5.x + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-json@8.1.0: + resolution: {integrity: sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==} + engines: {node: '>=18'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + react-country-flag@3.1.0: + resolution: {integrity: sha512-JWQFw1efdv9sTC+TGQvTKXQg1NKbDU2mBiAiRWcKM9F1sK+/zjhP2yGmm8YDddWyZdXVkR8Md47rPMJmo4YO5g==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16' + + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@19.1.0: + resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.3: + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + + supports-color@9.4.0: + resolution: {integrity: sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==} + engines: {node: '>=12'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.33.0: + resolution: {integrity: sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g==} + engines: {node: '>=16'} + + typescript@5.7.3: + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + +snapshots: + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/generator@7.26.5': + dependencies: + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.5 + '@babel/types': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/parser@7.26.5': + dependencies: + '@babel/types': 7.26.5 + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/runtime@7.27.6': {} + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.5 + '@babel/types': 7.26.5 + + '@babel/traverse@7.26.5': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.5 + '@babel/parser': 7.26.5 + '@babel/template': 7.25.9 + '@babel/types': 7.26.5 + debug: 4.4.0(supports-color@9.4.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.5': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@emnapi/runtime@1.4.4': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.0 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.0.0) + '@emotion/utils': 1.4.2 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@img/sharp-darwin-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.0 + optional: true + + '@img/sharp-darwin-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + optional: true + + '@img/sharp-linux-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.0 + optional: true + + '@img/sharp-linux-arm@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.0 + optional: true + + '@img/sharp-linux-ppc64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.0 + optional: true + + '@img/sharp-linux-s390x@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.0 + optional: true + + '@img/sharp-linux-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + optional: true + + '@img/sharp-wasm32@0.34.3': + dependencies: + '@emnapi/runtime': 1.4.4 + optional: true + + '@img/sharp-win32-arm64@0.34.3': + optional: true + + '@img/sharp-win32-ia32@0.34.3': + optional: true + + '@img/sharp-win32-x64@0.34.3': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@mui/core-downloads-tracker@7.2.0': {} + + '@mui/icons-material@7.2.0(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@mui/material-nextjs@7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + next: 15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + optionalDependencies: + '@emotion/cache': 11.14.0 + '@types/react': 19.0.7 + + '@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/core-downloads-tracker': 7.2.0 + '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/types': 7.4.4(@types/react@19.0.7) + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.0.7) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-is: 19.1.0 + react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@types/react': 19.0.7 + + '@mui/private-theming@7.2.0(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@mui/styled-engine@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + + '@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/private-theming': 7.2.0(@types/react@19.0.7)(react@19.0.0) + '@mui/styled-engine': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(react@19.0.0) + '@mui/types': 7.4.4(@types/react@19.0.7) + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 19.0.0 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@types/react': 19.0.7 + + '@mui/types@7.4.4(@types/react@19.0.7)': + dependencies: + '@babel/runtime': 7.27.6 + optionalDependencies: + '@types/react': 19.0.7 + + '@mui/utils@7.2.0(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/types': 7.4.4(@types/react@19.0.7) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-is: 19.1.0 + optionalDependencies: + '@types/react': 19.0.7 + + '@mui/x-charts-vendor@8.5.3': + dependencies: + '@babel/runtime': 7.27.6 + '@types/d3-color': 3.1.3 + '@types/d3-delaunay': 6.0.4 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-color: 3.1.0 + d3-delaunay: 6.0.4 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + delaunator: 5.0.1 + robust-predicates: 3.0.2 + + '@mui/x-charts@8.8.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + '@mui/x-charts-vendor': 8.5.3 + '@mui/x-internal-gestures': 0.2.1 + '@mui/x-internals': 8.8.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + bezier-easing: 2.1.0 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + reselect: 5.1.1 + use-sync-external-store: 1.5.0(react@19.0.0) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-date-pickers@7.29.4(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(dayjs@1.11.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + '@mui/x-internals': 7.29.0(@types/react@19.0.7)(react@19.0.0) + '@types/react-transition-group': 4.4.12(@types/react@19.0.7) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-transition-group: 4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + dayjs: 1.11.13 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internal-gestures@0.2.1': + dependencies: + '@babel/runtime': 7.27.6 + + '@mui/x-internals@7.29.0(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internals@8.8.0(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/system': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/utils': 7.2.0(@types/react@19.0.7)(react@19.0.0) + react: 19.0.0 + reselect: 5.1.1 + transitivePeerDependencies: + - '@types/react' + + '@next/env@15.4.1': {} + + '@next/swc-darwin-arm64@15.4.1': + optional: true + + '@next/swc-darwin-x64@15.4.1': + optional: true + + '@next/swc-linux-arm64-gnu@15.4.1': + optional: true + + '@next/swc-linux-arm64-musl@15.4.1': + optional: true + + '@next/swc-linux-x64-gnu@15.4.1': + optional: true + + '@next/swc-linux-x64-musl@15.4.1': + optional: true + + '@next/swc-win32-arm64-msvc@15.4.1': + optional: true + + '@next/swc-win32-x64-msvc@15.4.1': + optional: true + + '@popperjs/core@2.11.8': {} + + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.20.1': {} + + '@redocly/openapi-core@1.27.2(supports-color@9.4.0)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.20.1 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@9.4.0) + js-levenshtein: 1.1.6 + js-yaml: 4.1.0 + minimatch: 5.1.6 + node-fetch: 2.7.0 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - encoding + - supports-color + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tanstack/match-sorter-utils@8.19.4': + dependencies: + remove-accents: 0.5.0 + + '@tanstack/query-core@5.83.0': {} + + '@tanstack/query-devtools@5.81.2': {} + + '@tanstack/react-query-devtools@5.83.0(@tanstack/react-query@5.83.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/query-devtools': 5.81.2 + '@tanstack/react-query': 5.83.0(react@19.0.0) + react: 19.0.0 + + '@tanstack/react-query@5.83.0(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.83.0 + react: 19.0.0 + + '@tanstack/react-table@8.20.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/table-core': 8.20.5 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@tanstack/react-virtual@3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/virtual-core': 3.11.2 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + '@tanstack/table-core@8.20.5': {} + + '@tanstack/virtual-core@3.11.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/node@20.17.14': + dependencies: + undici-types: 6.19.8 + + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@19.0.3(@types/react@19.0.7)': + dependencies: + '@types/react': 19.0.7 + + '@types/react-transition-group@4.4.12(@types/react@19.0.7)': + dependencies: + '@types/react': 19.0.7 + + '@types/react@19.0.7': + dependencies: + csstype: 3.1.3 + + agent-base@7.1.3: {} + + ansi-colors@4.1.3: {} + + argparse@2.0.1: {} + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.26.0 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + + balanced-match@1.0.2: {} + + bezier-easing@2.1.0: {} + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001695: {} + + change-case@5.4.4: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + optional: true + + color-name@1.1.4: + optional: true + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + colorette@1.4.0: {} + + convert-source-map@1.9.0: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + csstype@3.1.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-format@3.1.0: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + dayjs@1.11.13: {} + + debug@4.4.0(supports-color@9.4.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 9.4.0 + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + detect-libc@2.0.4: + optional: true + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.27.6 + csstype: 3.1.3 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + escape-string-regexp@4.0.0: {} + + fast-deep-equal@3.1.3: {} + + find-root@1.1.0: {} + + function-bind@1.1.2: {} + + globals@11.12.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + highlight-words@2.0.0: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + https-proxy-agent@7.0.6(supports-color@9.4.0): + dependencies: + agent-base: 7.1.3 + debug: 4.4.0(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + index-to-position@0.1.2: {} + + internmap@2.0.3: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: + optional: true + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + js-levenshtein@1.1.6: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + lines-and-columns@1.2.4: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + material-react-table@3.2.1(e4be24f02074e47c0e16001efb50991b): + dependencies: + '@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/icons-material': 7.2.0(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@types/react@19.0.7)(react@19.0.0) + '@mui/material': 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(dayjs@1.11.13)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/match-sorter-utils': 8.19.4 + '@tanstack/react-table': 8.20.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/react-virtual': 3.11.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + highlight-words: 2.0.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.3: {} + + nanoid@3.3.8: {} + + next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@next/env': 15.4.1 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001695 + postcss: 8.4.31 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.1 + '@next/swc-darwin-x64': 15.4.1 + '@next/swc-linux-arm64-gnu': 15.4.1 + '@next/swc-linux-arm64-musl': 15.4.1 + '@next/swc-linux-x64-gnu': 15.4.1 + '@next/swc-linux-x64-musl': 15.4.1 + '@next/swc-win32-arm64-msvc': 15.4.1 + '@next/swc-win32-x64-msvc': 15.4.1 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + object-assign@4.1.1: {} + + openapi-typescript@7.5.2(typescript@5.7.3): + dependencies: + '@redocly/openapi-core': 1.27.2(supports-color@9.4.0) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.1.0 + supports-color: 9.4.0 + typescript: 5.7.3 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - encoding + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-json@8.1.0: + dependencies: + '@babel/code-frame': 7.26.2 + index-to-position: 0.1.2 + type-fest: 4.33.0 + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + pluralize@8.0.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + react-country-flag@3.1.0(react@19.0.0): + dependencies: + react: 19.0.0 + + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + + react-is@16.13.1: {} + + react-is@19.1.0: {} + + react-transition-group@4.4.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.27.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + + react@19.0.0: {} + + regenerator-runtime@0.14.1: {} + + remove-accents@0.5.0: {} + + require-from-string@2.0.2: {} + + reselect@5.1.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + robust-predicates@3.0.2: {} + + scheduler@0.25.0: {} + + semver@7.7.2: + optional: true + + sharp@0.34.3: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.3 + '@img/sharp-darwin-x64': 0.34.3 + '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-linux-arm': 0.34.3 + '@img/sharp-linux-arm64': 0.34.3 + '@img/sharp-linux-ppc64': 0.34.3 + '@img/sharp-linux-s390x': 0.34.3 + '@img/sharp-linux-x64': 0.34.3 + '@img/sharp-linuxmusl-arm64': 0.34.3 + '@img/sharp-linuxmusl-x64': 0.34.3 + '@img/sharp-wasm32': 0.34.3 + '@img/sharp-win32-arm64': 0.34.3 + '@img/sharp-win32-ia32': 0.34.3 + '@img/sharp-win32-x64': 0.34.3 + optional: true + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + source-map-js@1.2.1: {} + + source-map@0.5.7: {} + + styled-jsx@5.1.6(react@19.0.0): + dependencies: + client-only: 0.0.1 + react: 19.0.0 + + stylis@4.2.0: {} + + supports-color@9.4.0: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + tr46@0.0.3: {} + + tslib@2.8.1: {} + + type-fest@4.33.0: {} + + typescript@5.7.3: {} + + undici-types@6.19.8: {} + + uri-js-replace@1.0.1: {} + + use-sync-external-store@1.5.0(react@19.0.0): + dependencies: + react: 19.0.0 + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + yaml-ast-parser@0.0.43: {} + + yaml@1.10.2: {} + + yargs-parser@21.1.1: {} diff --git a/nym-node-status-api/nym-node-status-ui/src/app/layout.tsx b/nym-node-status-api/nym-node-status-ui/src/app/layout.tsx new file mode 100644 index 0000000000..83d23fa86d --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/src/app/layout.tsx @@ -0,0 +1,11 @@ +"use client"; + +export default function RootLayout(props: { children: React.ReactNode }) { + return ( + + + {props.children} + + + ); +} diff --git a/nym-node-status-api/nym-node-status-ui/src/app/page.tsx b/nym-node-status-api/nym-node-status-ui/src/app/page.tsx new file mode 100644 index 0000000000..b77b535926 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/src/app/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +export default function Home() { + return ( +
TODO
+ ); +} diff --git a/nym-node-status-api/nym-node-status-ui/tsconfig.json b/nym-node-status-api/nym-node-status-ui/tsconfig.json new file mode 100644 index 0000000000..8ed7498522 --- /dev/null +++ b/nym-node-status-api/nym-node-status-ui/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index ff14e1a644..ba4f7fbaea 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-node" -version = "1.15.0" +version = "1.16.0" authors.workspace = true repository.workspace = true homepage.workspace = true @@ -20,8 +20,10 @@ arc-swap = { workspace = true } bip39 = { workspace = true, features = ["zeroize"] } bs58.workspace = true bloomfilter = { workspace = true } -celes = { workspace = true } # country codes +celes = { workspace = true } +cfg-if = { workspace = true }# country codes colored = { workspace = true } +console-subscriber = { workspace = true, optional = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } futures = { workspace = true } @@ -50,7 +52,9 @@ nym-bin-common = { path = "../common/bin-common", features = [ "basic_tracing", "output_format", ] } -nym-client-core-config-types = { path = "../common/client-core/config-types", features = ["disk-persistence"] } +nym-client-core-config-types = { path = "../common/client-core/config-types", features = [ + "disk-persistence", +] } nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto", features = ["asymmetric", "rand"] } nym-nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } @@ -86,8 +90,14 @@ tower-http = { workspace = true, features = ["fs"] } utoipa = { workspace = true, features = ["axum_extras", "time"] } utoipa-swagger-ui = { workspace = true, features = ["axum"] } -nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa", "output", "middleware"] } -nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"] } +nym-http-api-common = { path = "../common/http-api-common", features = [ + "utoipa", + "output", + "middleware", +] } +nym-node-requests = { path = "nym-node-requests", default-features = false, features = [ + "openapi", +] } nym-node-metrics = { path = "nym-node-metrics" } # nodes: @@ -100,7 +110,7 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } # throughput tester to recreate lioness # we don't care about particular versions - just pull whatever is used by sphinx lioness = "*" -chacha = "*" +chacha = "0.3.0" arrayref = "*" blake2 = "=0.8.1" sha2 = { workspace = true } @@ -119,6 +129,8 @@ cargo_metadata = { workspace = true } criterion = { workspace = true, features = ["async_tokio"] } rand_chacha = { workspace = true } +[features] +tokio-console = ["console-subscriber"] [lints] workspace = true diff --git a/nym-node/src/cli/commands/debug/mod.rs b/nym-node/src/cli/commands/debug/mod.rs new file mode 100644 index 0000000000..9fc1175a80 --- /dev/null +++ b/nym-node/src/cli/commands/debug/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) mod reset_providers_dbs; diff --git a/nym-node/src/cli/commands/debug/reset_providers_dbs.rs b/nym-node/src/cli/commands/debug/reset_providers_dbs.rs new file mode 100644 index 0000000000..39130686ae --- /dev/null +++ b/nym-node/src/cli/commands/debug/reset_providers_dbs.rs @@ -0,0 +1,36 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::cli::helpers::ConfigArgs; +use crate::config::upgrade_helpers::try_load_current_config; +use crate::node::helpers::load_ed25519_identity_public_key; +use crate::node::ServiceProvidersData; +use nym_network_requester::{CustomGatewayDetails, GatewayDetails, GatewayRegistration}; +use std::fs; + +#[derive(Debug, clap::Args)] +pub struct Args { + #[clap(flatten)] + pub(crate) config: ConfigArgs, +} + +pub async fn execute(args: Args) -> anyhow::Result<()> { + let config = try_load_current_config(args.config.config_path()).await?; + + let public_key = load_ed25519_identity_public_key( + &config.storage_paths.keys.public_ed25519_identity_key_file, + )?; + + let storage_paths = &config.service_providers.storage_paths; + for db_path in [ + &storage_paths.authenticator.gateway_registrations, + &storage_paths.ip_packet_router.gateway_registrations, + &storage_paths.network_requester.gateway_registrations, + ] { + fs::remove_file(db_path)?; + let gateway_details: GatewayRegistration = + GatewayDetails::Custom(CustomGatewayDetails::new(public_key)).into(); + ServiceProvidersData::initialise_client_gateway_storage(db_path, &gateway_details).await?; + } + Ok(()) +} diff --git a/nym-node/src/cli/commands/mod.rs b/nym-node/src/cli/commands/mod.rs index 345a2143e7..6bab14a818 100644 --- a/nym-node/src/cli/commands/mod.rs +++ b/nym-node/src/cli/commands/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod bonding_information; pub(super) mod build_info; +pub(super) mod debug; pub(super) mod migrate; pub(crate) mod node_details; pub(crate) mod reset_sphinx_keys; diff --git a/nym-node/src/cli/commands/test_throughput.rs b/nym-node/src/cli/commands/test_throughput.rs index af7cc8ad42..57b6bb9d03 100644 --- a/nym-node/src/cli/commands/test_throughput.rs +++ b/nym-node/src/cli/commands/test_throughput.rs @@ -6,7 +6,6 @@ use crate::logging::granual_filtered_env; use crate::throughput_tester::test_mixing_throughput; use anyhow::bail; use humantime_serde::re::humantime; -use indicatif::ProgressStyle; use nym_bin_common::logging::default_tracing_fmt_layer; use std::env::temp_dir; use std::path::PathBuf; @@ -41,9 +40,6 @@ pub struct Args { fn init_test_logger() -> anyhow::Result<()> { let indicatif_layer = IndicatifLayer::new() - .with_progress_style(ProgressStyle::with_template( - "{span_child_prefix}{spinner} {span_fields} -- {span_name} {wide_msg}", - )?) .with_span_child_prefix_symbol("↳ ") .with_span_child_prefix_indent(" "); diff --git a/nym-node/src/cli/mod.rs b/nym-node/src/cli/mod.rs index 37e79bcd8f..b480743922 100644 --- a/nym-node/src/cli/mod.rs +++ b/nym-node/src/cli/mod.rs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::cli::commands::{ - bonding_information, build_info, migrate, node_details, reset_sphinx_keys, run, sign, + bonding_information, build_info, debug, migrate, node_details, reset_sphinx_keys, run, sign, test_throughput, }; use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG}; use crate::logging::setup_tracing_logger; -use clap::{Parser, Subcommand}; +use clap::{Args, Parser, Subcommand}; use nym_bin_common::bin_info; use std::future::Future; use std::sync::OnceLock; @@ -56,7 +56,7 @@ impl Cli { pub(crate) fn execute(self) -> anyhow::Result<()> { // NOTE: `test_throughput` sets up its own logger as it has to include additional layers if !matches!(self.command, Commands::TestThroughput(..)) { - setup_tracing_logger()?; + crate::logging::setup_tracing_logger()?; } match self.command { @@ -72,6 +72,11 @@ impl Cli { Commands::UnsafeResetSphinxKeys(args) => { { Self::execute_async(reset_sphinx_keys::execute(args))? }? } + Commands::Debug(debug) => match debug.command { + DebugCommands::ResetProvidersGatewayDbs(args) => { + { Self::execute_async(debug::reset_providers_dbs::execute(args))? }? + } + }, } Ok(()) } @@ -100,12 +105,28 @@ pub(crate) enum Commands { /// UNSAFE: reset existing sphinx keys and attempt to generate fresh one for the current network state UnsafeResetSphinxKeys(reset_sphinx_keys::Args), + /// Commands exposed for debug purposes, usually not meant to be used by operators + #[clap(hide = true)] + Debug(Debug), + /// Attempt to approximate the maximum mixnet throughput if nym-node /// was running on this machine in mixnet mode #[clap(hide = true)] TestThroughput(test_throughput::Args), } +#[derive(Debug, Args)] +pub(crate) struct Debug { + #[clap(subcommand)] + pub(crate) command: DebugCommands, +} + +#[derive(Subcommand, Debug)] +pub(crate) enum DebugCommands { + /// Reset the internal GatewaysDetailsStores of all service providers in case they got corrupted + ResetProvidersGatewayDbs(debug::reset_providers_dbs::Args), +} + #[cfg(test)] mod tests { use super::*; diff --git a/nym-node/src/logging.rs b/nym-node/src/logging.rs index 4852b68635..e1812059be 100644 --- a/nym-node/src/logging.rs +++ b/nym-node/src/logging.rs @@ -5,8 +5,9 @@ use nym_bin_common::logging::{default_tracing_env_filter, default_tracing_fmt_la use tracing_subscriber::filter::Directive; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, Layer}; -pub(crate) fn granual_filtered_env() -> anyhow::Result { +pub(crate) fn granual_filtered_env() -> anyhow::Result { fn directive_checked(directive: impl Into) -> anyhow::Result { directive.into().parse().map_err(From::from) } @@ -21,14 +22,23 @@ pub(crate) fn granual_filtered_env() -> anyhow::Result anyhow::Result { - Ok(tracing_subscriber::registry() - .with(default_tracing_fmt_layer(std::io::stderr)) - .with(granual_filtered_env()?)) -} - pub(crate) fn setup_tracing_logger() -> anyhow::Result<()> { - build_tracing_logger()?.init(); + let stderr_layer = + default_tracing_fmt_layer(std::io::stderr).with_filter(granual_filtered_env()?); + + cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] { + // instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time + let console_layer = console_subscriber::spawn(); + + tracing_subscriber::registry() + .with(console_layer) + .with(stderr_layer) + .init(); + } else { + tracing_subscriber::registry() + .with(stderr_layer) + .init(); + }} Ok(()) } diff --git a/nym-node/src/node/mixnet/shared/final_hop.rs b/nym-node/src/node/mixnet/shared/final_hop.rs index d38fc99d9f..cb6bc0ea64 100644 --- a/nym-node/src/node/mixnet/shared/final_hop.rs +++ b/nym-node/src/node/mixnet/shared/final_hop.rs @@ -1,7 +1,9 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: GPL-3.0-only -use nym_gateway::node::{ActiveClientsStore, GatewayStorage, GatewayStorageError}; +use nym_gateway::node::{ + ActiveClientsStore, GatewayStorage, GatewayStorageError, InboxGatewayStorage, +}; use nym_sphinx_types::DestinationAddressBytes; use tracing::debug; diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index c240686b8a..6ec9105482 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -164,7 +164,7 @@ impl ServiceProvidersData { Ok(()) } - async fn initialise_client_gateway_storage( + pub(crate) async fn initialise_client_gateway_storage( storage_path: &Path, registration: &GatewayRegistration, ) -> Result<(), ServiceProvidersError> { diff --git a/nym-node/src/throughput_tester/mod.rs b/nym-node/src/throughput_tester/mod.rs index 3371fb759c..aaa2360726 100644 --- a/nym-node/src/throughput_tester/mod.rs +++ b/nym-node/src/throughput_tester/mod.rs @@ -9,7 +9,6 @@ use crate::throughput_tester::global_stats::GlobalStatsUpdater; use crate::throughput_tester::stats::ClientStats; use futures::future::join_all; use human_repr::HumanDuration; -use indicatif::{ProgressState, ProgressStyle}; use nym_task::ShutdownToken; use rand::{thread_rng, Rng}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -124,18 +123,6 @@ pub(crate) fn test_mixing_throughput( } let header_span = info_span!("header"); - header_span.pb_set_style( - &ProgressStyle::with_template( - "testing mixing throughput of this machine... {wide_msg} {elapsed}\n{wide_bar}", - )? - .with_key( - "elapsed", - |state: &ProgressState, writer: &mut dyn std::fmt::Write| { - let _ = writer.write_str(&format!("{}", state.elapsed().human_duration())); - }, - ) - .progress_chars("---"), - ); header_span.pb_start(); // Bit of a hack to show a full "-----" line underneath the header. diff --git a/nym-validator-rewarder/src/rewarder/ticketbook_issuance/verifier.rs b/nym-validator-rewarder/src/rewarder/ticketbook_issuance/verifier.rs index fd04e18de0..cb0964caf3 100644 --- a/nym-validator-rewarder/src/rewarder/ticketbook_issuance/verifier.rs +++ b/nym-validator-rewarder/src/rewarder/ticketbook_issuance/verifier.rs @@ -15,9 +15,9 @@ use nym_validator_client::ecash::models::{ CommitedDeposit, DepositId, IssuedTicketbooksChallengeCommitmentRequestBody, IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksDataRequestBody, IssuedTicketbooksDataResponse, IssuedTicketbooksDataResponseBody, IssuedTicketbooksForResponse, - SignableMessageBody, SignedMessage, }; use nym_validator_client::nyxd::AccountId; +use nym_validator_client::signable::{SignableMessageBody, SignedMessage}; use rand::distributions::{Distribution, WeightedIndex}; use rand::prelude::SliceRandom; use rand::thread_rng; diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index 43e550ddf0..a0471cbecb 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +## [v1.2.19] (2025-07-15) + +- Amended the buy section ([#5841]) +- Remove bity dir ([#5844]) + +[#5841]: https://github.com/nymtech/nym/pull/5841 +[#5844]: https://github.com/nymtech/nym/pull/5844 + ## [2025.7-tex] (2025-04-14) - Fix the mac build of the wallet ([#5684]) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index a53976a960..2b9ac7375d 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "NymWallet" -version = "1.2.18" +version = "1.2.19" dependencies = [ "async-trait", "base64 0.13.1", @@ -2541,18 +2541,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getset" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3586f256131df87204eb733da72e3d3eb4f343c639f4b7be279ac7c48baeafe" -dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "ghash" version = "0.5.1" @@ -4020,14 +4008,15 @@ dependencies = [ "cosmrs", "cosmwasm-std", "ecdsa", - "getset", "hex", "humantime-serde", + "nym-coconut-dkg-common", "nym-compact-ecash", "nym-config", "nym-contracts-common", "nym-credentials-interface", "nym-crypto", + "nym-ecash-signer-check-types", "nym-ecash-time", "nym-mixnet-contract-common", "nym-network-defaults", @@ -4170,6 +4159,21 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "nym-ecash-signer-check-types" +version = "0.1.0" +dependencies = [ + "nym-coconut-dkg-common", + "nym-crypto", + "semver", + "serde", + "thiserror 2.0.12", + "time", + "tracing", + "url", + "utoipa", +] + [[package]] name = "nym-ecash-time" version = "0.1.0" @@ -5413,28 +5417,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.100", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 732346df21..6702636f1c 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.2.18", + "version": "1.2.19", "license": "MIT", "main": "index.js", "scripts": { @@ -132,4 +132,4 @@ "webpack-merge": "^5.8.0" }, "private": false -} +} \ No newline at end of file diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index dadfa64d89..52cbac1d9e 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "NymWallet" -version = "1.2.18" +version = "1.2.19" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index b19963f4ce..75193606bf 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -40,7 +40,7 @@ }, "productName": "NymWallet", "mainBinaryName": "NymWallet", - "version": "1.2.18", + "version": "1.2.19", "identifier": "net.nymtech.wallet", "plugins": { "updater": { diff --git a/scripts/wireguard-exit-policy/exit-policy-tests.sh b/scripts/wireguard-exit-policy/exit-policy-tests.sh index c7127c8cdd..5ae10b177c 100644 --- a/scripts/wireguard-exit-policy/exit-policy-tests.sh +++ b/scripts/wireguard-exit-policy/exit-policy-tests.sh @@ -47,6 +47,7 @@ test_port_range_rules() { "8087-8088:tcp:Simplify Media" "8232-8233:tcp:Zcash" "8332-8333:tcp:Bitcoin" + "18080-18081:tcp:Monero" ) local total_failures=0 @@ -148,7 +149,7 @@ test_critical_services() { # Verify default reject rule exists test_default_reject_rule() { echo -e "${YELLOW}This test takes some time, do not quit the process${NC}" - echo + echo echo -e "${YELLOW}Testing Default Reject Rule...${NC}" # Try different patterns to detect the reject rule @@ -236,4 +237,4 @@ if [[ $EUID -ne 0 ]]; then fi # Run the tests -run_all_tests "$@" \ No newline at end of file +run_all_tests "$@" diff --git a/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh b/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh index 5b8c4bc2e5..bce3d09711 100644 --- a/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh +++ b/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh @@ -357,9 +357,13 @@ apply_port_allowlist() { ["Lightning"]="9735" ["NDMP"]="10000" ["OpenPGP"]="11371" + ["Monero"]="18080-18081" + ["MoneroRPC"]="18089" ["GoogleVoice"]="19294" ["EnsimControlPanel"]="19638" + ["DarkFiTor"]="25551" ["Minecraft"]="25565" + ["DarkFi"]="26661" ["Steam"]="27000-27050" ["ElectrumSSL"]="50002" ["MOSH"]="60000-61000" @@ -672,4 +676,4 @@ main() { esac } -main "$@" \ No newline at end of file +main "$@" diff --git a/service-providers/authenticator/Cargo.toml b/service-providers/authenticator/Cargo.toml index 0c4347bb8d..85e4dc7021 100644 --- a/service-providers/authenticator/Cargo.toml +++ b/service-providers/authenticator/Cargo.toml @@ -54,3 +54,6 @@ nym-wireguard-types = { path = "../../common/wireguard-types" } [dev-dependencies] mock_instant = "0.5.3" +time = { workspace = true } + +nym-wireguard = { path = "../../common/wireguard", features = ["mock"] } diff --git a/service-providers/authenticator/src/authenticator.rs b/service-providers/authenticator/src/authenticator.rs index 268d96c1ef..2790ddddcb 100644 --- a/service-providers/authenticator/src/authenticator.rs +++ b/service-providers/authenticator/src/authenticator.rs @@ -31,7 +31,7 @@ pub struct Authenticator { custom_topology_provider: Option>, custom_gateway_transceiver: Option>, wireguard_gateway_data: WireguardGatewayData, - ecash_verifier: Option>, + ecash_verifier: Arc, used_private_network_ips: Vec, shutdown: Option, on_start: Option>, @@ -42,13 +42,14 @@ impl Authenticator { config: Config, wireguard_gateway_data: WireguardGatewayData, used_private_network_ips: Vec, + ecash_verifier: Arc, ) -> Self { Self { config, wait_for_gateway: false, custom_topology_provider: None, custom_gateway_transceiver: None, - ecash_verifier: None, + ecash_verifier, wireguard_gateway_data, used_private_network_ips, shutdown: None, @@ -56,13 +57,6 @@ impl Authenticator { } } - #[must_use] - #[allow(unused)] - pub fn with_ecash_verifier(mut self, ecash_verifier: Arc) -> Self { - self.ecash_verifier = Some(ecash_verifier); - self - } - #[must_use] #[allow(unused)] pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self { diff --git a/service-providers/authenticator/src/cli/add_gateway.rs b/service-providers/authenticator/src/cli/add_gateway.rs deleted file mode 100644 index 397a8be01e..0000000000 --- a/service-providers/authenticator/src/cli/add_gateway.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_bin_common::output_format::OutputFormat; -use nym_client_core::cli_helpers::client_add_gateway::{add_gateway, CommonClientAddGatewayArgs}; - -#[derive(clap::Args)] -pub(crate) struct Args { - #[command(flatten)] - common_args: CommonClientAddGatewayArgs, - - #[arg(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -impl AsRef for Args { - fn as_ref(&self) -> &CommonClientAddGatewayArgs { - &self.common_args - } -} - -pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> { - let output = args.output; - let res = add_gateway::(args, None).await?; - - println!("{}", output.format(&res)); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/build_info.rs b/service-providers/authenticator/src/cli/build_info.rs deleted file mode 100644 index 7351753134..0000000000 --- a/service-providers/authenticator/src/cli/build_info.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use clap::Args; -use nym_bin_common::bin_info_owned; -use nym_bin_common::output_format::OutputFormat; - -#[derive(Args)] -pub(crate) struct BuildInfo { - #[arg(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -pub(crate) fn execute(args: BuildInfo) { - println!("{}", args.output.format(&bin_info_owned!())) -} diff --git a/service-providers/authenticator/src/cli/ecash/import_coin_index_signatures.rs b/service-providers/authenticator/src/cli/ecash/import_coin_index_signatures.rs deleted file mode 100644 index c3b1aa922e..0000000000 --- a/service-providers/authenticator/src/cli/ecash/import_coin_index_signatures.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_import_coin_index_signatures::{ - import_coin_index_signatures, CommonClientImportCoinIndexSignaturesArgs, -}; - -pub(crate) async fn execute( - args: CommonClientImportCoinIndexSignaturesArgs, -) -> Result<(), AuthenticatorError> { - import_coin_index_signatures::(args).await?; - println!("successfully imported coin index signatures!"); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/ecash/import_credential.rs b/service-providers/authenticator/src/cli/ecash/import_credential.rs deleted file mode 100644 index e525eac518..0000000000 --- a/service-providers/authenticator/src/cli/ecash/import_credential.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_import_credential::{ - import_credential, CommonClientImportTicketBookArgs, -}; - -pub async fn execute(args: CommonClientImportTicketBookArgs) -> Result<(), AuthenticatorError> { - import_credential::(args).await?; - println!("successfully imported credential!"); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/ecash/import_expiration_date_signatures.rs b/service-providers/authenticator/src/cli/ecash/import_expiration_date_signatures.rs deleted file mode 100644 index 7db61d5238..0000000000 --- a/service-providers/authenticator/src/cli/ecash/import_expiration_date_signatures.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_import_expiration_date_signatures::{ - import_expiration_date_signatures, CommonClientImportExpirationDateSignaturesArgs, -}; - -pub(crate) async fn execute( - args: CommonClientImportExpirationDateSignaturesArgs, -) -> Result<(), AuthenticatorError> { - import_expiration_date_signatures::(args).await?; - println!("successfully imported expiration date signatures!"); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/ecash/import_master_verification_key.rs b/service-providers/authenticator/src/cli/ecash/import_master_verification_key.rs deleted file mode 100644 index d53ddacf4c..0000000000 --- a/service-providers/authenticator/src/cli/ecash/import_master_verification_key.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_import_master_verification_key::{ - import_master_verification_key, CommonClientImportMasterVerificationKeyArgs, -}; - -pub(crate) async fn execute( - args: CommonClientImportMasterVerificationKeyArgs, -) -> Result<(), AuthenticatorError> { - import_master_verification_key::(args).await?; - println!("successfully imported master verification key!"); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/ecash/mod.rs b/service-providers/authenticator/src/cli/ecash/mod.rs deleted file mode 100644 index 9272ed013c..0000000000 --- a/service-providers/authenticator/src/cli/ecash/mod.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use clap::{Args, Subcommand}; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_import_coin_index_signatures::CommonClientImportCoinIndexSignaturesArgs; -use nym_client_core::cli_helpers::client_import_credential::CommonClientImportTicketBookArgs; -use nym_client_core::cli_helpers::client_import_expiration_date_signatures::CommonClientImportExpirationDateSignaturesArgs; -use nym_client_core::cli_helpers::client_import_master_verification_key::CommonClientImportMasterVerificationKeyArgs; - -pub(crate) mod import_coin_index_signatures; -pub(crate) mod import_credential; -pub(crate) mod import_expiration_date_signatures; -pub(crate) mod import_master_verification_key; -pub(crate) mod show_ticketbooks; - -#[derive(Args)] -#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] -pub struct Ecash { - #[clap(subcommand)] - pub command: EcashCommands, -} - -impl Ecash { - pub async fn execute(self) -> Result<(), AuthenticatorError> { - match self.command { - EcashCommands::ShowTicketBooks(args) => show_ticketbooks::execute(args).await?, - EcashCommands::ImportTicketBook(args) => import_credential::execute(args).await?, - EcashCommands::ImportCoinIndexSignatures(args) => { - import_coin_index_signatures::execute(args).await? - } - EcashCommands::ImportExpirationDateSignatures(args) => { - import_expiration_date_signatures::execute(args).await? - } - EcashCommands::ImportMasterVerificationKey(args) => { - import_master_verification_key::execute(args).await? - } - } - Ok(()) - } -} - -#[derive(Subcommand)] -pub enum EcashCommands { - /// Display information associated with the imported ticketbooks, - ShowTicketBooks(show_ticketbooks::Args), - - /// Import a pre-generated ticketbook - ImportTicketBook(CommonClientImportTicketBookArgs), - - /// Import coin index signatures needed for ticketbooks - ImportCoinIndexSignatures(CommonClientImportCoinIndexSignaturesArgs), - - /// Import expiration date signatures needed for ticketbooks - ImportExpirationDateSignatures(CommonClientImportExpirationDateSignaturesArgs), - - /// Import master verification key needed for ticketbooks - ImportMasterVerificationKey(CommonClientImportMasterVerificationKeyArgs), -} diff --git a/service-providers/authenticator/src/cli/ecash/show_ticketbooks.rs b/service-providers/authenticator/src/cli/ecash/show_ticketbooks.rs deleted file mode 100644 index ca31d39802..0000000000 --- a/service-providers/authenticator/src/cli/ecash/show_ticketbooks.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_bin_common::output_format::OutputFormat; -use nym_client_core::cli_helpers::client_show_ticketbooks::{ - show_ticketbooks, CommonShowTicketbooksArgs, -}; - -#[derive(clap::Args)] -pub(crate) struct Args { - #[command(flatten)] - common_args: CommonShowTicketbooksArgs, - - #[arg(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -impl AsRef for Args { - fn as_ref(&self) -> &CommonShowTicketbooksArgs { - &self.common_args - } -} - -pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> { - let output = args.output; - let res = show_ticketbooks::(args).await?; - - println!("{}", output.format(&res)); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/init.rs b/service-providers/authenticator/src/cli/init.rs deleted file mode 100644 index 217c3160d0..0000000000 --- a/service-providers/authenticator/src/cli/init.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::{override_config, CliAuthenticatorClient, OverrideConfig}; -use clap::Args; -use nym_authenticator::{ - config::{default_config_directory, default_config_filepath, default_data_directory, Config}, - error::AuthenticatorError, -}; -use nym_bin_common::output_format::OutputFormat; -use nym_client_core::cli_helpers::client_init::{ - initialise_client, CommonClientInitArgs, InitResultsWithConfig, InitialisableClient, -}; -use serde::Serialize; -use std::{fmt::Display, fs, path::PathBuf}; - -impl InitialisableClient for CliAuthenticatorClient { - type InitArgs = Init; - - fn initialise_storage_paths(id: &str) -> Result<(), Self::Error> { - fs::create_dir_all(default_data_directory(id))?; - fs::create_dir_all(default_config_directory(id))?; - Ok(()) - } - - fn default_config_path(id: &str) -> PathBuf { - default_config_filepath(id) - } - - fn construct_config(init_args: &Self::InitArgs) -> Self::Config { - override_config( - Config::new(&init_args.common_args.id), - OverrideConfig::from(init_args.clone()), - ) - } -} - -#[derive(Args, Clone, Debug)] -pub(crate) struct Init { - #[command(flatten)] - common_args: CommonClientInitArgs, - - #[clap(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -impl From for OverrideConfig { - fn from(init_config: Init) -> Self { - OverrideConfig { - nym_apis: init_config.common_args.nym_apis, - nyxd_urls: init_config.common_args.nyxd_urls, - enabled_credentials_mode: init_config.common_args.enabled_credentials_mode, - } - } -} - -impl AsRef for Init { - fn as_ref(&self) -> &CommonClientInitArgs { - &self.common_args - } -} - -#[derive(Debug, Serialize)] -pub struct InitResults { - #[serde(flatten)] - client_core: nym_client_core::init::types::InitResults, - client_address: String, -} - -impl InitResults { - fn new(res: InitResultsWithConfig) -> Self { - Self { - client_address: res.init_results.address.to_string(), - client_core: res.init_results, - } - } -} - -impl Display for InitResults { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "{}", self.client_core)?; - write!(f, "Address of this authenticator: {}", self.client_address) - } -} - -pub(crate) async fn execute(args: Init) -> Result<(), AuthenticatorError> { - eprintln!("Initialising client..."); - - let output = args.output; - let res = initialise_client::(args, None).await?; - - let init_results = InitResults::new(res); - println!("{}", output.format(&init_results)); - - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/list_gateways.rs b/service-providers/authenticator/src/cli/list_gateways.rs deleted file mode 100644 index 9297d5cd5b..0000000000 --- a/service-providers/authenticator/src/cli/list_gateways.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_bin_common::output_format::OutputFormat; -use nym_client_core::cli_helpers::client_list_gateways::{ - list_gateways, CommonClientListGatewaysArgs, -}; - -#[derive(clap::Args)] -pub(crate) struct Args { - #[command(flatten)] - common_args: CommonClientListGatewaysArgs, - - #[arg(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -impl AsRef for Args { - fn as_ref(&self) -> &CommonClientListGatewaysArgs { - &self.common_args - } -} - -pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> { - let output = args.output; - let res = list_gateways::(args).await?; - - println!("{}", output.format(&res)); - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/mod.rs b/service-providers/authenticator/src/cli/mod.rs deleted file mode 100644 index 08aca6f720..0000000000 --- a/service-providers/authenticator/src/cli/mod.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::ecash::Ecash; -use clap::{CommandFactory, Parser, Subcommand}; -use log::error; -use nym_authenticator::{ - config::{helpers::try_upgrade_config, BaseClientConfig, Config}, - error::AuthenticatorError, -}; -use nym_bin_common::bin_info; -use nym_bin_common::completions::{fig_generate, ArgShell}; -use nym_client_core::cli_helpers::CliClient; -use std::sync::OnceLock; - -mod add_gateway; -mod build_info; -pub mod ecash; -mod init; -mod list_gateways; -mod peer_handler; -mod request; -mod run; -mod sign; -mod switch_gateway; - -pub(crate) struct CliAuthenticatorClient; - -impl CliClient for CliAuthenticatorClient { - const NAME: &'static str = "authenticator"; - type Error = AuthenticatorError; - type Config = Config; - - async fn try_upgrade_outdated_config(id: &str) -> Result<(), Self::Error> { - try_upgrade_config(id).await - } - - async fn try_load_current_config(id: &str) -> Result { - try_load_current_config(id).await - } -} - -fn pretty_build_info_static() -> &'static str { - static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); - PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) -} - -#[derive(Parser)] -#[command(author = "Nymtech", version, about, long_version = pretty_build_info_static())] -pub(crate) struct Cli { - /// Path pointing to an env file that configures the client. - #[arg(short, long)] - pub(crate) config_env_file: Option, - - /// Flag used for disabling the printed banner in tty. - #[arg(long)] - pub(crate) no_banner: bool, - - #[command(subcommand)] - command: Commands, -} - -#[allow(clippy::large_enum_variant)] -#[derive(Subcommand)] -pub(crate) enum Commands { - /// Initialize an authenticator. Do this first! - Init(init::Init), - - /// Run the authenticator with the provided configuration and optionally override - /// parameters. - Run(run::Run), - - /// Make a dummy request to a running authenticator - Request(request::Request), - - /// Ecash-related functionalities - Ecash(Ecash), - - /// List all registered with gateways - ListGateways(list_gateways::Args), - - /// Add new gateway to this client - AddGateway(add_gateway::Args), - - /// Change the currently active gateway. Note that you must have already registered with the new gateway! - SwitchGateway(switch_gateway::Args), - - /// Sign to prove ownership of this authenticator - Sign(sign::Sign), - - /// Show build information of this binary - BuildInfo(build_info::BuildInfo), - - /// Generate shell completions - Completions(ArgShell), - - /// Generate Fig specification - GenerateFigSpec, -} - -// Configuration that can be overridden. -pub(crate) struct OverrideConfig { - nym_apis: Option>, - nyxd_urls: Option>, - enabled_credentials_mode: Option, -} - -pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config { - config - .with_optional_base_custom_env( - BaseClientConfig::with_custom_nym_apis, - args.nym_apis, - nym_network_defaults::var_names::NYM_API, - nym_config::parse_urls, - ) - .with_optional_base_custom_env( - BaseClientConfig::with_custom_nyxd, - args.nyxd_urls, - nym_network_defaults::var_names::NYXD, - nym_config::parse_urls, - ) - .with_optional_base( - BaseClientConfig::with_disabled_credentials, - args.enabled_credentials_mode.map(|b| !b), - ) -} - -pub(crate) async fn execute(args: Cli) -> Result<(), AuthenticatorError> { - let bin_name = "nym-authenticator"; - - match args.command { - Commands::Init(m) => init::execute(m).await?, - Commands::Run(m) => run::execute(&m).await?, - Commands::Request(r) => request::execute(&r).await?, - Commands::Ecash(ecash) => ecash.execute().await?, - Commands::ListGateways(args) => list_gateways::execute(args).await?, - Commands::AddGateway(args) => add_gateway::execute(args).await?, - Commands::SwitchGateway(args) => switch_gateway::execute(args).await?, - Commands::Sign(m) => sign::execute(&m).await?, - Commands::BuildInfo(m) => build_info::execute(m), - Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name), - Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name), - } - Ok(()) -} - -async fn try_load_current_config(id: &str) -> Result { - // try to load the config as is - if let Ok(cfg) = Config::read_from_default_path(id) { - return if !cfg.validate() { - Err(AuthenticatorError::ConfigValidationFailure) - } else { - Ok(cfg) - }; - } - - // we couldn't load it - try upgrading it from older revisions - try_upgrade_config(id).await?; - - let config = match Config::read_from_default_path(id) { - Ok(cfg) => cfg, - Err(err) => { - error!("Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})"); - return Err(AuthenticatorError::FailedToLoadConfig(id.to_string())); - } - }; - - if !config.validate() { - return Err(AuthenticatorError::ConfigValidationFailure); - } - - Ok(config) -} diff --git a/service-providers/authenticator/src/cli/peer_handler.rs b/service-providers/authenticator/src/cli/peer_handler.rs deleted file mode 100644 index 402c09815a..0000000000 --- a/service-providers/authenticator/src/cli/peer_handler.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_sdk::TaskClient; -use nym_wireguard::peer_controller::{ - AddPeerControlResponse, GetClientBandwidthControlResponse, PeerControlRequest, - QueryBandwidthControlResponse, QueryPeerControlResponse, RemovePeerControlResponse, -}; -use tokio::sync::mpsc; - -pub struct DummyHandler { - peer_rx: mpsc::Receiver, - task_client: TaskClient, -} - -impl DummyHandler { - pub fn new(peer_rx: mpsc::Receiver, task_client: TaskClient) -> Self { - DummyHandler { - peer_rx, - task_client, - } - } - - pub async fn run(mut self) { - while !self.task_client.is_shutdown() { - tokio::select! { - msg = self.peer_rx.recv() => { - if let Some(msg) = msg { - match msg { - PeerControlRequest::AddPeer { peer, client_id, response_tx } => { - log::info!("[DUMMY] Adding peer {peer:?} with client id {client_id:?}"); - response_tx.send(AddPeerControlResponse { success: true }).ok(); - } - PeerControlRequest::RemovePeer { key, response_tx } => { - log::info!("[DUMMY] Removing peer {key:?}"); - response_tx.send(RemovePeerControlResponse { success: true }).ok(); - } - PeerControlRequest::QueryPeer{key, response_tx} => { - log::info!("[DUMMY] Querying peer {key:?}"); - response_tx.send(QueryPeerControlResponse { success: true, peer: None }).ok(); - } - PeerControlRequest::QueryBandwidth{key, response_tx} => { - log::info!("[DUMMY] Querying bandwidth for peer {key:?}"); - response_tx.send(QueryBandwidthControlResponse { success: true, bandwidth_data: None }).ok(); - } - PeerControlRequest::GetClientBandwidth{key, response_tx} => { - log::info!("[DUMMY] Getting client bandwidth for peer {key:?}"); - response_tx.send(GetClientBandwidthControlResponse {client_bandwidth: None }).ok(); - } - } - } else { - break; - } - } - - _ = self.task_client.recv() => { - log::trace!("DummyHandler: Received shutdown"); - } - } - } - log::debug!("DummyHandler: Exiting"); - } -} diff --git a/service-providers/authenticator/src/cli/request.rs b/service-providers/authenticator/src/cli/request.rs deleted file mode 100644 index 9ceb3f6143..0000000000 --- a/service-providers/authenticator/src/cli/request.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::try_load_current_config; -use crate::cli::AuthenticatorError; -use crate::cli::{override_config, OverrideConfig}; -use clap::{Args, Subcommand}; -use nym_authenticator_requests::latest::{ - registration::{ClientMac, FinalMessage, GatewayClient, InitMessage, IpPair}, - request::{AuthenticatorRequest, AuthenticatorRequestData}, -}; -use nym_client_core::cli_helpers::client_run::CommonClientRunArgs; -use nym_sdk::mixnet::{MixnetMessageSender, Recipient, TransmissionLane}; -use nym_task::TaskHandle; -use nym_wireguard_types::PeerPublicKey; -use std::net::{Ipv4Addr, Ipv6Addr}; -use std::str::FromStr; -use std::time::Duration; -use tokio::time::sleep; - -#[allow(clippy::struct_excessive_bools)] -#[derive(Args, Clone)] -pub(crate) struct Request { - #[command(flatten)] - common_args: CommonClientRunArgs, - - #[command(subcommand)] - request: RequestType, - - authenticator_recipient: String, -} - -impl From for OverrideConfig { - fn from(request_config: Request) -> Self { - OverrideConfig { - nym_apis: None, - nyxd_urls: request_config.common_args.nyxd_urls, - enabled_credentials_mode: request_config.common_args.enabled_credentials_mode, - } - } -} - -#[derive(Clone, Subcommand)] -pub(crate) enum RequestType { - Initial(Initial), - Final(Final), - QueryBandwidth(QueryBandwidth), -} - -#[derive(Args, Clone, Debug)] -pub(crate) struct Initial { - pub_key: String, -} - -#[derive(Args, Clone, Debug)] -pub(crate) struct Final { - pub_key: String, - private_ipv4: String, - private_ipv6: String, - mac: String, -} - -#[derive(Args, Clone, Debug)] -pub(crate) struct QueryBandwidth { - pub_key: String, -} - -impl TryFrom for AuthenticatorRequestData { - type Error = AuthenticatorError; - - fn try_from(value: RequestType) -> Result { - let ret = match value { - RequestType::Initial(req) => AuthenticatorRequestData::Initial(InitMessage::new( - PeerPublicKey::from_str(&req.pub_key)?, - )), - RequestType::Final(req) => AuthenticatorRequestData::Final(Box::new(FinalMessage { - gateway_client: GatewayClient { - pub_key: PeerPublicKey::from_str(&req.pub_key)?, - private_ips: IpPair::new( - Ipv4Addr::from_str(&req.private_ipv4)?, - Ipv6Addr::from_str(&req.private_ipv6)?, - ), - mac: ClientMac::from_str(&req.mac)?, - }, - credential: None, - })), - RequestType::QueryBandwidth(req) => { - AuthenticatorRequestData::QueryBandwidth(PeerPublicKey::from_str(&req.pub_key)?) - } - }; - Ok(ret) - } -} - -pub(crate) async fn execute(args: &Request) -> Result<(), AuthenticatorError> { - let mut config = try_load_current_config(&args.common_args.id).await?; - config = override_config(config, OverrideConfig::from(args.clone())); - - let shutdown = TaskHandle::default(); - let mixnet_client = nym_authenticator::mixnet_client::create_mixnet_client( - &config.base, - shutdown.get_handle().named("nym_sdk::MixnetClient"), - None, - None, - false, - &config.storage_paths.common_paths, - ) - .await?; - - let request_data = AuthenticatorRequestData::try_from(args.request.clone())?; - let authenticator_recipient = Recipient::from_str(&args.authenticator_recipient)?; - let (request, _) = match request_data { - AuthenticatorRequestData::Initial(init_message) => { - AuthenticatorRequest::new_initial_request(init_message) - } - AuthenticatorRequestData::Final(final_message) => { - AuthenticatorRequest::new_final_request(*final_message) - } - AuthenticatorRequestData::QueryBandwidth(query_message) => { - AuthenticatorRequest::new_query_request(query_message) - } - AuthenticatorRequestData::TopUpBandwidth(top_up_message) => { - AuthenticatorRequest::new_topup_request(*top_up_message) - } - }; - mixnet_client - .split_sender() - .send(nym_sdk::mixnet::InputMessage::new_regular( - authenticator_recipient, - request.to_bytes().unwrap(), - TransmissionLane::General, - None, - )) - .await - .map_err(|source| AuthenticatorError::FailedToSendPacketToMixnet { - source: Box::new(source), - })?; - - log::info!("Sent request, sleeping 60 seconds or until killed"); - sleep(Duration::from_secs(60)).await; - - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/run.rs b/service-providers/authenticator/src/cli/run.rs deleted file mode 100644 index d6b273f5b1..0000000000 --- a/service-providers/authenticator/src/cli/run.rs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::peer_handler::DummyHandler; -use crate::cli::try_load_current_config; -use crate::cli::{override_config, OverrideConfig}; -use clap::Args; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_run::CommonClientRunArgs; -use nym_crypto::asymmetric::x25519::KeyPair; -use nym_task::TaskHandle; -use nym_wireguard::WireguardGatewayData; -use rand::rngs::OsRng; -use std::sync::Arc; - -#[allow(clippy::struct_excessive_bools)] -#[derive(Args, Clone)] -pub(crate) struct Run { - #[command(flatten)] - common_args: CommonClientRunArgs, -} - -impl From for OverrideConfig { - fn from(run_config: Run) -> Self { - OverrideConfig { - nym_apis: None, - nyxd_urls: run_config.common_args.nyxd_urls, - enabled_credentials_mode: run_config.common_args.enabled_credentials_mode, - } - } -} - -pub(crate) async fn execute(args: &Run) -> Result<(), AuthenticatorError> { - let mut config = try_load_current_config(&args.common_args.id).await?; - config = override_config(config, OverrideConfig::from(args.clone())); - log::debug!("Using config: {config:#?}"); - - log::info!("Starting authenticator service provider"); - let (wireguard_gateway_data, peer_rx) = WireguardGatewayData::new( - config.authenticator.clone().into(), - Arc::new(KeyPair::new(&mut OsRng)), - ); - let task_handler = TaskHandle::default(); - let handler = DummyHandler::new(peer_rx, task_handler.fork("peer_handler")); - tokio::spawn(async move { - handler.run().await; - }); - - let mut server = nym_authenticator::Authenticator::new(config, wireguard_gateway_data, vec![]); - if let Some(custom_mixnet) = &args.common_args.custom_mixnet { - server = server.with_stored_topology(custom_mixnet)? - } - - server.run_service_provider().await -} diff --git a/service-providers/authenticator/src/cli/sign.rs b/service-providers/authenticator/src/cli/sign.rs deleted file mode 100644 index 30bd8ed1d1..0000000000 --- a/service-providers/authenticator/src/cli/sign.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::try_load_current_config; -use clap::Args; -use nym_authenticator::error::AuthenticatorError; -use nym_bin_common::output_format::OutputFormat; -use nym_client_core::client::key_manager::persistence::OnDiskKeys; -use nym_client_core::error::ClientCoreError; -use nym_crypto::asymmetric::ed25519; -use nym_types::helpers::ConsoleSigningOutput; - -#[derive(Args, Clone)] -pub(crate) struct Sign { - /// The id of the mixnode you want to sign with - #[arg(long)] - id: String, - - /// Signs a transaction-specific payload, that is going to be sent to the smart contract, with your identity key - #[arg(long)] - contract_msg: String, - - #[arg(short, long, default_value_t = OutputFormat::default())] - output: OutputFormat, -} - -fn print_signed_contract_msg( - private_key: &ed25519::PrivateKey, - raw_msg: &str, - output: OutputFormat, -) { - let trimmed = raw_msg.trim(); - eprintln!(">>> attempting to sign {trimmed}"); - - let Ok(decoded) = bs58::decode(trimmed).into_vec() else { - println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters"); - return; - }; - - eprintln!(">>> decoding the message..."); - - // we don't really care about what particular information is embedded inside of it, - // we just want to know if user correctly copied the string, i.e. whether it's a valid bs58 encoded json - if serde_json::from_slice::(&decoded).is_err() { - println!("it seems you have incorrectly copied the message to sign. Make sure you didn't accidentally skip any characters"); - return; - }; - - // if this is a valid json, it MUST be a valid string - let decoded_string = String::from_utf8(decoded.clone()).unwrap(); - let signature = private_key.sign(&decoded).to_base58_string(); - - let sign_output = ConsoleSigningOutput::new(decoded_string, signature); - println!("{}", output.format(&sign_output)); -} - -pub(crate) async fn execute(args: &Sign) -> Result<(), AuthenticatorError> { - let config = try_load_current_config(&args.id).await?; - - let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys); - let identity_keypair = key_store.load_identity_keypair().map_err(|source| { - AuthenticatorError::ClientCoreError(ClientCoreError::KeyStoreError { - source: Box::new(source), - }) - })?; - - print_signed_contract_msg( - identity_keypair.private_key(), - &args.contract_msg, - args.output, - ); - - Ok(()) -} diff --git a/service-providers/authenticator/src/cli/switch_gateway.rs b/service-providers/authenticator/src/cli/switch_gateway.rs deleted file mode 100644 index 24ce8d48e2..0000000000 --- a/service-providers/authenticator/src/cli/switch_gateway.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::cli::CliAuthenticatorClient; -use nym_authenticator::error::AuthenticatorError; -use nym_client_core::cli_helpers::client_switch_gateway::{ - switch_gateway, CommonClientSwitchGatewaysArgs, -}; - -#[derive(clap::Args, Clone, Debug)] -pub struct Args { - #[command(flatten)] - common_args: CommonClientSwitchGatewaysArgs, -} - -impl AsRef for Args { - fn as_ref(&self) -> &CommonClientSwitchGatewaysArgs { - &self.common_args - } -} - -pub(crate) async fn execute(args: Args) -> Result<(), AuthenticatorError> { - switch_gateway::(args).await -} diff --git a/service-providers/authenticator/src/error.rs b/service-providers/authenticator/src/error.rs index d6672a4076..7774fc1e48 100644 --- a/service-providers/authenticator/src/error.rs +++ b/service-providers/authenticator/src/error.rs @@ -17,6 +17,9 @@ pub enum AuthenticatorError { #[error("{0}")] CredentialVerificationError(#[from] nym_credential_verification::Error), + #[error("invalid credential type")] + InvalidCredentialType, + #[error("the entity wrapping the network requester has disconnected")] DisconnectedParent, @@ -77,13 +80,7 @@ pub enum AuthenticatorError { #[error("peers can't be interacted with anymore")] PeerInteractionStopped, - #[error("operation is not supported")] - UnsupportedOperation, - - #[error("operation unavailable for older client")] - OldClient, - - #[error("storage should have the requested bandwidht entry")] + #[error("storage should have the requested bandwidth entry")] MissingClientBandwidthEntry, #[error("unknown version number")] @@ -103,6 +100,9 @@ pub enum AuthenticatorError { #[error("{0}")] RecipientFormatting(#[from] nym_sdk::mixnet::RecipientFormattingError), + + #[error("no credential received")] + NoCredentialReceived, } pub type Result = std::result::Result; diff --git a/service-providers/authenticator/src/main.rs b/service-providers/authenticator/src/main.rs deleted file mode 100644 index 0883e64a99..0000000000 --- a/service-providers/authenticator/src/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2024 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -mod cli; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - use clap::Parser; - - let args = cli::Cli::parse(); - nym_bin_common::logging::setup_tracing_logger(); - nym_network_defaults::setup_env(args.config_env_file.as_ref()); - - if !args.no_banner { - nym_bin_common::logging::maybe_print_banner(clap::crate_name!(), clap::crate_version!()); - } - - cli::execute(args).await?; - Ok(()) -} diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index b28df22982..b8a7221d2e 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -23,13 +23,15 @@ use nym_authenticator_requests::{ }, v1, v2, v3, v4, v5, CURRENT_VERSION, }; +use nym_credential_verification::ecash::traits::EcashManager; use nym_credential_verification::{ - bandwidth_storage_manager::BandwidthStorageManager, ecash::EcashManager, - BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, + bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig, + ClientBandwidth, CredentialVerifier, }; -use nym_credentials_interface::CredentialSpendingData; +use nym_credentials_interface::{CredentialSpendingData, TicketType}; use nym_crypto::asymmetric::x25519::KeyPair; use nym_gateway_requests::models::CredentialSpendingRequest; +use nym_gateway_storage::models::PersistedBandwidth; use nym_sdk::mixnet::{ AnonymousSenderTag, InputMessage, MixnetMessageSender, Recipient, TransmissionLane, }; @@ -74,7 +76,7 @@ pub(crate) struct MixnetListener { pub(crate) peer_manager: PeerManager, - pub(crate) ecash_verifier: Option>, + pub(crate) ecash_verifier: Arc, pub(crate) timeout_check_interval: IntervalStream, @@ -88,7 +90,7 @@ impl MixnetListener { wireguard_gateway_data: WireguardGatewayData, mixnet_client: nym_sdk::mixnet::MixnetClient, task_handle: TaskHandle, - ecash_verifier: Option>, + ecash_verifier: Arc, ) -> Self { let timeout_check_interval = IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK)); @@ -500,38 +502,37 @@ impl MixnetListener { 128, )); - // If gateway does ecash verification and client sends a credential, we do the additional - // credential verification. Later this will become mandatory. - if let (Some(ecash_verifier), Some(credential)) = - (self.ecash_verifier.clone(), final_message.credential()) + let Some(credential) = final_message.credential() else { + return Err(AuthenticatorError::NoCredentialReceived); + }; + let client_id = self + .ecash_verifier + .storage() + .insert_wireguard_peer( + &peer, + TicketType::try_from_encoded(credential.payment.t_type) + .map_err(|_| AuthenticatorError::InvalidCredentialType)? + .into(), + ) + .await?; + if let Err(e) = + credential_verification(self.ecash_verifier.clone(), credential, client_id).await { - let client_id = ecash_verifier + self.ecash_verifier .storage() - .insert_wireguard_peer(&peer, true) - .await? - .ok_or(AuthenticatorError::InternalError( - "peer with ticket shouldn't have been used before without a ticket".to_string(), - ))?; - if let Err(e) = - Self::credential_verification(ecash_verifier.clone(), credential, client_id).await - { - ecash_verifier - .storage() - .remove_wireguard_peer(&peer.public_key.to_string()) - .await?; - return Err(e); - } - let public_key = peer.public_key.to_string(); - if let Err(e) = self.peer_manager.add_peer(peer, Some(client_id)).await { - ecash_verifier - .storage() - .remove_wireguard_peer(&public_key) - .await?; - return Err(e); - } - } else { - self.peer_manager.add_peer(peer, None).await?; + .remove_wireguard_peer(&peer.public_key.to_string()) + .await?; + return Err(e); } + let public_key = peer.public_key.to_string(); + if let Err(e) = self.peer_manager.add_peer(peer).await { + self.ecash_verifier + .storage() + .remove_wireguard_peer(&public_key) + .await?; + return Err(e); + } + registred_and_free .registration_in_progres .remove(&final_message.pub_key()); @@ -596,37 +597,6 @@ impl MixnetListener { Ok((bytes, reply_to)) } - async fn credential_verification( - ecash_verifier: Arc, - credential: CredentialSpendingData, - client_id: i64, - ) -> Result { - ecash_verifier - .storage() - .create_bandwidth_entry(client_id) - .await?; - let bandwidth = ecash_verifier - .storage() - .get_available_bandwidth(client_id) - .await? - .ok_or(AuthenticatorError::InternalError( - "bandwidth entry should have just been created".to_string(), - ))?; - let client_bandwidth = ClientBandwidth::new(bandwidth.into()); - let mut verifier = CredentialVerifier::new( - CredentialSpendingRequest::new(credential), - ecash_verifier.clone(), - BandwidthStorageManager::new( - ecash_verifier.storage().clone(), - client_bandwidth, - client_id, - BandwidthFlushingBehaviourConfig::default(), - true, - ), - ); - Ok(verifier.verify().await?) - } - async fn on_query_bandwidth_request( &mut self, msg: Box, @@ -634,12 +604,12 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let bandwidth_data = self.peer_manager.query_bandwidth(msg).await?; + let bandwidth_data = self.peer_manager.query_bandwidth(msg.pub_key()).await?; let bytes = match AuthenticatorVersion::from(protocol) { AuthenticatorVersion::V1 => { v1::response::AuthenticatorResponse::new_remaining_bandwidth( bandwidth_data.map(|data| v1::registration::RemainingBandwidthData { - available_bandwidth: data.available_bandwidth as u64, + available_bandwidth: data as u64, suspended: false, }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, @@ -652,8 +622,10 @@ impl MixnetListener { } AuthenticatorVersion::V2 => { v2::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|data| v2::registration::RemainingBandwidthData { - available_bandwidth: data.available_bandwidth, + bandwidth_data.map(|available_bandwidth| { + v2::registration::RemainingBandwidthData { + available_bandwidth, + } }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -665,8 +637,10 @@ impl MixnetListener { } AuthenticatorVersion::V3 => { v3::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|data| v3::registration::RemainingBandwidthData { - available_bandwidth: data.available_bandwidth, + bandwidth_data.map(|available_bandwidth| { + v3::registration::RemainingBandwidthData { + available_bandwidth, + } }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -678,8 +652,10 @@ impl MixnetListener { } AuthenticatorVersion::V4 => { v4::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|data| v4::registration::RemainingBandwidthData { - available_bandwidth: data.available_bandwidth, + bandwidth_data.map(|available_bandwidth| { + v4::registration::RemainingBandwidthData { + available_bandwidth, + } }), reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?, request_id, @@ -691,8 +667,10 @@ impl MixnetListener { } AuthenticatorVersion::V5 => { v5::response::AuthenticatorResponse::new_remaining_bandwidth( - bandwidth_data.map(|data| v5::registration::RemainingBandwidthData { - available_bandwidth: data.available_bandwidth, + bandwidth_data.map(|available_bandwidth| { + v5::registration::RemainingBandwidthData { + available_bandwidth, + } }), request_id, ) @@ -713,17 +691,13 @@ impl MixnetListener { request_id: u64, reply_to: Option, ) -> AuthenticatorHandleResult { - let Some(ecash_verifier) = self.ecash_verifier.clone() else { - return Err(AuthenticatorError::UnsupportedOperation); - }; - - let client_id = ecash_verifier + let client_id = self + .ecash_verifier .storage() .get_wireguard_peer(&msg.pub_key().to_string()) .await? .ok_or(AuthenticatorError::MissingClientBandwidthEntry)? - .client_id - .ok_or(AuthenticatorError::OldClient)?; + .client_id; let client_bandwidth = self .peer_manager .query_client_bandwidth(msg.pub_key()) @@ -737,9 +711,9 @@ impl MixnetListener { let credential = msg.credential(); let mut verifier = CredentialVerifier::new( CredentialSpendingRequest::new(credential.clone()), - ecash_verifier.clone(), + self.ecash_verifier.clone(), BandwidthStorageManager::new( - ecash_verifier.storage().clone(), + self.ecash_verifier.storage(), client_bandwidth, client_id, BandwidthFlushingBehaviourConfig::default(), @@ -907,6 +881,45 @@ impl MixnetListener { } } +pub async fn credential_storage_preparation( + ecash_verifier: Arc, + client_id: i64, +) -> Result { + ecash_verifier + .storage() + .create_bandwidth_entry(client_id) + .await?; + let bandwidth = ecash_verifier + .storage() + .get_available_bandwidth(client_id) + .await? + .ok_or(AuthenticatorError::InternalError( + "bandwidth entry should have just been created".to_string(), + ))?; + Ok(bandwidth) +} + +async fn credential_verification( + ecash_verifier: Arc, + credential: CredentialSpendingData, + client_id: i64, +) -> Result { + let bandwidth = credential_storage_preparation(ecash_verifier.clone(), client_id).await?; + let client_bandwidth = ClientBandwidth::new(bandwidth.into()); + let mut verifier = CredentialVerifier::new( + CredentialSpendingRequest::new(credential), + ecash_verifier.clone(), + BandwidthStorageManager::new( + ecash_verifier.storage(), + client_bandwidth, + client_id, + BandwidthFlushingBehaviourConfig::default(), + true, + ), + ); + Ok(verifier.verify().await?) +} + fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result { let request_version = *reconstructed .message diff --git a/service-providers/authenticator/src/peer_manager.rs b/service-providers/authenticator/src/peer_manager.rs index 2cb5ac137b..3d4c86a95d 100644 --- a/service-providers/authenticator/src/peer_manager.rs +++ b/service-providers/authenticator/src/peer_manager.rs @@ -4,15 +4,11 @@ use crate::error::*; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::channel::oneshot; -use nym_authenticator_requests::{ - latest::registration::{GatewayClient, RemainingBandwidthData}, - traits::QueryBandwidthMessage, -}; use nym_credential_verification::ClientBandwidth; use nym_wireguard::{ peer_controller::{ AddPeerControlResponse, GetClientBandwidthControlResponse, PeerControlRequest, - QueryBandwidthControlResponse, QueryPeerControlResponse, RemovePeerControlResponse, + QueryPeerControlResponse, RemovePeerControlResponse, }, WireguardGatewayData, }; @@ -28,13 +24,9 @@ impl PeerManager { wireguard_gateway_data, } } - pub async fn add_peer(&mut self, peer: Peer, client_id: Option) -> Result<()> { + pub async fn add_peer(&mut self, peer: Peer) -> Result<()> { let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::AddPeer { - peer, - client_id, - response_tx, - }; + let msg = PeerControlRequest::AddPeer { peer, response_tx }; self.wireguard_gateway_data .peer_tx() .send(msg) @@ -52,8 +44,8 @@ impl PeerManager { Ok(()) } - pub async fn _remove_peer(&mut self, client: &GatewayClient) -> Result<()> { - let key = Key::new(client.pub_key().to_bytes()); + pub async fn _remove_peer(&mut self, pub_key: PeerPublicKey) -> Result<()> { + let key = Key::new(pub_key.to_bytes()); let (response_tx, response_rx) = oneshot::channel(); let msg = PeerControlRequest::RemovePeer { key, response_tx }; self.wireguard_gateway_data @@ -63,7 +55,7 @@ impl PeerManager { .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; let RemovePeerControlResponse { success } = response_rx.await.map_err(|_| { - AuthenticatorError::InternalError("no response for add peer".to_string()) + AuthenticatorError::InternalError("no response for remove peer".to_string()) })?; if !success { return Err(AuthenticatorError::InternalError( @@ -94,31 +86,13 @@ impl PeerManager { Ok(peer) } - pub async fn query_bandwidth( - &mut self, - msg: Box, - ) -> Result> { - let key = Key::new(msg.pub_key().to_bytes()); - let (response_tx, response_rx) = oneshot::channel(); - let msg = PeerControlRequest::QueryBandwidth { key, response_tx }; - self.wireguard_gateway_data - .peer_tx() - .send(msg) - .await - .map_err(|_| AuthenticatorError::PeerInteractionStopped)?; - - let QueryBandwidthControlResponse { - success, - bandwidth_data, - } = response_rx.await.map_err(|_| { - AuthenticatorError::InternalError("no response for query bandwidth".to_string()) - })?; - if !success { - return Err(AuthenticatorError::InternalError( - "querying bandwidth could not be performed".to_string(), - )); - } - Ok(bandwidth_data) + pub async fn query_bandwidth(&mut self, public_key: PeerPublicKey) -> Result> { + let res = if let Some(client_bandwidth) = self.query_client_bandwidth(public_key).await? { + Some(client_bandwidth.available().await) + } else { + None + }; + Ok(res) } pub async fn query_client_bandwidth( @@ -143,3 +117,243 @@ impl PeerManager { Ok(client_bandwidth) } } + +#[cfg(test)] +mod tests { + use std::{str::FromStr, sync::Arc}; + + use nym_credential_verification::{ + bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager, + }; + use nym_credentials_interface::Bandwidth; + use nym_crypto::asymmetric::x25519::KeyPair; + use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage}; + use nym_wireguard::peer_controller::{start_controller, stop_controller}; + use rand::rngs::OsRng; + use time::{Duration, OffsetDateTime}; + use tokio::sync::RwLock; + + use crate::{config::Authenticator, mixnet_listener::credential_storage_preparation}; + + use super::*; + + #[tokio::test] + async fn add_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + let peer = Peer::default(); + let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); + + assert!(peer_manager.add_peer(peer.clone()).await.is_err()); + + let client_id = storage + .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + assert!(peer_manager.add_peer(peer.clone()).await.is_err()); + + credential_storage_preparation(Arc::new(ecash_manager), client_id) + .await + .unwrap(); + peer_manager.add_peer(peer.clone()).await.unwrap(); + + stop_controller(task_manager).await; + } + + async fn helper_add_peer( + storage: &Arc>, + peer_manager: &mut PeerManager, + ) -> i64 { + let peer = Peer::default(); + let ecash_manager = MockEcashManager::new(Box::new(storage.clone())); + let client_id = storage + .insert_wireguard_peer(&peer, FromStr::from_str("entry_wireguard").unwrap()) + .await + .unwrap(); + credential_storage_preparation(Arc::new(ecash_manager), client_id) + .await + .unwrap(); + peer_manager.add_peer(peer.clone()).await.unwrap(); + + client_id + } + + #[tokio::test] + async fn remove_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + helper_add_peer(&storage, &mut peer_manager).await; + peer_manager._remove_peer(public_key).await.unwrap(); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_peer() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager.query_peer(public_key).await.unwrap().is_none()); + + helper_add_peer(&storage, &mut peer_manager).await; + let peer = peer_manager.query_peer(public_key).await.unwrap().unwrap(); + assert_eq!(peer.public_key, key); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager + .query_bandwidth(public_key) + .await + .unwrap() + .is_none()); + + helper_add_peer(&storage, &mut peer_manager).await; + let available_bandwidth = peer_manager + .query_bandwidth(public_key) + .await + .unwrap() + .unwrap(); + assert_eq!(available_bandwidth, 0); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn query_client_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + assert!(peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap() + .is_none()); + + helper_add_peer(&storage, &mut peer_manager).await; + let available_bandwidth = peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap() + .unwrap() + .available() + .await; + assert_eq!(available_bandwidth, 0); + + stop_controller(task_manager).await; + } + + #[tokio::test] + async fn increase_decrease_bandwidth() { + let (wireguard_data, request_rx) = WireguardGatewayData::new( + Authenticator::default().into(), + Arc::new(KeyPair::new(&mut OsRng)), + ); + let mut peer_manager = PeerManager::new(wireguard_data); + let key = Key::default(); + let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap(); + let top_up = 42; + let consume = 4; + let (storage, task_manager) = start_controller( + peer_manager.wireguard_gateway_data.peer_tx().clone(), + request_rx, + ); + + let client_id = helper_add_peer(&storage, &mut peer_manager).await; + let client_bandwidth = peer_manager + .query_client_bandwidth(public_key) + .await + .unwrap() + .unwrap(); + + let mut bw_manager = BandwidthStorageManager::new( + Box::new(storage), + client_bandwidth.clone(), + client_id, + Default::default(), + true, + ); + bw_manager + .increase_bandwidth( + Bandwidth::new_unchecked(top_up as u64), + OffsetDateTime::now_utc() + .checked_add(Duration::minutes(1)) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(client_bandwidth.available().await, top_up); + assert_eq!( + peer_manager + .query_bandwidth(public_key) + .await + .unwrap() + .unwrap(), + top_up + ); + + bw_manager.try_use_bandwidth(consume).await.unwrap(); + let remaining = top_up - consume; + assert_eq!(client_bandwidth.available().await, remaining); + assert_eq!( + peer_manager + .query_bandwidth(public_key) + .await + .unwrap() + .unwrap(), + remaining + ); + + stop_controller(task_manager).await; + } +} diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 1ee3da46c4..82570fa878 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-network-requester" license = "GPL-3.0" -version = "1.1.60" +version = "1.1.61" authors.workspace = true edition.workspace = true rust-version = "1.70" diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000000..8ebb62df5f --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,18 @@ +sonar.projectKey=nymtech_nym +sonar.organization=nymtech + + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=nym +#sonar.projectVersion=1.0 + + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 + +sonar.c.file.suffixes=- +sonar.cpp.file.suffixes=- +sonar.objc.file.suffixes=- diff --git a/sqlx-pool-guard/src/lib.rs b/sqlx-pool-guard/src/lib.rs index 3dd15ee6c7..3e16683de3 100644 --- a/sqlx-pool-guard/src/lib.rs +++ b/sqlx-pool-guard/src/lib.rs @@ -39,7 +39,12 @@ pub struct SqlitePoolGuard { impl SqlitePoolGuard { /// Create new instance providing path to database and connection pool - pub fn new(database_path: PathBuf, connection_pool: sqlx::SqlitePool) -> Self { + pub fn new(connection_pool: sqlx::SqlitePool) -> Self { + let database_path = connection_pool + .connect_options() + .get_filename() + .to_path_buf(); + Self { database_path, connection_pool, @@ -160,7 +165,7 @@ mod tests { .await .unwrap(); - let guard = SqlitePoolGuard::new(database_path.clone(), connection_pool); + let guard = SqlitePoolGuard::new(connection_pool); assert!( guard .wait_for_db_files_close() diff --git a/sqlx-pool-guard/src/windows.rs b/sqlx-pool-guard/src/windows.rs index 799eac2abe..6d6d1c695f 100644 --- a/sqlx-pool-guard/src/windows.rs +++ b/sqlx-pool-guard/src/windows.rs @@ -31,40 +31,16 @@ const SYSTEM_HANDLE_INFORMATION_CLASS: SYSTEM_INFORMATION_CLASS = SYSTEM_INFORMA /// The number is based on what I observe on a pretty standard Windows 11 const SYSTEM_HANDLE_INFORMATION_INITIAL_SIZE: usize = 2_500_000; +/// Max retry attempts for querying system handle information before giving up +const MAX_RETRY_ATTEMPTS: u32 = 5; + /// Check if there are no open handles to the given files. /// /// Uses undocumented NT API to obtain open handles on the system. /// See: https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/get-all-open-handles-and-kernel-object-address-from-userland pub async fn check_files_closed(file_paths: &[&Path]) -> io::Result { let current_pid = unsafe { GetCurrentProcessId() }; - - // Allocate info struct on heap with some initial value - let mut reserved_memory = SYSTEM_HANDLE_INFORMATION_INITIAL_SIZE; - let mut handle_table_info = HeapGuard::::new(reserved_memory)?; - - // Request system handle information - let mut status: NTSTATUS = NTSTATUS::default(); - let mut return_len = reserved_memory as u32; - for _ in 0..2 { - status = unsafe { - NtQuerySystemInformation( - SYSTEM_HANDLE_INFORMATION_CLASS, - handle_table_info.as_mut_ptr() as _, - return_len, - &mut return_len, - ) - }; - - // Buffer is too small, resize memory and retry again. - if status == STATUS_INFO_LENGTH_MISMATCH { - tracing::trace!("Buffer is too small ({reserved_memory}), resizing to {return_len}"); - reserved_memory = return_len as usize; - handle_table_info.reallocate(reserved_memory)?; - } else { - break; - } - } - status.ok()?; + let handle_table_info = query_system_handle_table()?; // Convert returned data into slice let num_handles = unsafe { (*handle_table_info.inner).number_of_handles }; @@ -106,6 +82,43 @@ pub async fn check_files_closed(file_paths: &[&Path]) -> io::Result { Ok(true) } +fn query_system_handle_table() -> io::Result> { + // Allocate info struct on heap with some initial value + let mut reserved_memory = SYSTEM_HANDLE_INFORMATION_INITIAL_SIZE; + let mut handle_table_info = HeapGuard::::new(reserved_memory)?; + + // Request system handle information + let mut status: NTSTATUS = NTSTATUS::default(); + for _ in 0..MAX_RETRY_ATTEMPTS { + let mut return_len = reserved_memory as u32; + status = unsafe { + NtQuerySystemInformation( + SYSTEM_HANDLE_INFORMATION_CLASS, + handle_table_info.as_mut_ptr() as _, + return_len, + &mut return_len, + ) + }; + + // Buffer is too small, resize memory and retry again. + if status == STATUS_INFO_LENGTH_MISMATCH { + // Allocate a bit more memory since the size of table can change between calls + let resize_to = (return_len as usize) * 3 / 2; + + tracing::trace!( + "Buffer is too small ({reserved_memory}), returned length: {return_len}, resizing buffer to: {resize_to}" + ); + + reserved_memory = resize_to; + handle_table_info.reallocate(reserved_memory)?; + } else { + break; + } + } + + Ok(status.ok().map(|_| handle_table_info)?) +} + #[repr(C)] #[derive(Copy, Clone)] struct SystemHandleInformation { diff --git a/tools/internal/testnet-manager/src/manager/dkg_skip.rs b/tools/internal/testnet-manager/src/manager/dkg_skip.rs index ea33af03fb..9f87a468fa 100644 --- a/tools/internal/testnet-manager/src/manager/dkg_skip.rs +++ b/tools/internal/testnet-manager/src/manager/dkg_skip.rs @@ -412,10 +412,10 @@ impl NetworkManager { let mut receivers = Vec::new(); for signer in &ctx.ecash_signers { - // send 101nym to the admin + // send 250nym to the admin receivers.push(( signer.data.cosmos_account.address.clone(), - admin.mix_coins(101_000000), + admin.mix_coins(250_000000), )) } diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 6967b19afb..6fd47a0d13 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.59" +version = "1.1.60" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index a9c760c5fe..d84276ed3d 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.24" +version = "0.1.25" authors.workspace = true repository.workspace = true homepage.workspace = true