Compare commits

..

4 Commits

Author SHA1 Message Date
Tommy Verrall d39ec4a048 more white space 2025-07-15 16:06:46 +02:00
Tommy Verrall c03c580b1f indentation 2025-07-15 16:05:51 +02:00
Tommy Verrall e0cc094ac8 revert-revert-revert 2025-07-15 15:54:12 +02:00
Tommy Verrall f1fcaa309b try changing the workflow 2025-07-15 15:39:21 +02:00
191 changed files with 3099 additions and 11281 deletions
@@ -38,14 +38,15 @@ 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
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
run: |
echo "RUSTFLAGS=--cfg tokio_unstable" >> $GITHUB_ENV
echo "CARGO_FEATURES=--features console" >> $GITHUB_ENV
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
@@ -102,6 +103,7 @@ 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
+12 -6
View File
@@ -49,6 +49,8 @@ jobs:
run: |
curl -L0 https://www.ssl.com/download/codesigntool-for-linux-and-macos/ -o codesigntool.zip
unzip codesigntool.zip
chmod +x CodeSignTool.sh
- name: Get EV certificate credential id
working-directory: nym-wallet/src-tauri
if: ${{ inputs.sign }}
@@ -56,6 +58,7 @@ jobs:
shell: bash
run: |
echo "SSL_COM_CREDENTIAL_ID=$(./CodeSignTool.sh get_credential_ids -username=${{ secrets.SSL_COM_USERNAME }} -password=${{ secrets.SSL_COM_PASSWORD }} | sed -n '1!p' | sed 's/- //')" >> "$GITHUB_OUTPUT"
- name: Add custom sign command to tauri.conf.json
working-directory: nym-wallet/src-tauri
if: ${{ inputs.sign }}
@@ -79,6 +82,7 @@ jobs:
]
}
}' tauri.conf.json
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
@@ -93,12 +97,14 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
SSL_COM_USERNAME: ${{ inputs.sign && secrets.SSL_COM_USERNAME }}
SSL_COM_PASSWORD: ${{ inputs.sign && secrets.SSL_COM_PASSWORD }}
SSL_COM_CREDENTIAL_ID: ${{ inputs.sign && steps.get_credential_ids.outputs.SSL_COM_CREDENTIAL_ID }}
SSL_COM_TOTP_SECRET: ${{ inputs.sign && secrets.SSL_COM_TOTP_SECRET }}
SSL_COM_USERNAME: ${{ inputs.sign && secrets.SSL_COM_USERNAME || '' }}
SSL_COM_PASSWORD: ${{ inputs.sign && secrets.SSL_COM_PASSWORD || '' }}
SSL_COM_CREDENTIAL_ID: ${{ inputs.sign && steps.get_credential_ids.outputs.SSL_COM_CREDENTIAL_ID || '' }}
SSL_COM_TOTP_SECRET: ${{ inputs.sign && secrets.SSL_COM_TOTP_SECRET || '' }}
CODE_SIGN_TOOL_PATH: ${{ inputs.sign && 'C:\\actions-runner\\_work\\nym\\nym\\nym-wallet\\src-tauri\\' || '' }}
run: |
echo "Starting build process..."
echo "Signing enabled: ${{ inputs.sign }}"
yarn build
- name: Check bundle directory
@@ -147,7 +153,7 @@ jobs:
nym-wallet/${{ env.BUNDLE_PATH }}/msi/*.msi.zip*
nym-wallet/${{ env.BUNDLE_PATH }}/*/nym-wallet*.msi
nym-wallet/src-tauri/target/release/bundle/msi/*.msi
- name: Find MSI path for deployment
id: find-msi
shell: bash
@@ -167,4 +173,4 @@ jobs:
needs: publish-tauri
with:
release_tag: ${{ needs.publish-tauri.outputs.release_tag || github.ref_name }}
secrets: inherit
secrets: inherit
@@ -38,9 +38,10 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
id: get_version
run: |
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: cleanup-gateway-probe-ref
id: cleanup_gateway_probe_ref
+3 -2
View File
@@ -32,9 +32,10 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.45.4
id: get_version
run: |
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
with:
cmd: 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
-48
View File
@@ -4,54 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.13-emmental] (2025-07-22)
- fix: don't allow mixnode running in exit mode ([#5898])
- fix contract build process in Makefile ([#5892])
- bugfix: ignore 'Send' responses when claiming bandwidth ([#5884])
- Update push-node-status-agent.yaml ([#5882])
- listen for shutdown signals during nym-node startup ([#5879])
- feat: forbid running mixnode + entry on the same node ([#5878])
- chore: 1.88 clippy ([#5877])
- Batch SQL writes for packet stats ([#5874])
- fix the broken link ([#5873])
- Set busy_timeout in sqlx ([#5872])
- feat: basic performance contract integration [within Nym API] ([#5871])
- scraper bugfix: ignore precommits from missing validators ([#5867])
- Return true remaining ([#5866])
- Make Mix hops optional for Mixnet Client SURBs ([#5861])
- Check gateway supported versions ([#5860])
- Add build info endpoints ([#5857])
- Clear out screaming logs ([#5856])
- fix removal of qa env ([#5855])
- Use display when printing paths ([#5853])
- feat: initial performance contract ([#5833])
- Security patches for the `dkg` crate ([#5828])
- HTTP Discovery objects & network defaults ([#5814])
[#5898]: https://github.com/nymtech/nym/pull/5898
[#5892]: https://github.com/nymtech/nym/pull/5892
[#5884]: https://github.com/nymtech/nym/pull/5884
[#5882]: https://github.com/nymtech/nym/pull/5882
[#5879]: https://github.com/nymtech/nym/pull/5879
[#5878]: https://github.com/nymtech/nym/pull/5878
[#5877]: https://github.com/nymtech/nym/pull/5877
[#5874]: https://github.com/nymtech/nym/pull/5874
[#5873]: https://github.com/nymtech/nym/pull/5873
[#5872]: https://github.com/nymtech/nym/pull/5872
[#5871]: https://github.com/nymtech/nym/pull/5871
[#5867]: https://github.com/nymtech/nym/pull/5867
[#5866]: https://github.com/nymtech/nym/pull/5866
[#5861]: https://github.com/nymtech/nym/pull/5861
[#5860]: https://github.com/nymtech/nym/pull/5860
[#5857]: https://github.com/nymtech/nym/pull/5857
[#5856]: https://github.com/nymtech/nym/pull/5856
[#5855]: https://github.com/nymtech/nym/pull/5855
[#5853]: https://github.com/nymtech/nym/pull/5853
[#5833]: https://github.com/nymtech/nym/pull/5833
[#5828]: https://github.com/nymtech/nym/pull/5828
[#5814]: https://github.com/nymtech/nym/pull/5814
## [2025.12-dolcelatte] (2025-07-07)
- bugfix: key-rotation + reply SURBs ([#5876])
-686
View File
@@ -1,686 +0,0 @@
# 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 <package-name>
# 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 <package-name>
# 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 <package-name>
# 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 <migration_name>
# 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
Generated
+957 -1014
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.59"
version = "1.1.58"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.59"
version = "1.1.58"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -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,7 +49,8 @@ impl StorageManager {
}
};
let connection_pool = SqlitePoolGuard::new(connection_pool);
let connection_pool =
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
if let Err(err) = sqlx::migrate!("./fs_surbs_migrations")
.run(&*connection_pool)
@@ -41,11 +41,6 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_epoch_at_height(&self, height: u64) -> Result<Option<Epoch>, NyxdError> {
let request = DkgQueryMsg::GetEpochStateAtHeight { height };
self.query_dkg_contract(request).await
}
async fn can_advance_state(&self) -> Result<StateAdvanceResponse, NyxdError> {
let request = DkgQueryMsg::CanAdvanceState {};
self.query_dkg_contract(request).await
@@ -92,34 +87,6 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_epoch_dealers_paged(
&self,
epoch_id: EpochId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedDealerResponse, NyxdError> {
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<String>,
limit: Option<u32>,
) -> Result<PagedDealerResponse, NyxdError> {
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<String>,
@@ -241,20 +208,6 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
collect_paged!(self, get_current_dealers_paged, dealers)
}
async fn get_all_epoch_dealers(
&self,
epoch_id: EpochId,
) -> Result<Vec<DealerDetails>, NyxdError> {
collect_paged!(self, get_epoch_dealers_paged, dealers, epoch_id)
}
async fn get_all_epoch_dealers_addresses(
&self,
epoch_id: EpochId,
) -> Result<Vec<DealerDetails>, NyxdError> {
collect_paged!(self, get_epoch_dealers_addresses_paged, dealers, epoch_id)
}
async fn get_all_dealer_indices(&self) -> Result<Vec<(Addr, NodeIndex)>, NyxdError> {
collect_paged!(self, get_dealer_indices_paged, indices)
}
@@ -304,9 +257,6 @@ 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()
@@ -326,20 +276,6 @@ 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()
}
@@ -55,14 +55,6 @@ impl DealerDetailsResponse {
}
}
#[cw_serde]
pub struct PagedDealerAddressesResponse {
pub dealers: Vec<Addr>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<Addr>,
}
#[cw_serde]
pub struct PagedDealerResponse {
pub dealers: Vec<DealerDetails>,
@@ -12,8 +12,8 @@ use cosmwasm_schema::cw_serde;
#[cfg(feature = "schema")]
use crate::{
dealer::{
DealerDetailsResponse, PagedDealerAddressesResponse, PagedDealerIndexResponse,
PagedDealerResponse, RegisteredDealerDetails,
DealerDetailsResponse, PagedDealerIndexResponse, PagedDealerResponse,
RegisteredDealerDetails,
},
dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
@@ -84,9 +84,6 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(Epoch))]
GetCurrentEpochState {},
#[cfg_attr(feature = "schema", returns(Option<Epoch>))]
GetEpochStateAtHeight { height: u64 },
#[cfg_attr(feature = "schema", returns(u64))]
GetCurrentEpochThreshold {},
@@ -105,20 +102,6 @@ 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<u32>,
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(PagedDealerResponse))]
GetEpochDealers {
epoch_id: EpochId,
limit: Option<u32>,
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(PagedDealerResponse))]
GetCurrentDealers {
limit: Option<u32>,
@@ -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,7 +75,8 @@ impl PersistentStorage {
}
};
let connection_pool = SqlitePoolGuard::new(connection_pool);
let connection_pool =
SqlitePoolGuard::new(database_path.as_ref().to_path_buf(), connection_pool);
if let Err(err) = sqlx::migrate!("./migrations").run(&*connection_pool).await {
error!("Failed to perform migration on the SQLx database: {err}");
-1
View File
@@ -1091,7 +1091,6 @@ dependencies = [
name = "nym-coconut-dkg"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmwasm-schema",
"cosmwasm-std",
"cw-controllers",
-1
View File
@@ -29,7 +29,6 @@ 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" }
@@ -361,29 +361,6 @@
},
"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": [
@@ -483,80 +460,6 @@
},
"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": [
@@ -1955,375 +1858,6 @@
}
}
},
"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",
@@ -28,29 +28,6 @@
},
"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": [
@@ -150,80 +127,6 @@
},
"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": [
@@ -1,70 +0,0 @@
{
"$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
}
}
}
@@ -1,34 +0,0 @@
{
"$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"
}
}
}
@@ -1,265 +0,0 @@
{
"$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"
}
}
}
+12 -34
View File
@@ -3,7 +3,6 @@
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;
@@ -14,14 +13,13 @@ 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_at_height, query_epoch_threshold,
query_epoch_threshold,
};
use crate::epoch_state::storage::save_epoch;
use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD};
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};
@@ -69,9 +67,8 @@ pub fn instantiate(
};
STATE.save(deps.storage, &state)?;
save_epoch(
CURRENT_EPOCH.save(
deps.storage,
env.block.height,
&Epoch::new(
EpochState::WaitingInitialisation,
0,
@@ -103,7 +100,6 @@ pub fn execute(
resharing,
} => try_add_dealer(
deps,
env,
info,
bte_key_with_proof,
identity_key,
@@ -122,7 +118,7 @@ pub fn execute(
try_commit_verification_key_share(deps, env, info, share, resharing)
}
ExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => {
try_verify_verification_key_share(deps, env, info, owner, resharing)
try_verify_verification_key_share(deps, info, owner, resharing)
}
ExecuteMsg::AdvanceEpochState {} => try_advance_epoch_state(deps, env),
ExecuteMsg::TriggerReset {} => try_trigger_reset(deps, env, info),
@@ -135,9 +131,6 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
let response = match msg {
QueryMsg::GetState {} => 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)?)?
}
@@ -158,26 +151,6 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
QueryMsg::GetDealerDetails { dealer_address } => {
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)?)?
}
@@ -248,11 +221,16 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
}
#[entry_point]
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
introduce_historical_epochs(deps, env)?;
// 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)?;
Ok(Response::new())
}
@@ -356,7 +334,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
+99 -353
View File
@@ -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::load_current_epoch;
use crate::epoch_state::storage::CURRENT_EPOCH;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::dealer::{
DealerDetailsResponse, DealerType, PagedDealerAddressesResponse, PagedDealerIndexResponse,
PagedDealerResponse, RegisteredDealerDetails,
DealerDetailsResponse, DealerType, 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 => load_current_epoch(deps.storage)?.epoch_id,
None => CURRENT_EPOCH.load(deps.storage)?.epoch_id,
};
Ok(RegisteredDealerDetails {
@@ -36,7 +36,7 @@ pub fn query_dealer_details(
dealer_address: String,
) -> StdResult<DealerDetailsResponse> {
let addr = deps.api.addr_validate(&dealer_address)?;
let current_epoch_id = load_current_epoch(deps.storage)?.epoch_id;
let current_epoch_id = CURRENT_EPOCH.load(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,37 +82,8 @@ pub fn query_dealers_indices_paged(
Ok(PagedDealerIndexResponse::new(dealers, start_next_after))
}
pub fn query_epoch_dealers_addresses_paged(
pub fn query_current_dealers_paged(
deps: Deps<'_>,
epoch_id: EpochId,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerAddressesResponse> {
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::<StdResult<Vec<_>>>()?;
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<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
@@ -125,8 +96,10 @@ pub fn query_epoch_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(epoch_id)
.prefix(current_epoch_id)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| {
@@ -134,7 +107,7 @@ pub fn query_epoch_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, epoch_id)
let assigned_index = get_dealer_index(deps.storage, &address, current_epoch_id)
.expect("could not retrieve dealer index for a registered dealer");
DealerDetails {
@@ -152,15 +125,6 @@ pub fn query_epoch_dealers_paged(
Ok(PagedDealerResponse::new(dealers, limit, start_next_after))
}
pub fn query_current_dealers_paged(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealerResponse> {
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::*;
@@ -194,330 +158,112 @@ pub(crate) mod tests {
}
}
#[cfg(test)]
mod current_epoch_dealers {
use super::*;
#[test]
fn dealers_empty_on_init() {
let deps = init_contract();
#[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());
}
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
assert_eq!(0, page1.dealers.len() as u32);
}
#[test]
fn epoch_dealers_addresses() {
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();
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);
}
fill_dealers(&mut deps, 0, 1000);
// 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);
}
// query without explicitly setting a limit
let page1 = query_current_dealers_paged(deps.as_ref(), None, None).unwrap();
// 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]);
assert_eq!(DEALERS_PAGE_DEFAULT_LIMIT, page1.dealers.len() as u32);
insert_dealer(deps.as_mut(), 1, &fixtures[4]);
insert_dealer(deps.as_mut(), 1, &fixtures[5]);
insert_dealer(deps.as_mut(), 1, &fixtures[6]);
remove_dealers(&mut deps, 0, 1000);
}
insert_dealer(deps.as_mut(), 2, &fixtures[1]);
insert_dealer(deps.as_mut(), 2, &fixtures[4]);
insert_dealer(deps.as_mut(), 2, &fixtures[6]);
#[test]
fn dealers_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
insert_dealer(deps.as_mut(), 3, &fixtures[7]);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALERS_PAGE_MAX_LIMIT;
for fixture in &fixtures {
insert_dealer(deps.as_mut(), 4, fixture);
}
fill_dealers(&mut deps, 0, 1000);
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))
}
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(), 1, None, None).unwrap();
assert_eq!(3, res.dealers.len() as u32);
for fixture in &fixtures[4..=6] {
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(), 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))
}
remove_dealers(&mut deps, 0, 1000);
}
let res = query_epoch_dealers_addresses_paged(deps.as_ref(), 3, None, None).unwrap();
assert_eq!(vec![fixtures[7].address.clone()], res.dealers);
#[test]
fn dealers_pagination_works() {
let mut deps = init_contract();
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
);
let per_page = 2;
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
);
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);
}
}
@@ -13,9 +13,6 @@ 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<NodeIndex> = Item::new("node_index_counter");
pub(crate) const DEALERS_INDICES: Map<Dealer, NodeIndex> = Map::new("dealer_index");
@@ -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::{load_current_epoch, save_epoch};
use crate::epoch_state::storage::CURRENT_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, Env, MessageInfo, Response};
use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult};
use nym_coconut_dkg_common::dealer::DealerRegistrationDetails;
use nym_coconut_dkg_common::types::{EncodedBTEPublicKeyWithProof, EpochState};
@@ -28,14 +28,13 @@ 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<Response, ContractError> {
let epoch = load_current_epoch(deps.storage)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
check_epoch_state(deps.storage, EpochState::PublicKeySubmission { resharing })?;
// make sure this potential dealer actually belong to the group
@@ -69,16 +68,16 @@ pub fn try_add_dealer(
);
// increment the number of registered dealers
{
let current_epoch = load_current_epoch(deps.storage)?;
let mut updated_epoch = current_epoch;
CURRENT_EPOCH.update(deps.storage, |epoch| -> StdResult<_> {
let mut updated_epoch = epoch;
updated_epoch.state_progress.registered_dealers += 1;
if is_resharing_dealer {
updated_epoch.state_progress.registered_resharing_dealers += 1;
}
save_epoch(deps.storage, env.block.height, &updated_epoch)?;
}
Ok(updated_epoch)
})?;
Ok(Response::new().add_attribute("node_index", node_index.to_string()))
}
@@ -116,11 +115,10 @@ 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.clone()).unwrap();
try_advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_add_dealer(
deps.as_mut(),
env,
info,
bte_key_with_proof,
identity,
@@ -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::{load_current_epoch, save_epoch};
use crate::epoch_state::storage::CURRENT_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<DealingChunkInfo>,
resharing: bool,
) -> Result<Response, ContractError> {
let epoch = load_current_epoch(deps.storage)?;
let epoch = CURRENT_EPOCH.load(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 = load_current_epoch(deps.storage)?;
let mut epoch = CURRENT_EPOCH.load(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;
save_epoch(deps.storage, env.block.height, &epoch)?;
CURRENT_EPOCH.save(deps.storage, &epoch)?;
}
Ok(Response::new())
@@ -309,10 +309,12 @@ pub(crate) mod tests {
);
// same index, but next epoch
let mut epoch = load_current_epoch(&deps.storage).unwrap();
epoch.epoch_id += 1;
save_epoch(deps.as_mut().storage, epoch.epoch_id, &epoch).unwrap();
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.epoch_id += 1;
Ok(epoch)
})
.unwrap();
re_register_dealer(deps.as_mut(), &info.sender);
try_submit_dealings_metadata(
@@ -1,19 +1,17 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{
load_current_epoch, EPOCH_THRESHOLDS, HISTORICAL_EPOCH, THRESHOLD,
};
use crate::epoch_state::storage::{CURRENT_EPOCH, EPOCH_THRESHOLDS, THRESHOLD};
use crate::epoch_state::utils::check_state_completion;
use crate::error::ContractError;
use cosmwasm_std::{Env, StdResult, Storage};
use cosmwasm_std::{Env, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochId, EpochState, StateAdvanceResponse};
pub(crate) fn query_can_advance_state(
storage: &dyn Storage,
env: Env,
) -> Result<StateAdvanceResponse, ContractError> {
let epoch = load_current_epoch(storage)?;
let epoch = CURRENT_EPOCH.load(storage)?;
if epoch.state == EpochState::WaitingInitialisation {
return Ok(StateAdvanceResponse::default());
@@ -36,14 +34,9 @@ pub(crate) fn query_can_advance_state(
}
pub(crate) fn query_current_epoch(storage: &dyn Storage) -> Result<Epoch, ContractError> {
load_current_epoch(storage).map_err(|_| ContractError::EpochNotInitialised)
}
pub(crate) fn query_epoch_at_height(
storage: &dyn Storage,
height: u64,
) -> StdResult<Option<Epoch>> {
HISTORICAL_EPOCH.may_load_at_height(storage, height)
CURRENT_EPOCH
.load(storage)
.map_err(|_| ContractError::EpochNotInitialised)
}
pub(crate) fn query_current_epoch_threshold(
@@ -1,225 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{StdResult, Storage};
use cw_storage_plus::{Item, Map, SnapshotItem, Strategy};
use cw_storage_plus::{Item, Map};
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<Epoch> = Item::new("current_epoch");
pub const HISTORICAL_EPOCH: SnapshotItem<Epoch> = SnapshotItem::new(
"historical_epoch",
"historical_epoch__checkpoints",
"historical_epoch__changelog",
Strategy::EveryBlock,
);
pub const THRESHOLD: Item<u64> = Item::new("threshold");
pub const EPOCH_THRESHOLDS: Map<EpochId, u64> = 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<Epoch> {
#[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(())
}
}
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{load_current_epoch, save_epoch, EPOCH_THRESHOLDS, THRESHOLD};
use crate::epoch_state::storage::{CURRENT_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<Response, ContractError> {
// 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 = load_current_epoch(deps.storage)?;
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
// checks whether the given phase has either completed or reached its deadline
ensure_can_advance_state(deps.as_ref(), &env, &current_epoch)?;
@@ -82,7 +82,7 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
};
// update the epoch state
save_epoch(deps.storage, env.block.height, &next_epoch)?;
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
Ok(Response::new())
}
@@ -90,33 +90,23 @@ pub fn try_advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response,
#[cfg(test)]
mod tests {
use super::*;
use crate::epoch_state::storage::load_current_epoch;
use crate::epoch_state::transactions::try_initiate_dkg;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::state::storage::STATE;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{message_info, mock_env};
use cosmwasm_std::{Addr, Storage};
use cosmwasm_std::{Addr, StdResult, Storage};
use nym_coconut_dkg_common::types::TimeConfiguration;
fn update_epoch<A>(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, env: &Env, epoch: Epoch) {
save_epoch(storage, env.block.height, &epoch).unwrap();
fn set_epoch(storage: &mut dyn Storage, epoch: Epoch) {
CURRENT_EPOCH.save(storage, &epoch).unwrap();
}
let mut deps = init_contract();
@@ -124,18 +114,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, &env, epoch);
set_epoch(deps.as_mut().storage, 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, &env, epoch);
set_epoch(deps.as_mut().storage, 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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -148,7 +138,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -156,7 +146,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -164,7 +154,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_ok());
check_epoch_state(
@@ -177,7 +167,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -186,7 +176,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -195,7 +185,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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_ok());
check_epoch_state(
@@ -210,7 +200,7 @@ mod tests {
&env,
);
epoch.state_progress.registered_dealers = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -219,7 +209,7 @@ mod tests {
&env,
);
epoch.state_progress.registered_dealers = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -229,7 +219,7 @@ mod tests {
);
epoch.state_progress.registered_dealers = 5;
epoch.state_progress.submitted_key_shares = 4;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -239,7 +229,7 @@ mod tests {
);
epoch.state_progress.registered_dealers = 5;
epoch.state_progress.submitted_key_shares = 4;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -249,7 +239,7 @@ mod tests {
);
epoch.state_progress.registered_dealers = 5;
epoch.state_progress.submitted_key_shares = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_ok());
check_epoch_state(
@@ -264,7 +254,7 @@ mod tests {
);
epoch.state_progress.registered_dealers = 5;
epoch.state_progress.submitted_key_shares = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_ok());
check_epoch_state(
@@ -278,7 +268,7 @@ mod tests {
EpochState::VerificationKeyValidation { resharing: false },
&env,
);
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -286,7 +276,7 @@ mod tests {
EpochState::VerificationKeyValidation { resharing: true },
&env,
);
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -296,7 +286,7 @@ mod tests {
&env,
);
epoch.state_progress.submitted_key_shares = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -305,7 +295,7 @@ mod tests {
&env,
);
epoch.state_progress.submitted_key_shares = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -315,7 +305,7 @@ mod tests {
);
epoch.state_progress.submitted_key_shares = 5;
epoch.state_progress.verified_keys = 4;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -325,7 +315,7 @@ mod tests {
);
epoch.state_progress.submitted_key_shares = 5;
epoch.state_progress.verified_keys = 4;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
@@ -335,7 +325,7 @@ mod tests {
);
epoch.state_progress.submitted_key_shares = 5;
epoch.state_progress.verified_keys = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, 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();
@@ -346,14 +336,14 @@ mod tests {
);
epoch.state_progress.submitted_key_shares = 5;
epoch.state_progress.verified_keys = 5;
set_epoch(deps.as_mut().storage, &env, epoch);
set_epoch(deps.as_mut().storage, 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, &env, epoch);
set_epoch(deps.as_mut().storage, epoch);
let res = try_advance_epoch_state(deps.as_mut(), env.clone());
assert!(res.is_err());
}
@@ -376,7 +366,7 @@ mod tests {
)
.unwrap();
let epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
@@ -400,16 +390,19 @@ mod tests {
env.block.time = env.block.time.plus_seconds(1);
// add some dealers to prevent short-circuiting
update_epoch(deps.as_mut().storage, &env, |mut e| {
e.state_progress.registered_dealers = 42;
e
});
CURRENT_EPOCH
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
e.state_progress.registered_dealers = 42;
Ok(e)
})
.unwrap();
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 = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::DealingExchange { resharing: false }
@@ -432,7 +425,7 @@ mod tests {
env.block.time = env.block.time.plus_seconds(3);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeySubmission { resharing: false }
@@ -459,7 +452,7 @@ mod tests {
env.block.time = env.block.time.plus_seconds(3);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeyValidation { resharing: false }
@@ -485,13 +478,16 @@ mod tests {
);
// add some key shares to prevent short-circuiting
update_epoch(deps.as_mut().storage, &env, |mut e| {
e.state_progress.submitted_key_shares = 42;
e
});
CURRENT_EPOCH
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
e.state_progress.submitted_key_shares = 42;
Ok(e)
})
.unwrap();
env.block.time = env.block.time.plus_seconds(3);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::VerificationKeyFinalization { resharing: false }
@@ -516,14 +512,16 @@ mod tests {
);
// add some finalized keys to prevent reset
update_epoch(deps.as_mut().storage, &env, |mut e| {
e.state_progress.verified_keys = 42;
e
});
CURRENT_EPOCH
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
e.state_progress.verified_keys = 42;
Ok(e)
})
.unwrap();
env.block.time = env.block.time.plus_seconds(1);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::InProgress);
assert_eq!(
epoch.deadline.unwrap(),
@@ -549,9 +547,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 = load_current_epoch(deps.as_mut().storage).unwrap();
let prev_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let mut expected_epoch = Epoch::new(
EpochState::InProgress,
prev_epoch.epoch_id,
@@ -572,11 +570,11 @@ mod tests {
// fewer than the threshold
epoch.state_progress.verified_keys = 41;
save_epoch(deps.as_mut().storage, env.block.height, &epoch).unwrap();
CURRENT_EPOCH.save(deps.as_mut().storage, &epoch).unwrap();
env.block.time = env.block.time.plus_seconds(5000000);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = load_current_epoch(deps.as_mut().storage).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let expected_epoch = Epoch::new(
EpochState::PublicKeySubmission { resharing: false },
epoch.epoch_id + 1,
@@ -600,10 +598,12 @@ mod tests {
assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none());
update_epoch(deps.as_mut().storage, &env, |mut e| {
e.state_progress.registered_dealers = 100;
e
});
CURRENT_EPOCH
.update(deps.as_mut().storage, |mut e| -> StdResult<_> {
e.state_progress.registered_dealers = 100;
Ok(e)
})
.unwrap();
env.block.time = env
.block
@@ -1,7 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::{load_current_epoch, save_epoch, THRESHOLD};
use crate::epoch_state::storage::{CURRENT_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 = load_current_epoch(deps.storage)?;
let epoch = CURRENT_EPOCH.load(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);
save_epoch(deps.storage, env.block.height, &initial_epoch)?;
CURRENT_EPOCH.save(deps.storage, &initial_epoch)?;
Ok(Response::default())
}
@@ -49,7 +49,7 @@ pub(crate) fn try_trigger_reset(
) -> Result<Response, ContractError> {
// only the admin is allowed to trigger DKG reset
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
let current_epoch = load_current_epoch(deps.storage)?;
let current_epoch = CURRENT_EPOCH.load(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);
save_epoch(deps.storage, env.block.height, &next_epoch)?;
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
reset_dkg_state(deps.storage)?;
@@ -71,7 +71,7 @@ pub(crate) fn try_trigger_resharing(
) -> Result<Response, ContractError> {
// only the admin is allowed to trigger DKG resharing
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
let current_epoch = load_current_epoch(deps.storage)?;
let current_epoch = CURRENT_EPOCH.load(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);
save_epoch(deps.storage, env.block.height, &next_epoch)?;
CURRENT_EPOCH.save(deps.storage, &next_epoch)?;
reset_dkg_state(deps.storage)?;
@@ -89,7 +89,6 @@ 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;
@@ -100,7 +99,7 @@ pub(crate) mod tests {
let mut deps = init_contract();
let env = mock_env();
let initial_epoch_info = load_current_epoch(&deps.storage).unwrap();
let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert!(initial_epoch_info.deadline.is_none());
let not_admin = deps.api.addr_make("not an admin");
@@ -126,7 +125,7 @@ pub(crate) mod tests {
assert_eq!(ContractError::AlreadyInitialised, res);
// sets the correct epoch data
let epoch = load_current_epoch(&deps.storage).unwrap();
let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(epoch.epoch_id, 0);
assert_eq!(
epoch.state,
@@ -1,7 +1,7 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::load_current_epoch;
use crate::epoch_state::storage::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 = load_current_epoch(storage)?.state;
let epoch_state = CURRENT_EPOCH.load(storage)?.state;
if epoch_state != against {
Err(ContractError::IncorrectEpochState {
current_state: epoch_state.to_string(),
@@ -66,7 +66,6 @@ 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;
@@ -211,12 +210,12 @@ pub(crate) mod test {
let env = mock_env();
for fixed_state in EpochState::first().all_until(EpochState::InProgress) {
save_epoch(
deps.as_mut().storage,
env.block.height,
&Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time),
)
.unwrap();
CURRENT_EPOCH
.save(
deps.as_mut().storage,
&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 {
-3
View File
@@ -10,9 +10,6 @@ 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),
-1
View File
@@ -11,7 +11,6 @@ mod dealers;
mod dealings;
mod epoch_state;
pub mod error;
mod queued_migrations;
mod state;
mod support;
mod verification_key_shares;
@@ -1,21 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::epoch_state::storage::HISTORICAL_EPOCH;
use crate::error::ContractError;
use cosmwasm_std::{DepsMut, Env};
pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> {
if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() {
return Err(ContractError::FailedMigration {
comment: "this migration has already been run before".to_string(),
});
}
#[allow(deprecated)]
let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?;
// we won't have information on intermediate states prior to now, but that's not the end of the world
HISTORICAL_EPOCH.save(deps.storage, &current, env.block.height)?;
Ok(())
}
@@ -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<DealingChunkInfo> {
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(),
@@ -3,7 +3,7 @@
use crate::contract::instantiate;
use crate::dealers::storage::{DEALERS_INDICES, EPOCH_DEALERS_MAP};
use crate::epoch_state::storage::load_current_epoch;
use crate::epoch_state::storage::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<Vec<(Member, u64)>> = Mutex::new(Vec::new());
pub fn re_register_dealer(deps: DepsMut, dealer: &Addr) {
let epoch_id = load_current_epoch(deps.storage).unwrap().epoch_id;
let epoch_id = CURRENT_EPOCH.load(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 = load_current_epoch(deps.storage).unwrap().epoch_id;
let epoch_id = CURRENT_EPOCH.load(deps.storage).unwrap().epoch_id;
insert_dealer(deps, epoch_id, details)
}
@@ -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()
@@ -3,7 +3,7 @@
use crate::constants::BLOCK_TIME_FOR_VERIFICATION_SECS;
use crate::dealers::storage::get_dealer_details;
use crate::epoch_state::storage::{load_current_epoch, save_epoch};
use crate::epoch_state::storage::CURRENT_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 = load_current_epoch(deps.storage)?;
let mut epoch = CURRENT_EPOCH.load(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: load_current_epoch(deps.storage)?.epoch_id,
epoch_id: CURRENT_EPOCH.load(deps.storage)?.epoch_id,
verified: false,
};
vk_shares().save(deps.storage, (&info.sender, epoch_id), &data)?;
@@ -60,14 +60,13 @@ pub fn try_commit_verification_key_share(
)?;
epoch.state_progress.submitted_key_shares += 1;
save_epoch(deps.storage, env.block.height, &epoch)?;
CURRENT_EPOCH.save(deps.storage, &epoch)?;
Ok(Response::new().add_message(msg))
}
pub fn try_verify_verification_key_share(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
owner: String,
resharing: bool,
@@ -78,7 +77,7 @@ pub fn try_verify_verification_key_share(
deps.storage,
EpochState::VerificationKeyFinalization { resharing },
)?;
let mut epoch = load_current_epoch(deps.storage)?;
let mut epoch = CURRENT_EPOCH.load(deps.storage)?;
let epoch_id = epoch.epoch_id;
MULTISIG.assert_admin(deps.as_ref(), &info.sender)?;
@@ -94,7 +93,7 @@ pub fn try_verify_verification_key_share(
})?;
epoch.state_progress.verified_keys += 1;
save_epoch(deps.storage, env.block.height, &epoch)?;
CURRENT_EPOCH.save(deps.storage, &epoch)?;
Ok(Response::default())
}
@@ -260,14 +259,9 @@ 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(),
env.clone(),
info.clone(),
owner.clone(),
false,
)
.unwrap_err();
let ret =
try_verify_verification_key_share(deps.as_mut(), info.clone(), owner.clone(), false)
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
@@ -297,26 +291,15 @@ mod tests {
.block
.time
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
try_advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_verify_verification_key_share(
deps.as_mut(),
env.clone(),
info,
owner.clone(),
false,
)
.unwrap_err();
let ret = try_verify_verification_key_share(deps.as_mut(), info, owner.clone(), false)
.unwrap_err();
assert_eq!(ret, ContractError::Admin(AdminError::NotAdmin {}));
let ret = try_verify_verification_key_share(
deps.as_mut(),
env.clone(),
multisig_info,
owner.clone(),
false,
)
.unwrap_err();
let ret =
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.clone(), false)
.unwrap_err();
assert_eq!(
ret,
ContractError::NoCommitForOwner {
@@ -373,15 +356,9 @@ mod tests {
.block
.time
.plus_seconds(TimeConfiguration::default().verification_key_validation_time_secs);
try_advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
try_advance_epoch_state(deps.as_mut(), env).unwrap();
try_verify_verification_key_share(
deps.as_mut(),
env,
multisig_info,
owner.to_string(),
false,
)
.unwrap();
try_verify_verification_key_share(deps.as_mut(), multisig_info, owner.to_string(), false)
.unwrap();
}
}
@@ -1 +1 @@
Tuesday, July 22nd 2025, 11:37:27 UTC
Friday, July 4th 2025, 14:45:02 UTC
@@ -2,14 +2,13 @@
Usage: nym-node [OPTIONS] <COMMAND>
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
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)
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)
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
@@ -306,7 +306,6 @@ 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=<NS_AGENT_PRIVATE_KEY>
export NODE_STATUS_AGENT_PROBE_MNEMONIC=<NS_AGENT_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}
@@ -47,83 +47,6 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `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 Rusts `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)
@@ -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-22T09:24:35.790560275Z
Build Version: 1.15.0
Commit SHA: 578c9b0567656d86812aa21eb0b4c93b5a7235bd
Commit Date: 2025-07-22T11:09:35.000000000+02:00
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
Commit Branch: HEAD
rustc Version: 1.86.0
rustc Channel: stable
@@ -9,7 +9,7 @@ Nym Node operators running Gateway functionality are already familiar with the m
## Preparation
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.
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).
## Installation
@@ -34,65 +34,27 @@ 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 -h
./target/release/nym-gateway-probe --help
```
- Output:
```sh
Usage: nym-gateway-probe [OPTIONS] --mnemonic <MNEMONIC>
Usage: nym-gateway-probe [OPTIONS]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
-g, --entry-gateway <ENTRY_GATEWAY>
The specific gateway specified by ID
-n, --node <NODE>
Identity of the node to test
--min-gateway-mixnet-performance <MIN_GATEWAY_MIXNET_PERFORMANCE>
--min-gateway-vpn-performance <MIN_GATEWAY_VPN_PERFORMANCE>
--only-wireguard
-i, --ignore-egress-epoch-role
Disable logging during probe
--no-log
-a, --amnezia-args <AMNEZIA_ARGS>
Arguments to be appended to the wireguard config enabling amnezia-wg configuration
--netstack-download-timeout-sec <NETSTACK_DOWNLOAD_TIMEOUT_SEC>
[default: 180]
--netstack-v4-dns <NETSTACK_V4_DNS>
[default: 1.1.1.1]
--netstack-v6-dns <NETSTACK_V6_DNS>
[default: 2606:4700:4700::1111]
--netstack-num-ping <NETSTACK_NUM_PING>
[default: 5]
--netstack-send-timeout-sec <NETSTACK_SEND_TIMEOUT_SEC>
[default: 3]
--netstack-recv-timeout-sec <NETSTACK_RECV_TIMEOUT_SEC>
[default: 3]
--netstack-ping-hosts-v4 <NETSTACK_PING_HOSTS_V4>
[default: nymtech.net]
--netstack-ping-ips-v4 <NETSTACK_PING_IPS_V4>
[default: 1.1.1.1]
--netstack-ping-hosts-v6 <NETSTACK_PING_HOSTS_V6>
[default: ipv6.google.com]
--netstack-ping-ips-v6 <NETSTACK_PING_IPS_V6>
[default: 2001:4860:4860::8888 2606:4700:4700::1111 2620:fe::fe]
--mnemonic <MNEMONIC>
-h, --help
Print help
-V, --version
Print version
-c, --config-env-file <CONFIG_ENV_FILE> Path pointing to an env file describing the network
-g, --gateway <GATEWAY>
-n, --no-log
-h, --help Print help
-V, --version Print version
```
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.
To run the client, simply add a flag `--gateway` with a targeted gateway identity key.
```sh
./target/release/nym-gateway-probe -n <GATEWAY_IDENTITY_KEY> --mnemonic <MNEMONIC>
./target/release/nym-gateway-probe --gateway <GATEWAY_IDENTITY_KEY>
```
For any `nym-node --mode exit-gateway` the aim is to have this outcome:
@@ -125,4 +87,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 `-n` flag it will pick a random node to test.
If you don't provide a `--gateway` flag it will pick a random one to test.
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.62"
version = "1.1.61"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
-58
View File
@@ -4,61 +4,3 @@ 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).
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-agent"
version = "1.0.4"
version = "1.0.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -14,25 +14,13 @@ rust-version.workspace = true
readme.workspace = true
[dependencies]
anyhow = { workspace = true }
anyhow = { workspace = true}
clap = { workspace = true, features = ["derive", "env"] }
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-bin-common = { path = "../../common/bin-common", features = ["models"]}
nym-node-status-client = { path = "../nym-node-status-client" }
nym-crypto = { git = "https://github.com/nymtech/nym.git", branch = "release/2025.11-cheddar", features = [
"asymmetric",
"rand",
] }
nym-crypto = { path = "../../common/crypto", 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"] }
@@ -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-api/nym-node-status-agent
WORKDIR /usr/src/nym/nym-node-status-agent
RUN cargo build --release
#-------------------------------------------------------------------
@@ -1,71 +0,0 @@
#!/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 <gateway-probe-git-ref>"
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}"
@@ -21,7 +21,6 @@ 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}
@@ -1,45 +1,17 @@
use crate::probe::GwProbe;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use nym_crypto::asymmetric::ed25519::PrivateKey;
use std::{env, sync::OnceLock};
use std::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<String> = OnceLock::new();
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}
fn parse_server_config(s: &str) -> Result<ServerConfig, String> {
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::<u16>()
.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 {
@@ -50,19 +22,20 @@ pub(crate) struct Args {
#[derive(Subcommand, Debug)]
pub(crate) enum Command {
RunProbe {
/// Server configurations in format "address:port:auth_key"
/// Can be specified multiple times for multiple servers
#[arg(short, long, required = true)]
server: Vec<String>,
#[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,
/// 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",
@@ -81,29 +54,22 @@ impl Args {
pub(crate) async fn execute(&self) -> anyhow::Result<()> {
match &self.command {
Command::RunProbe {
server,
server_address,
server_port,
ns_api_auth_key,
probe_path,
mnemonic,
probe_extra_args,
} => {
// 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}");
})?
}
} => 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}");
})?,
Command::GenerateKeypair { path } => {
let path = path
.to_owned()
@@ -1,143 +1,33 @@
use crate::cli::{GwProbe, ServerConfig};
use crate::cli::GwProbe;
use anyhow::Context;
use nym_crypto::asymmetric::ed25519::PrivateKey;
pub(crate) async fn run_probe(
servers: &[ServerConfig],
server_ip: &str,
server_port: u16,
ns_api_auth_key: &str,
probe_path: &str,
mnemonic: &str,
probe_extra_args: &Vec<String>,
) -> anyhow::Result<()> {
if servers.is_empty() {
anyhow::bail!("No servers configured");
}
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);
let probe = GwProbe::new(probe_path.to_string());
let version = probe.version().await;
tracing::info!("Probe version:\n{}", version);
// Always use first server as primary
let primary_server = &servers[0];
tracing::info!(
"Requesting testrun from primary server: {}:{}",
primary_server.address,
primary_server.port
);
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);
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::<Vec<_>>();
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)
}
ns_api_client
.submit_results(testrun.testrun_id, log, testrun.assigned_at_utc)
.await?;
} else {
tracing::info!("No testruns available, exiting")
}
Ok(())
}
@@ -73,7 +73,6 @@ impl GwProbe {
pub(crate) fn run_and_get_log(
&self,
gateway_key: &Option<String>,
mnemonic: &str,
probe_extra_args: &Vec<String>,
) -> String {
let mut command = std::process::Command::new(&self.path);
@@ -82,7 +81,6 @@ 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 {
@@ -1,32 +0,0 @@
# 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=
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,32 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,20 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -1,43 +1,43 @@
{
"db_name": "PostgreSQL",
"db_name": "SQLite",
"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": [
{
"ordinal": 0,
"name": "date_utc!",
"type_info": "Varchar"
"ordinal": 0,
"type_info": "Text"
},
{
"ordinal": 1,
"name": "total_stake!: i64",
"type_info": "Numeric"
"ordinal": 1,
"type_info": "Integer"
},
{
"ordinal": 2,
"name": "total_packets_received!: i64",
"type_info": "Int8"
"ordinal": 2,
"type_info": "Integer"
},
{
"ordinal": 3,
"name": "total_packets_sent!: i64",
"type_info": "Int8"
"ordinal": 3,
"type_info": "Integer"
},
{
"ordinal": 4,
"name": "total_packets_dropped!: i64",
"type_info": "Int8"
"ordinal": 4,
"type_info": "Integer"
}
],
"parameters": {
"Left": []
"Right": 0
},
"nullable": [
null,
null,
null,
null,
null
false,
false,
true,
true,
true
]
},
"hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07"
@@ -0,0 +1,32 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,26 @@
{
"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"
}
@@ -0,0 +1,26 @@
{
"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"
}
@@ -0,0 +1,20 @@
{
"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"
}
@@ -0,0 +1,86 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,38 @@
{
"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"
}
@@ -0,0 +1,92 @@
{
"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"
}
@@ -0,0 +1,20 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE testruns SET status = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "418944f2eccb838cb3882f34469203c8569f03fdd39ce09d7b74177896e52a8c"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_probe_log = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "4afcc6673890f795c2793f1e2f8570ee787fc7daf00fcb916f18d1cb7d6c8f08"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO testruns (gateway_id, status, ip_address, created_utc, log) VALUES (?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "4d865e873c9cb133883da94db72dcdebd4969e1f240def9fb1bf946f4a1f342f"
}
@@ -1,22 +0,0 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,26 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -1,19 +1,19 @@
{
"db_name": "PostgreSQL",
"db_name": "SQLite",
"query": "SELECT count(id) FROM mixnodes",
"describe": {
"columns": [
{
"name": "count(id)",
"ordinal": 0,
"name": "count",
"type_info": "Int8"
"type_info": "Integer"
}
],
"parameters": {
"Left": []
"Right": 0
},
"nullable": [
null
false
]
},
"hash": "670b7ed7d57a6986181b24be24ca667e8cacdf677ccb906415b3fe92be0c436b"
@@ -0,0 +1,12 @@
{
"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"
}
@@ -1,26 +0,0 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_probe_result = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "6ef3efde571d46961244cd90420f3de5949a5ff2083453cb879af8a1689efe2f"
}
@@ -0,0 +1,38 @@
{
"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"
}
@@ -0,0 +1,32 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -1,19 +1,19 @@
{
"db_name": "PostgreSQL",
"db_name": "SQLite",
"query": "SELECT count(id) FROM gateways",
"describe": {
"columns": [
{
"name": "count(id)",
"ordinal": 0,
"name": "count",
"type_info": "Int8"
"type_info": "Integer"
}
],
"parameters": {
"Left": []
"Right": 0
},
"nullable": [
null
false
]
},
"hash": "86ff64db477a1d6235179b0b88d86b86d1b9be62336c9eac0eef44987a5451b5"
@@ -0,0 +1,32 @@
{
"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"
}
@@ -1,21 +1,21 @@
{
"db_name": "PostgreSQL",
"db_name": "SQLite",
"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": [
{
"ordinal": 0,
"name": "gateway_identity_key!",
"type_info": "Varchar"
"ordinal": 0,
"type_info": "Text"
},
{
"ordinal": 1,
"name": "bonded: bool",
"type_info": "Bool"
"ordinal": 1,
"type_info": "Integer"
}
],
"parameters": {
"Left": []
"Right": 0
},
"nullable": [
false,
@@ -0,0 +1,86 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,56 @@
{
"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"
}
@@ -0,0 +1,20 @@
{
"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"
}
@@ -0,0 +1,44 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"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"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE gateways SET last_testrun_utc = ?, last_updated_utc = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "c214c001acbbf79fa499816f36ec586c4c29c03efb4cf0c40b73a5c76159cf5c"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE\n mixnodes\n SET\n bonded = false\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "c42917c9542c1d720d92035863064741aefc9f7a7d1630f6b863ebd8174b6684"
}
@@ -0,0 +1,26 @@
{
"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"
}
@@ -0,0 +1,20 @@
{
"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"
}

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