This document defines the QUIC-based protocol for communication between the Quantus Network node and external QPoW miner services.
The node delegates the mining task (finding a valid nonce) to external miner services over persistent QUIC connections. The node provides the necessary parameters (header hash, difficulty) and each external miner independently searches for a valid nonce according to the QPoW rules defined in the qpow-math crate. Miners push results back when found.
- Lower latency: Results are pushed immediately when found (no polling)
- Connection resilience: Built-in connection migration and recovery
- Multiplexed streams: Multiple operations on single connection
- Built-in TLS: Encrypted by default
┌─────────────────────────────────┐
│ Node │
│ (QUIC Server on port 9833) │
│ │
┌──────────┐ │ Broadcasts: NewJob │
│ Miner 1 │ ──connect───► │ Receives: JobResult │
└──────────┘ │ │
│ Supports multiple miners │
┌──────────┐ │ First valid result wins │
│ Miner 2 │ ──connect───► │ │
└──────────┘ └─────────────────────────────────┘
┌──────────┐
│ Miner 3 │ ──connect───►
└──────────┘
- Node acts as the QUIC server, listening on port 9833 (default)
- Miners act as QUIC clients, connecting to the node
- Single bidirectional stream per miner connection
- Connection persists across multiple mining jobs
- Multiple miners can connect simultaneously
When multiple miners are connected:
- Node broadcasts the same
NewJobto all connected miners - Each miner independently selects a random starting nonce
- First miner to find a valid solution sends
JobResult - Node uses the first valid result, ignores subsequent results for same job
- New job broadcast implicitly cancels work on all miners
The protocol uses only two message types:
| Direction | Message | Description |
|---|---|---|
| Node → Miner | NewJob |
Submit a mining job (implicitly cancels any previous job) |
| Miner → Node | JobResult |
Mining result (completed, failed, or cancelled) |
Messages are length-prefixed JSON:
┌─────────────────┬─────────────────────────────────┐
│ Length (4 bytes)│ JSON payload (MinerMessage) │
│ big-endian u32 │ │
└─────────────────┴─────────────────────────────────┘
Maximum message size: 16 MB
See the quantus-miner-api crate for the canonical Rust definitions.
pub enum MinerMessage {
NewJob(MiningRequest),
JobResult(MiningResult),
}| Field | Type | Description |
|---|---|---|
job_id |
String | Unique identifier (UUID recommended) |
mining_hash |
String | Header hash (64 hex chars, no 0x prefix) |
distance_threshold |
String | Difficulty (U512 as decimal string) |
Note: Nonce range is not specified - each miner independently selects a random starting point.
| Field | Type | Description |
|---|---|---|
status |
ApiResponseStatus | Result status (see below) |
job_id |
String | Job identifier |
nonce |
Option | Winning nonce (U512 hex, no 0x prefix) |
work |
Option | Winning nonce as bytes (128 hex chars) |
hash_count |
u64 | Number of nonces checked |
elapsed_time |
f64 | Time spent mining (seconds) |
| Value | Description |
|---|---|
completed |
Valid nonce found |
failed |
Nonce range exhausted without finding solution |
cancelled |
Job was cancelled (new job received) |
running |
Job still in progress (not typically sent) |
Miner Node
│ │
│──── QUIC Connect ─────────────────────────►│
│◄─── Connection Established ────────────────│
│ │
│◄─── NewJob { job_id: "abc", ... } ─────────│
│ │
│ (picks random nonce, starts mining) │
│ │
│──── JobResult { job_id: "abc", ... } ─────►│ (found solution!)
│ │
│ (node submits block, gets new work) │
│ │
│◄─── NewJob { job_id: "def", ... } ─────────│
│ │
When a new block arrives before the miner finds a solution, the node simply sends a new NewJob. The miner automatically cancels the previous job:
Miner Node
│ │
│◄─── NewJob { job_id: "abc", ... } ─────────│
│ │
│ (mining "abc") │
│ │
│ (new block arrives at node!) │
│ │
│◄─── NewJob { job_id: "def", ... } ─────────│
│ │
│ (cancels "abc", starts "def") │
│ │
│──── JobResult { job_id: "def", ... } ─────►│
When a miner connects while a job is active, it immediately receives the current job:
Miner (new) Node
│ │ (already mining job "abc")
│──── QUIC Connect ─────────────────────────►│
│◄─── Connection Established ────────────────│
│◄─── NewJob { job_id: "abc", ... } ─────────│ (current job sent immediately)
│ │
│ (joins mining effort) │
If a result arrives for an old job, the node discards it:
Miner Node
│ │
│◄─── NewJob { job_id: "abc", ... } ─────────│
│ │
│◄─── NewJob { job_id: "def", ... } ─────────│ (almost simultaneous)
│ │
│──── JobResult { job_id: "abc", ... } ─────►│ (stale, node ignores)
│ │
│──── JobResult { job_id: "def", ... } ─────►│ (current, node uses)
# Listen for external miner connections on port 9833
quantus-node --miner-listen-port 9833# Connect to node
quantus-miner serve --node-addr 127.0.0.1:9833The node generates a self-signed TLS certificate at startup. The miner skips certificate verification by default (insecure mode). For production deployments, consider:
- Certificate pinning: Configure the miner to accept only specific certificate fingerprints
- Proper CA: Use certificates signed by a trusted CA
- Network isolation: Run node and miner on a private network
The miner automatically reconnects with exponential backoff:
- Initial delay: 1 second
- Maximum delay: 30 seconds
The node continues operating with remaining connected miners.
If the miner receives an invalid MiningRequest, it sends a JobResult with status failed.
- All hex values should be sent without the
0xprefix - The miner implements validation logic from
qpow_math::is_valid_nonce - The node uses the
workfield fromMiningResultto constructQPoWSeal - ALPN protocol identifier:
quantus-miner - Each miner independently generates a random nonce starting point using cryptographically secure randomness
- With a 512-bit nonce space, collision between miners is statistically impossible