Qanum User Guide
Running a node, qanumd and qanum-cli, wallets and everyday flows.
This page reproduces docs/USER-GUIDE.md from qanum-core v0.9.0 (release tag). Links to files that are not published on this site are shown as plain text until the source repository is public.
Contents
A practical guide to running a Qanum node, configuring it, and using the command-line tools. This is the how to use it document; for the full reference of every RPC method see RPC.md, and for every configuration option see CONFIGURATION.md.
Table of contents
- 1. Quick start
- 2. The two binaries
- 3. Networks and default ports
- 4. Configuration in one minute
- 5. Running the node (
qanumd) - 6. Querying the node (
qanum-cli) - 7. Everyday flows
- 8. Units: QAN and jomas
- 9. Security notes
- 10. Troubleshooting
- Command cheatsheet
1. Quick start
# Build (release). The post-quantum OQS backend is mandatory: `crypto-oqs`
# is a default feature and the node does not build or start without it
cargo build --release --features crypto-oqs
# Start a regtest node that mines blocks continuously
./target/release/qanumd -chain=regtest -gen=1 --reward-address=<your_address>
# In another terminal, talk to it
./target/release/qanum-cli -chain=regtest getblockchaininfo
The qanum-cli commands below assume -chain=regtest (and RPC auth flags
if your node requires them). See the cheatsheet at the bottom.
Tip: regtest is the recommended network for experimenting. Blocks are generated with proof-of-work at minimum difficulty, so they are fast, and you can mint blocks instantly with
generatetoaddress(see Everyday flows).
First sync on testnet
./target/release/qanumd -testnet
# In another terminal: how many peers am I connected to?
./target/release/qanum-cli -testnet getconnectioncount
net_v2 note: the whole P2P stack (peers, header sync, IBD and the QUSN snapshot bootstrap of AIP-0001) is on by default. Opt out with
-nonetv2(CLI) ornetv2=0(qanum.conf); if the log showsWARNING net_v2=disabled, the node is RPC-only (no peers, no sync).
2. The two binaries
| Binary | Purpose |
|---|---|
qanumd |
The node daemon. Starts the node, syncs over the P2P network, mines, serves RPC. Also has one-shot maintenance commands (init, chainstate-info, reindex, …). |
qanum-cli |
The client. Talks to a running node over RPC (--addr). Also has offline commands that read the datadir directly (listkeys, signrequest, verifyrelease, reindex…). |
Ecosystem compatibility: binary roles, flag spelling (
-key=value), config keys and RPC method names follow the de-facto conventions of the Bitcoin node ecosystem, so existing operator habits, scripts, explorers and configuration files transfer directly. Qanum-specific commands are marked as such throughout this guide.
3. Networks and default ports
| Network | -chain= |
RPC (loopback) | P2P |
|---|---|---|---|
| Mainnet | main / mainnet |
127.0.0.1:8338 |
8445 |
| Testnet | test / testnet |
127.0.0.1:18338 |
18445 |
| Regtest | regtest |
127.0.0.1:28338 |
28445 |
All P2P traffic, on every network, is encrypted and authenticated (Noise hybridXX with ML-KEM-1024 and an ML-DSA-87 node identity pinned trust-on-first-use, TOFU). There is a single P2P port. Every network requires the OQS verifier (see Security notes).
Selecting the network (both binaries, same flags)
Both qanumd and qanum-cli accept the same network flags, in single-
and double-dash spelling:
-chain=<net> | --chain=<net> | -chain=<net> # with = or as separate arg
-testnet | -regtest | -mainnet # boolean shorthands
Allowed values: main | mainnet, test | testnet, regtest. Examples
that are all equivalent:
qanumd -chain=regtest qanum-cli -chain=regtest getblockcount
qanumd --network=test qanum-cli -testnet getblockcount
qanumd -regtest qanum-cli --chain=regtest getblockcount
4. Configuration in one minute
Configuration lives in ~/.qanum/qanum.conf (loaded automatically by both
binaries). A commented template ships with the repo:
cp qanum.conf ~/.qanum/qanum.conf
# edit to taste
Precedence: command-line flags > qanum.conf > built-in defaults.
The most important keys:
# Which network: main / test / regtest
chain=regtest
# RPC
rpc=1
rpc_bind=127.0.0.1:28338
#rpcuser=user
#rpcpassword=change-me
# Validation (mainnet requires verifier=oqs)
verifier=oqs
# Mine on startup (CPU mining, demo/testing)
#gen=1
#reward_address=tqn1...
# Optional indexes for the explorer RPC
#txindex=1
#addressindex=1
# In-memory block-store index (default 1). 0 = directory-scan fallback.
#blockstore_cache=1
#blockstore_index=1
#checkblockindex=0
See CONFIGURATION.md for the complete list of keys, flags and their defaults.
5. Running the node (qanumd)
Daemon mode (default)
qanumd -chain=regtest # start, stay in the foreground
qanumd -chain=regtest -gen=1 # start and mine with CPU
qanumd -chain=regtest -gen=1 --reward-address=<addr> # rewards to an address
qanumd -chain=testnet -addnode=peer:18445 # join a specific peer
Useful daemon flags (full list in CONFIGURATION.md):
| Flag | Meaning |
|---|---|
-listen=0 |
Don’t accept inbound P2P connections |
-port=<p> / -port <p> |
Override the P2P port (the only one). Space and = forms are equivalent. -plaintextport was removed: it stops the node |
-nonetv2 |
Disable the whole P2P network stack (net-v2). Any later net-v2 flag (-port, -addnode, …) re-enables it |
-addnode=<host[:port]> |
Also connect to this peer (repeatable). IP or hostname; a hostname is re-resolved every 60 s (dynamic DNS) |
-connect=<ip:port> |
Connect only to these peers (repeatable) |
-seednode=<host\|ip>[:port] / -dnsseed=<host> |
Bootstrap from a static seed (hostname or IP) / DNS seed |
-maxconnections=<n> |
Cap on P2P connections (default 64). Inbound cannot fill the last min(8, n/2) slots, which stay reserved for outbound |
-rpcbind=ip:port |
RPC listen address (default depends on network). --rpc-bind is also accepted. |
--rpcuser=u --rpcpassword=p |
Basic auth for RPC. Prefer qanum.conf (chmod 600) or the QANUM_RPCPASSWORD environment variable over argv — see Secrets and the command line below |
--no-rpc |
Disable the internal RPC server |
-rpcthreads=<n> |
Number of worker threads serving RPC calls (default 16) |
-rpcworkqueue=<n> |
Depth of the RPC work queue (default 64). When full, excess requests are rejected with HTTP 503 “Work queue depth exceeded” |
-gen=1, --mine |
Enable CPU mining |
--reward-address=<addr> |
Address that receives the block reward |
--verifier=oqs |
Post-quantum signature verification |
--strict-full |
Require full validation (PoW + signatures) |
-minimumchainwork=<hex> |
[CVE-2019-25220] Replaces the compiled per-network minimum chain work (0 on every network today). The effective anti-DoS threshold is max(best header tip work − 1 day of work, this value); header chains below it go through the header pre-synchronization before being stored. A node whose tip is below this value stays in IBD. 0 = no static floor |
-assumevalid=<hex\|0> |
Signature-validity assumption: skip post-quantum signature checks only for buried ancestors of this block on the best header chain (PoW and UTXO always verified; never chooses the chain). 0 disables it. Default: the compiled hash of the network (see CONFIGURATION.md) |
One-shot maintenance commands
Run once and exit — they don’t need the daemon running:
| Command | What it does |
|---|---|
qanumd init |
Initialize the chainstate for the selected network |
qanumd chainstate-info |
Show current chainstate info (height, tip, utxos…) |
qanumd mempool-state |
Show mempool statistics (offline) |
qanumd list-mempool |
List transaction ids in the mempool (offline) |
qanumd list-blocks |
List all stored blocks (height, hash, file) |
qanumd show-block <h> |
Show details of one stored block |
qanumd reset |
Reset the chainstate back to the network genesis |
qanumd reindex |
Rebuild the chainstate from the blocks on disk (integrity check + reconnection from genesis with full consensus validation — see “Offline / maintenance”) |
qanumd utxo-snapshot-export <path> |
Export the tip UTXO set as a trustless QUSN v2 snapshot (manifest + full header chain 0..=H + UTXO set) — see “Snapshot sync (QUSN v2)” |
qanumd utxo-snapshot-verify <path> |
Re-link the snapshot’s embedded header chain, re-check PoW where the network enables it, recompute utxo_root and pin it against the local chain |
qanumd utxo-snapshot-install <path> [--force] |
Install a verified v2 snapshot as the sync base of a fresh datadir; refuses to overwrite an existing chain unless --force. Without --force it also refuses a snapshot whose chain has a block marked invalid on this node, or that forks from a known header chain with more work |
qanumd mine-block / mine-blocks <n> |
Mine and import one / n blocks immediately (same template builder as the daemon: max(MTP+1, now), package selection, block budgets) |
qanumd start |
Same as running with no command |
Example:
qanumd -chain=regtest --datadir=/tmp/node init
qanumd -chain=regtest --datadir=/tmp/node mine-blocks 5
qanumd -chain=regtest --datadir=/tmp/node chainstate-info
Note: these one-shot commands work on the datadir directly and take the datadir lock, so they refuse to run while
qanumdis running on that datadir (stop the daemon first, or useqanum-cliover RPC). To submit a transaction or a block, useqanum-cli sendrawtransaction/submitblock.
6. Querying the node (qanum-cli)
The client follows the ecosystem-standard naming. Options:
| Option | Meaning |
|---|---|
-rpcconnect=<ip> |
Host of the node to send commands to (default 127.0.0.1) |
-rpcport=<port> |
Port of the node RPC (default from the selected network) |
--addr=<ip:port> |
Host and port in one argument — Qanum shorthand for the two above |
-chain=<net> / -chain=<net> |
main|mainnet, test|testnet, regtest (also -testnet/-regtest/-mainnet) |
--rpcuser / --rpcpassword |
Basic auth credentials (argv risk — see below) |
-stdinrpcpass |
Read the RPC password from standard input (first line) instead of argv |
-stdinwalletpassphrase |
Read the wallet passphrase from stdin (only for walletpassphrase/walletpassphrasechange; the passphrase becomes the first RPC argument) |
-stdin |
Read extra RPC arguments from stdin, one per line until EOF/Ctrl-D — the way to keep ALL secrets (e.g. both passphrases of walletpassphrasechange, or encryptwallet) out of argv |
--rpccookiefile=<path> |
Cookie file (default datadir/.cookie) |
--datadir=<path> |
Local datadir for offline commands and the default cookie |
--compact |
Compact JSON output |
-h <cmd> / help <cmd> |
Per-command help: usage, arguments, result and examples. Also --help <cmd> and <cmd> --help. qanum-cli help prints the general usage |
-version / --version |
Print the client version and exit — also accepted by qanumd |
Blockchain
getblockchaininfo overall chain state (extended with Qanum fields)
getblockcount height of the best tip
uptime seconds since the node started
stop request a graceful shutdown of the node
estimatesmartfee [n] estimated fee rate for ~n blocks (default 6)
getbestblockhash hash of the best tip
getblockhash <h> hash at height h
getblock <hash-or-h> [verbose] block details; verbose=0 → raw hex
getblockstats <hash-or-h> block statistics (weight, fees, subsidy, percentiles)
getblockfilter <hash> [type] BIP158 compact block filter of a block (type: basic)
(with -blockfilterindex both filter and header come from the persistent index, O(1))
getblockheader <hash-or-h> [verbose]
gettxout <txid> <vout> UTXO lookup (null if spent/nonexistent)
gettxoutsetinfo UTXO set statistics
getindexinfo [index_name] optional-index sync status (txindex / addressindex / blockfilterindex)
getblockstoreinfo block-store cache + index KPIs (listings = real blocks/ scans; index_source)
getchaintips known chain tips (active / valid-fork / headers-only / invalid)
getaddressbalance <addr> balance of an address (requires -addressindex)
getaddresstxids <addr> history of an address (requires -addressindex)
getaddressutxos <addr> unspent outputs of an address (requires -addressindex)
Generating
generatetoaddress <n> <addr> regtest only: mint n blocks instantly to addr
Mining
getmininginfo mining state (gen, blocks mined, LOCAL hashrate, NETWORK hashrate, difficulty, miner threads)
getnetworkhashps [nblocks] [height] estimated NETWORK hash rate (chain work/time; -1 = whole chain)
Mempool / raw transactions
getmempoolinfo mempool statistics (`size`, `bytes`, `maxmempool` = 32e6, fees; plus datadir fields)
getrawmempool [verbose] list of txids (verbose=1 → detail per tx)
getmempoolentry <txid> detail of one mempool tx (fee, size, depends…)
getrawtransaction <txid> [verbose] tx as hex (0) or JSON (1)
sendrawtransaction <hex> [verif=oqs] submit a signed raw tx
Network
getnetworkinfo node/network info (version, services, fees…)
getpeerinfo connected peers
getsnapshotinfo UTXO snapshot status/progress (AIP-0001)
addnode <ip:port> [add|remove|onetry] manage the addnode list
getaddednodeinfo [node] show the addnode list
disconnectnode <ip:port> remove from addnode list and close the connection
setban <ip> <add|remove> [bantime] [absolute] ban/unban an IP (default 24 h)
listbanned list banned IPs (manual and automatic)
clearbanned lift every ban and forget misbehavior scores
Bans and manual peers. A peer that breaks the
protocol accumulates a per-IP misbehavior score; at 100 its IP is banned for
24 h (inbound refused, no automatic outbound). Connections you opened with
-addnode / -connect (or addnode add) are never scored or banned: the node
only logs WARNING: not punishing manually connected peer …, and keeps dialling
them even if their IP is banned (e.g. because of an inbound connection from the
same host). To stop talking to such a peer, use addnode <node> remove, not
setban. setban remove / clearbanned also reset the misbehavior score.
With -blockfilterindex=1, an up-to-date net_v2 node advertises
NODE_COMPACT_FILTERS and serves BIP157 getcfilters, getcfheaders, and
getcfcheckpt requests from the persistent index. P2P requests never trigger
on-demand filter construction; invalid ranges/types/hashes receive no response.
Wallet
getnewaddress [label] [type] new address (`mldsa44` default, `mldsa87` level 5) or recovery key (`slhdsa`, `slhdsa256s`)
getwalletinfo wallet state (encrypted, unlocked_until, keys, balance)
encryptwallet <pw> encrypt the wallet (Argon2id + AES-256-GCM); locks it
walletpassphrase <pw> [t] unlock the wallet in RAM for t s (default 300, 0 = forever)
walletlock lock the wallet (drop decrypted keys from RAM)
walletpassphrasechange <o> <n> change the wallet passphrase
createwallet <name> create and load a named wallet
loadwallet <name> load an existing named wallet
unloadwallet <name> unload a named wallet
listwallets list loaded wallets (restored across node restarts)
backupwallet <destination> atomically back up the wallet file
# WARNING: dumpwallet writes every secret key as plaintext JSON
dumpwallet <filename> export keys (encrypted wallet must be unlocked)
importwallet <filename> validate and merge keys (must be unlocked if encrypted; rejects oversized dumps, wrong suite lengths, and — with OQS — secrets that do not match the public key)
--wallet=<name> select the wallet for the RPC (multi-wallet)
getaddressinfo <addr> address info (template, ismine, label, nist_level)
getreceivedbyaddress <addr> [minconf] total received by an address
listtransactions [count] [skip] P2PQK/P2RKPQ/Vault/HTLC history (confirmed + mempool)
gettransaction <txid> wallet tx details, including custody sends/receives
listsinceblock [hash] wallet history since a block
signmessage <addr> <msg> sign a message with a wallet key
verifymessage <addr> <sig> <msg> verify a message signature
sendmany <addr:amt,...> <fee> send to multiple addresses at once
keypoolrefill [size] pre-generate wallet keys of the -addresstype type (OQS required)
migratesuites [level5] [execute] [options] move funds to level-5 keys (dry run by default)
abandontransaction <txid> mark a wallet tx as abandoned
> New encrypted-wallet blobs keep only ownership metadata (`suite_id`,
> `key_id`, label and key count) outside the ciphertext. Balance, UTXO,
> history and `ismine` reads therefore work while locked. Signing, key
> generation, `listkeys`, `showkey`, `dumpwallet` and `importwallet` still
> require `walletpassphrase`. Older blobs migrate on passphrase change or the
> next authenticated wallet rewrite.
getbalance live P2PQK/custody balances (spendable/delayed/total)
listunspent [minconf] wallet-controlled UTXOs
listreceivedbyaddress [minconf] [include_empty]
listkeys public keys in your keyring (alias: list-keys)
showkey <key_id> details of one key (alias: show-key)
sendtoaddress <addr> <value> <fee> send (value/fee in QAN or jomas)
sendfrom <from> <to> <value> <fee> send from a specific address
Unconfirmed change and spendable balance. Coin selection spends
confirmed UTXOs only: after a send, your change sits in the mempool and
is not selectable until the transaction mines. A back-to-back send that
needs that change therefore fails with insufficient spendable funds; the
error itself reports any unconfirmed wallet-owned outputs pending in the
mempool (amount and an example txid) so automated clients can tell the
transient state apart from a genuinely empty wallet. getbalance counts
those pending outputs in spendable/delayed (confirmed UTXOs already
spent in the mempool are excluded), so the balance and the selection policy
differ by design: the balance shows what you own, the selector spends what
is confirmed.
Advanced addresses and custody
addr-htlc <sender> <recv> <hash_lock> <expiry> HTLC address
addr-p2rkpq <main_key> <rec_key> <delay> custodial recovery address
addr-vault <hot_key> <cold_key> <delay> time-locked vault address
createmultisig <m> <key1> [key2..key16] m-of-n multisig address
p2rkpq-main <txid> <vout> <dest_key> <value> spend P2RKPQ main branch
p2rkpq-recovery <txid> <vout> <dest_key> <value> spend P2RKPQ recovery branch
htlc-claim <txid> <vout> <preimage_hex> <dest_key> <value> claim before timeout
htlc-refund <txid> <vout> <dest_key> <value> refund at/after timeout
vault-init <txid> <vout> <pending> [return_val] initialize a vault
vault-recover <txid> <vout> <dest_key> <value> recover a vault after delay
vault-finalize <txid> <vout> <dest_key> <value> finalize a vault
vault-cancel <txid> <vout> <dest_key> <value> cancel a vault
All P2RKPQ/Vault/HTLC builders honor --wallet=<name>. The selected wallet
must own the key for the requested branch: main/recovery for P2RKPQ; hot for
vault init/finalize; cold for vault recover/cancel; recipient for HTLC claim;
refund for HTLC refund. A wrong wallet fails early with
KeyNotOwnedByWallet. Ownership can be checked while a new-format encrypted
wallet is locked, but signing still requires walletpassphrase.
The P2RKPQ recovery key must be an SLH-DSA-128s key id — generate it with
getnewaddress <label> slhdsa, which returns {key_id, suite, note} instead
of an address (SLH-DSA-128s has no standalone payment address; the key_id
is what addr-p2rkpq takes as <rec_key>). The requirement is enforced at
build time with an actionable error. Consensus also accepts the level-5
pair, an ML-DSA-87 main key with an SLH-DSA-SHAKE-256s recovery key (from
the suites5 activation height), but never a pair that mixes levels:
create it with getnewaddress <label> mldsa87 and
getnewaddress <label> slhdsa256s.
NIST level 5. From the suites5 activation height the wallet
creates level-5 keys: getnewaddress <label> mldsa87 (an ML-DSA-87
address) and slhdsa256s (a recovery key). Before that height they are
refused with error -4, which names the height. Without -addresstype the
default type is ML-DSA-87 once level 5 is active and ML-DSA-44 before;
qanumd -addresstype=<type> fixes it; -changetype chooses the change type (if
unset, change follows a level-5 recipient, as in Bitcoin Core).
getaddressinfo, validateaddress and listkeys report nist_level
(ML-DSA-44 = 2, SLH-DSA-128s = 1, ML-DSA-87 and SLH-DSA-256s = 5). A level-5
input costs more: about 7.2 kB of signature against 3.7 kB, so roughly twice
the fee.
Retirement of level 1/2. From a second height (retire12: testnet
from its genesis, height 0; regtest only with -testactivationheight=retire12@<h>)
new outputs may no longer use ML-DSA-44 or SLH-DSA-128s. Old coins stay
spendable. getnewaddress mldsa44 and a send to an ML-DSA-44 address fail
with -4 (ML-DSA-44 is retired: … use a level-5 suite (mldsa87)); new
keys, change and mining rewards use ML-DSA-87. A vault funded before the
height can still be unvaulted and finalized. getblockchaininfo shows both
heights under softforks.
Moving existing funds to level 5: migratesuites. Migration is
optional; old outputs stay spendable.
qanum-cli migratesuites # dry run: plan, exact fees, what stays
qanum-cli migratesuites level5 true # execute (wallet unlocked if encrypted)
qanum-cli migratesuites level5 true '{"fee_rate":2,"consolidate":true}'
- The dry run changes nothing. It lists the holdings, the transactions it would send with their exact size and fee, the outputs it cannot move (each with the reason and the manual path) and what spending the new outputs will cost.
- The execution creates fresh level-5 keys, labelled
migrated-from:<key id>, then signs and sends. It sends one transaction per source key, so addresses you kept apart are not linked;consolidate=truemerges them. - Each transaction stays under the mempool’s 101 000-byte limit, about 26 ML-DSA-44 inputs.
- What it does not move automatically: multisig, vault, HTLC, immature coinbase, and a P2RKPQ whose recovery key is not in this wallet (creating a new one here would change who holds it). Spend those by hand to a level-5 address.
The remainder is the fee. These one-shot custody spends take exactly one
input (<txid> <vout>) and create only the output(s) you ask for — there is
no change output and no fee argument: whatever is left of the input value
after <value> (for vault-init, after pending_value and return_value)
goes to the miner as fee. Choose <value> accordingly; a <value> equal
to the input value leaves a zero fee and the submit fails with FeeTooLow.
The payment commands (sendtoaddress, sendfrom, sendmany) and
buildp2multispend work the other way around: they take an explicit <fee>
and return change.
createmultisig <m> <key1> [key2..keyN] builds an m-of-n multisig (P2MULTI)
address. Consensus accepts 1 to 16 keys with 1 <= m <= n, ML-DSA-44
or, from the suites5 activation height, ML-DSA-87, mixed if wanted;
qanum-cli help createmultisig prints the current limit, which is read from
consensus rather than written into the help text. Unlike the builders above
it does not require the wallet to own any of the keys: you can derive the
address of a multisig you only cosign, or do not sign at all. Key ids are
sorted canonically before encoding, so every cosigner who lists the same set
of keys gets the same address regardless of the order they type them in.
Duplicate keys are rejected: they would quietly lower the real threshold.
Paying such an address works from sendtoaddress, sendmany and sendfrom
like any other template. Spending from a P2MULTI output is covered below
under Threshold spending.
addr-htlcer> <payment_hash> <timeout> now emits the canonical
109-byte HTLC payload. receiver claims before timeout with the matching
preimage; sender refunds at or after timeout. The selected wallet must contain
both address keys. htlc-claim validates SHA3-256(preimage) and height before
building; htlc-refund validates the timeout height.
listtransactions, gettransaction, listsinceblock, and
getwalletinfo.txcount include P2RKPQ, Vault, VaultPending, and canonical HTLC
receives/sends. This works through the address index, the no-index canonical
fallback, and the mempool (including chained unconfirmed spends). A custody
output appears once even if the wallet owns both branch keys.
getbalance and getwalletinfo.balance also include unconfirmed custody
outputs. P2RKPQ delays, Vault hot/cold rules, VaultPending unlock heights, and
HTLC refund timeouts are reflected in spendable vs delayed totals. HTLC
recipient outputs stay delayed/locked until a separate preimage-aware spend is
built, because the wallet does not store preimages. Chained mempool outputs
that are already spent are not counted.
Threshold spending (P2MULTI)
buildp2multispend <anchor_txid> <anchor_vout> <anchor_value> <anchor_address> \
<dest_address> <value> <fee> [txid:vout:value ...]
signp2multi <unsigned_tx_hex> <anchor_address> <anchor_value> <key_id> [input_index]
combinep2multi <tx_hex_or_build_json> <anchor_address> <anchor_values|-> <seat_json_or_file>...
Spending a P2MULTI output takes m cosigners, so it cannot be a single
command: no one machine holds m keys, and that is the point of a threshold
address. The work splits in four steps, and only the last one needs a node.
- Build.
buildp2multispendproduces the unsigned transaction and the sighash each seat must sign. Change returns to the anchor address — without a change output the remainder of the spent coin would go to the miner as fee. - Sign. Each cosigner runs
signp2multion their own machine with their own wallet (--wallet=<name>), and sends back a small JSON withkey_index,input_index,suite_id,pubkey_hexandsig_hex. This step is offline: no node, no network. - Assemble.
combinep2multimerges the seat JSONs (inline or as file paths;-takes the anchor values from thebuildp2multispendJSON) into the final transaction: it sorts the seats into strictly increasingkey_indexorder, attaches oneauth_datablob per input, and refuses to emit below thresholdm(listing the missing seats). Every seat is validated against the anchor payload (range, duplicates, pinned suite, key id, lengths) and against the sighash recomputed from the tx and the anchor values, and each signature is verified cryptographically (liboqs) before emitting. The output carries thesigned_tx_hexand itstxid_hex. - Broadcast. Send the
signed_tx_hexwithsendrawtransaction. Consensus rejects a transaction whoseauthcount differs from its input count.
Fees are not an afterthought here: each seat contributes an ML-DSA-44 public
key plus signature — 3 735 bytes per signature (7 222 for an ML-DSA-87
seat) — so the cost scales with
m, not with n. A 2-of-3 spend is roughly 7.7 kB; a 9-of-16 is about
33 kB per input. A fee guessed too low is rejected at broadcast, after the
cosigners have already signed.
That per-signature cost also meets a byte ceiling. All m signatures for
one input share that input’s auth entry, capped at
MAX_AUTH_BYTES_PER_INPUT (131 072 bytes). The binding rule is the key cap
of 16: a 16-of-16 ML-DSA-44 spend uses 59 761 of those bytes, and the
budget is sized so that a 16-of-16 with ML-DSA-87 keys (115 553 bytes)
still fits.
Spending several outputs at once
A withdrawal larger than any single P2MULTI output needs to spend more than
one. List the extra outputs after the fixed arguments as txid:vout:value
(value in jomas):
qanum-cli buildp2multispend <txid> 0 40000j <anchor> <dest> 30000j 30470j \
<txid2>:0:40000
The response then carries a sighashes array with one entry per input
(sighash_hex still holds input 0, for single-input callers). Every input is
authorised separately, so the signing work multiplies: a two-input spend
under a 2-of-3 set needs four signatures, not two. Each cosigner signs
each input, passing the input index as the last argument:
qanum-cli --wallet=guardian-a signp2multi <unsigned_hex> <anchor> 40000j <key_id> 0
qanum-cli --wallet=guardian-a signp2multi <unsigned_hex> <anchor> 40000j <key_id> 1
A signature is bound to its input: the sighash preimage commits to the input index and to the value of the output that input spends, so the same seat’s signature cannot be reused on another input. The fee grows roughly linearly with the input count for the same reason — each input carries its own witness.
Because each input pays for its own m signatures, a high threshold also
bounds how many outputs one transaction may consolidate:
MAX_TX_AUTH_BYTES is 1 000 000, so a 16-of-16 spend fits 16 inputs,
while a 2-of-3 fits over a hundred. A withdrawal that needs more must be
split across transactions.
signp2multihonours--wallet=<name>. A guardian seat normally lives in its own wallet rather than the datadir root; without the flag the command looks in the root keyring and reports the key as missing.
Peer identities (TOFU) and the node identity
listtofupeers pinned peer identities (address, peer_id, last_seen)
removetofuentry <ip[:port]> forget a pin (IP clears every port; IP:port clears one)
resettofu forget ALL pins (LAN testing)
rotatep2pidentity [true] new node identity; peers that pinned the old one follow it
These are RPCs of the running node. With the node stopped, delete
<datadir>/peer_ids.json to forget every pin. See
Security notes before removing pins or rotating.
Offline / maintenance
reindex rebuild chainstate from disk (node must be stopped)
reindexfilters rebuild the BIP157/158 filter index from disk (node must be stopped)
signrequest <request.json> [out.json] cold-sign a transaction request
verifyrelease <dir> check a downloaded release: post-quantum signatures of SHA512SUMS + SHA-512 of every file (docs/SECURITY.md)
build-payment <src> <addr> <value> <fee> build unsigned tx (debug)
build-payment-auto <addr> <value> <fee> build unsigned tx, auto UTXO
build-p2rkpq-main <txid> <vout> <dest> <value> dry-run P2RKPQ main builder (prints the signing request; remainder is fee)
datadir-info datadir diagnostics (paths, files existence)
list-blocks all stored blocks (height/hash/path + index fields)
reorg-plan fork realignment plan (null when on the best tip)
Values in
build-payment*accept QAN decimals (1.0) or jomas integers (100000000), likesendtoaddress.
reindexandreindexfiltersare full offline rebuilds (the node must be stopped) and print progress while they run.reindexverifies every block file (header re-hash against the index), puts the chainstate back at genesis (the block files are kept) and reconnects the best stored chain with full consensus validation, as Bitcoin Core-reindex-chainstatedoes: proof of work, subsidy, scripts and signatures with the--verifierof the command (signatures are skipped only for blocks covered by the compiled assumevalid, as in a normal sync). A stored block that fails is discarded and the best valid chain stays; the log ends withreindex: chainstate rebuilt with full validation: height=… tip=… (connected=… discarded=…). Its cost is that of validating the stored chain again (not yet measured on the full testnet chain). Ifchainstate.binis missing or corrupt, the node does the same by itself at start (logchainstate reset to genesis: …). On a pruned datadirreindexis refused: the pruned blocks keep no signatures, so the chain cannot be validated again from disk (as in Bitcoin Core, where pruning is incompatible with-reindex-chainstate); sync a new datadir instead. Missing undo data of recent blocks is rebuilt by the node itself at start, pruned or not.reindexfiltersrebuilds the BIP157/158 index linearly and is much cheaper. Plan a maintenance window.When the block data itself is suspect (disk errors,
blocks/copied from another machine), do a fresh initial block download instead: stop the node and start it with a new datadir — keeping only the irreplaceable parts of the old one (wallets/,p2p_identity.json,peer_ids.json,addrman.json) — and let it sync from the network (~25 min for ~24.5k testnet blocks on Pi-class hardware).
7. Everyday flows
generatetoaddressis synchronous: the CLI prints the block hashes when all requested blocks are mined. Progress can be followed withgetblockcountfrom another terminal (it advances during generation).
Mint regtest coins instantly (no CPU wait)
ADDR=$(qanum-cli -chain=regtest getnewaddress | python3 -c "import json,sys;print(json.load(sys.stdin)['address'])")
qanum-cli -chain=regtest generatetoaddress 101 "$ADDR"
qanum-cli -chain=regtest getbalance
generatetoaddress skips the proof-of-work search (regtest does not check
the header hash against the target; other regtest nodes accept the
blocks), validates each block like a received one and announces it to
connected peers, so you reach coinbase maturity (1152 blocks) in about half a
minute instead of ~20 minutes of CPU mining.
Mine with the daemon and collect rewards
ADDR=$(qanum-cli -chain=regtest getnewaddress)
qanumd -chain=regtest -gen=1 --reward-address="$ADDR" --mining-interval-ms=500
# in another terminal:
qanum-cli -chain=regtest getbalance
Send a payment
qanum-cli -chain=regtest sendtoaddress <destination> 1.0 0.0001
# 1.0 QAN amount, 0.0001 QAN fee — values accept QAN decimals or jomas
sendtoaddress builds the transaction, signs it locally with your keyring
(requires the OQS build for signature generation) and submits it.
Node fee rate (settxfee). Pass 0 as the fee and the send charges the
node’s configured rate instead: settxfee <rate> persists a fee rate in
jomas per byte (integer — not a QAN/kvB decimal), and every send built
with fee=0 (sendtoaddress, sendfrom, sendmany and the build-payment*
dry-run builders) then charges rate × estimated signed size. settxfee 0
clears the rate and returns to explicit-fee-only sends. An explicit non-zero
fee always wins over the configured rate, so settxfee never silently alters
a send that carries its own fee.
Sign a transaction offline (cold signing)
# 1) build an unsigned payment request (the response embeds a signing_request)
qanum-cli -chain=regtest build-payment-auto <dest> 1.0 0.0001 > response.json
python3 -c "import json;json.dump(json.load(open('response.json'))['signing_request'],open('request.json','w'))"
# 2) sign it on an offline machine (needs the OQS build + the wallet datadir)
qanum-cli --datadir=/path/to/offline/wallet signrequest request.json signed.json
# 3) broadcast the signed hex from the node
qanum-cli -chain=regtest sendrawtransaction \
$(python3 -c "import json;print(json.load(open('signed.json'))['signed_tx_hex'])")
Synchronize two nodes
# Node A (miner)
qanumd -chain=regtest --datadir=/tmp/a -gen=1 --reward-address=<addr> &
# Node B (joins A)
qanumd -chain=regtest --datadir=/tmp/b -addnode=127.0.0.1:28445 &
# both should converge to the same height:
qanum-cli -chain=regtest --addr=127.0.0.1:28338 getblockcount
qanum-cli -chain=regtest --addr=127.0.0.1:28348 getblockcount
Peer admin:
qanum-cli -chain=regtest addnode 127.0.0.1:28445 add
qanum-cli -chain=regtest getaddednodeinfo
qanum-cli -chain=regtest disconnectnode 127.0.0.1:28445
Snapshot sync (QUSN v2)
QUSN snapshots let a fresh node start from a recent UTXO set instead of
replaying the whole history — without trusting anyone. Every header
commits the UTXO-set root (utxo_root), so the artifact carries its own
proof: the downloader re-links the embedded header chain (hash/prev
linkage + PoW where the network enables it) and recomputes the root, which
must equal the tip header’s own commitment. Compare with assumeutxo-style
bootstrap, where the snapshot hash is anchored socially.
Two ways to get the artifact:
1. Out-of-band (operator copies a file). An archive node exports it:
# On the source node, with the daemon STOPPED (one-shot commands take the
# datadir lock and refuse to run while qanumd is running)
qanumd --datadir=/data/qanum utxo-snapshot-export /tmp/snapshot.qusn
# On the receiving machine: verify first (prints the re-linked header
# count, the committed vs recomputed root, and the pin against the local
# chain if one exists)
qanumd --datadir=/data/qanum-fresh utxo-snapshot-verify /tmp/snapshot.qusn
# Install as the sync base (fresh datadir only; --force overrides)
qanumd --datadir=/data/qanum-fresh utxo-snapshot-install /tmp/snapshot.qusn
Then start the daemon normally: it forward-syncs only blocks H+1..tip.
The export always follows the best chain: it walks back from the
chainstate tip through prev_hash links rather than scanning the block
directory by height, so blocks left on disk by past reorgs (stale fork
blocks) can never leak into the artifact — even on a node whose block
store contains abandoned branches.
2. Over the network (AIP-0001).
Archive nodes advertise service bit NODE_UTXO_SNAPSHOTS and serve the
artifact in rate-limited 2 MiB chunks. A fresh node opts in at start:
# Public network: servers and corroborators are found through the DNS seeds
qanumd -testnet --datadir=/data/qanum-fresh -snapshot-ibd=1
# Naming the archive node: it is tried first; corroborators still come from
# the DNS seeds (or from more -connect/-addnode peers, see below)
qanumd -testnet --datadir=/data/qanum-fresh -snapshot-ibd=1 -connect=<archive-host>:<p2p-port>
A single peer is never enough: the snapshot is installed only after an independent peer confirms its tip (see “Corroboration” below).
The bootstrap runs before the P2P runtime starts: connect (Noise + TOFU),
handshake, download to <datadir>/.qusn-download/, verify through the
same trustless path as a local file, install, then the regular sync loop
finishes IBD. The whole bootstrap runs under a global time budget of
15 minutes across all candidates, so fallen or malicious peers can never
delay startup indefinitely — when the budget runs out the node logs it
and falls back to normal IBD like any other bootstrap failure. On
any failure (unreachable peer, peer without the service bit,
verification failure, budget exhausted…) it falls back to normal genesis IBD —
the flag can never make a node unable to sync.
Resumable, chunk-checked downloads. Every 2 MiB chunk is verified on
arrival against a Merkle root announced up front (a corrupt or malicious
chunk is rejected immediately, and the bootstrap moves on to the next
candidate), and progress is saved after each verified chunk. If the node
restarts — or switches to a different server offering the same snapshot
— the download resumes from the last verified chunks instead of starting
over (gaps included); a different snapshot restarts it. A partial
.qusn-download/ is only resumed while the datadir is still fresh (for
example after the node was stopped mid-download). Once the node has fallen
back to normal IBD and has a chain, a leftover partial download is deleted
at the next start, and -snapshot-ibd=0 always deletes it. Size policy: the artifact install cap is 512 MiB
and it is enforced up front (offers above it are rejected before any
chunk is downloaded, and the staged file is size-checked before it is
read into memory), so an oversized or malicious artifact wastes neither
bandwidth nor RAM — the node simply falls back to normal IBD.
Parallel download. Multi-chunk snapshots are fetched from up to 4
servers at once: any extra peer offering the same artifact (checked
field by field from QusnMeta) gets a disjoint slice of the missing
chunks. Per-chunk Merkle proofs keep this trustless — a server feeding
corrupt chunks only loses its slice, and the missing chunks are re-dealt
to the remaining peers. With a single archive node the download simply
runs over one connection. Connections that survive a round are RE-USED
in the next one (they keep their registered server stream), and the
downloader honors each server’s rate-limit window locally before
re-asking — servers answer a too-early GetQusn with an empty meta
instead of silence, so a re-acquisition never stalls the round.
Peer discovery. You don’t need to know an archive node up front.
Explicit -connect/-addnode candidates are tried first; any reachable
peer WITHOUT the snapshot bit is asked for its address table (GetAddr),
and the entries that announce the bit join the candidate list. When the
list runs dry, the bootstrap refills it from (1) the local addrman —
peers recorded with NODE_UTXO_SNAPSHOTS in previous runs — and (2) the
network’s DNS seeds (or -dnsseed=<host> overrides). With nothing to try
at all it reports no candidate peers and falls back to normal IBD.
(qanumd asks the DNS seeds for snapshot candidates even under -connect;
the regular P2P runtime that starts afterwards honours connect-only mode.)
Corroboration and minimum work. Verification proves the artifact is internally consistent; it cannot prove that its chain is the one the honest network follows. Two checks tie it to the network before anything is installed:
- Minimum chain work. The snapshot tip must carry at least the
network’s minimum chain work (compiled per network;
-minimumchainworkoverrides it). A cheaper artifact is refused. - Independent confirmation.
-snapshotcorroborators=<n>peers (default and minimum 1) must answer aGetHeadersrequest from the tip’s parent with the snapshot tip itself. A corroborator is not one of the servers offering this artifact and sits in a network group (IPv4 /16, IPv6 /32) different from the servers and from the other corroborators. Peers met during discovery count as candidates. A peer that answers with a different block rejects the artifact (the next server is tried). A candidate that is behind or cannot be reached (restarting, connection cut) is asked again, 10, 20, 40 and then every 60 s, so a short outage of the only corroborator does not cost the snapshot; too few confirmations within the 15-minute budget mean normal IBD, with aWARN snapshot IBD: verified snapshot ... NOT installedline.getsnapshotinfokeeps the last attempt inlast_bootstrap_attempt(outcomeunconfirmed, withconfirmed/needed). - The unconfirmed snapshot is kept (
.qusn-download/verified.qusn, at most one). At each start with-snapshot-ibd=1, while the local chain is still below it, it is verified again and its tip corroborated again (never by the servers that sent it). Once confirmed, it replaces the chain synced so far, provided that chain is a strict prefix of the snapshot chain; the blocks and undo data below the snapshot are then deleted, so the datadir ends like a fresh snapshot install. If the local chain forks below the snapshot, or reaches its height, the snapshot is deleted. This is how Bitcoin Core’sloadtxoutsetworks during IBD. - What the node already knows. A snapshot is refused when a block of
its chain is marked invalid on this node (
invalidateblock), or when the node knows a header chain with more work that does not contain the snapshot tip (Bitcoin CoreActivateSnapshot).
In practice: on the public testnet the two DNS seeds sit in different
networks and serve this role automatically. On a private network name
at least one more peer that does not serve the same artifact and is in a
different /16 (-connect/-addnode). A private testnet whose nodes all
share one LAN (one /16) cannot corroborate, so -snapshot-ibd falls back to
normal IBD there — use the out-of-band install (way 1) instead. Regtest
groups peers by full socket address, so local multi-node tests work.
After install the node cannot reorganize below the snapshot height (it
has no block bodies or undo data there). If the network ever follows a
chain that forks below it, the node logs a WARN line saying so and asking
to wipe the datadir and resync; the peers are not punished.
Operational notes:
- Snapshot bootstrap only applies to a datadir at genesis; it never overwrites real history.
- The bootstrapped node keeps no block bodies below the snapshot height
(only headers inside the chainstate); historical
getblockneeds an archive peer or the bodies synced separately. Undos for blocks after the snapshot are built normally as the chain extends. - Servers: serving is read-only, capped by
MAX_QUSN_BYTES, limited to one stream per peer with a 60 s re-request interval (a request inside the window gets an empty-meta answer plus misbehavior, never silence); at most 4 chunk requests may be in flight per peer; prune=off (archive) nodes only. The export itself is cached: the artifact is produced once per tip and reused for every request until the chain advances, so several nodes bootstrapping in the same block interval cost the archive node a single export. The cache keeps one artifact (datadir/snapshots/qusn-<height>.qusn); a new export replaces the previous file. Stale artifacts never accumulate: superseded files are swept at export time, leftovers from previous runs are removed at daemon startup, and a manualutxo-snapshot-exportwhose destination is the cache directory sweeps its obsolete siblings too (files with other names are never touched). - Diagnostics:
qanum-cli getsnapshotinforeports the whole picture on a running node — whether it advertises the snapshot service bit and can serve, how this chainstate was bootstrapped (bootstrap, written todatadir/snapshot_bootstrap.jsonat install time), which artifacts are staged underdatadir/snapshots/, and the live serving activity (streams in flight, rate-limited peers, streams served since startup, how many were served from the export cache, and — if it has ever run — the background IBD progress underbody_backfill).
Background IBD: filling in the bodies below the snapshot (iteration 11).
A snapshot node starts with headers 0..=H but block bodies only above
H. With -backfill-bodies=1 (requires net_v2) the daemon downloads the
missing bodies 1..=H from connected peers in the background, through
the normal protocol, without ever touching the chainstate. Each body is
verified against its PoW-committed header before being stored — header
hash, prev link AND the transaction-tree commitments of that header
(merkle_root over txids, auth_root over wtxids, duplicate txids
rejected), so a peer cannot serve the real header with arbitrary
transactions; progress persists in datadir/.body-backfill/resume.json
after EVERY stored block (atomic write), so restarts resume exactly where
they stopped. The honest guarantee: the stored bodies are exactly
those committed by the header chain verified at snapshot install — the
transactions inside cannot be re-validated against the historical UTXO
set (which this node does not hold), so if full historical verification
matters to you, run a full IBD from genesis instead. Progress and state
are reported by getsnapshotinfo (body_backfill). One operational
note: the worker opens its own connection to each candidate peer. There
is no per-IP connection cap (as in Bitcoin Core), so several nodes
behind one NAT can backfill from the same serving peer. On the live testnet deployment the full
range (~31k bodies) backfilled in ~13 minutes.
Official testnet snapshots (published artifact). Besides the P2P bootstrap above, the project publishes an official testnet artifact so a fresh node does not depend on reaching a particular live peer:
- Where: the
testnet-snapshotsrelease ofgithub.com/qanum/qanum— a rolling release where each re-export supersedes the previous artifact. Assets are named by export date and height (testnet-<YYYYMMDD>-h<height>.qusn) with a.sha256sidecar. While the repository is private, downloads need a GitHub token; the same URL works anonymously once the repository is public. - Freshness: the artifact is re-exported from the live testnet tip at least weekly, and on every version bump that changes the snapshot format. Treat a snapshot as stale when it is more than 14 days old or more than 20,000 blocks behind tip — fetch a newer artifact or fall back to a plain IBD. Forward-sync closes the remaining gap either way, so staleness only affects the size of that gap, never correctness.
-
Flow: always check the sidecar, then verify before installing:
sha256sum -c testnet-<YYYYMMDD>-h<height>.qusn.sha256 qanumd --datadir=/data/qanum-fresh utxo-snapshot-verify <artifact> qanumd --datadir=/data/qanum-fresh utxo-snapshot-install <artifact> -
Expectations by profile (measured on a ~7.9 MB artifact at height ~32.8k, against a same-host seed over loopback):
Profile verify install full QUSN network bootstrap Raspberry Pi 4 (4×Cortex-A72 @ 1.8 GHz) ~12 s ~12 s ~65 s VPS-class x86_64 (3-core EPYC) ~3 s ~3 s < 45 s Both profiles sit far below the 900 s bootstrap budget; even a modest wide-area link for the ~8 MB artifact does not change that.
8. Units: QAN and jomas
- The smallest unit is the joma.
1 QAN = 100_000_000 jomas(1e8). - RPC responses report balances in jomas (integers) and QAN
(
spendable_qan,total_qan). - CLI amount arguments (
sendtoaddress,build-payment*) accept either:- a QAN decimal with up to 8 decimals:
1.0,0.0001 - an explicit jomas amount with the
jsuffix:100000000j,5j
- a QAN decimal with up to 8 decimals:
- A bare integer is ambiguous and rejected (the only
exception is
0): write5.0for 5 QAN or5jfor 5 jomas. The error message always shows both forms. - Fees in the CLI are also in QAN or jomas, same rule.
9. Security notes
- Signature verification is mandatory. Every node verifies with the
OQS backend (
--verifier=oqs, the default and only value) after a startup known-answer self-test of every signature suite and of the ML-KEM-1024 key exchange; if the backend fails it, the node does not start.accept-all/reject-allexist only in the regtest simulation build (--features sim-verifiers, version tagged-sim). - P2P is always encrypted and authenticated. Every connection runs a
Noise hybridXX handshake (X25519 + ML-KEM-1024, ChaCha20-Poly1305,
SHA-512) in which each node proves its ML-DSA-87 node identity
(
p2p_identity.json, owner-only);peer_idis a hash of that key (getnetworkinfo.localpeerid,getpeerinfo.peer_id). Only one connection per identity is kept. There is no unencrypted port on any network. - Peer identities are pinned with TOFU (
peer_ids.json, owner-only) keyed byip:port. A different identity at a pinned address is rejected, unless the peer proves a rotation: its new identity carries a signature of exactly the pinned identity (seerotatep2pidentitybelow), and then the pin moves to the new identity. Any other change needsremovetofuentry(orresettofu). Uselisttofupeersto inspect. Removing a pin re-opens the TOFU window for that peer immediately: the next outbound handshake pins automatically whichever identity answers — remove only when a legitimate change is expected, and verify the newpeer_idout-of-band vialisttofupeersafterwards. Pins are created by outbound/feeler handshakes only — an inbound peer never creates a pin (an unsolicited connection could otherwise pre-squat a victimip:port), and the store is capped with LRU eviction. The first handshake to an address pins whoever answers, so treat DNS-seed results as untrusted for initial anchoring: pin identity-critical peers via explicit-connect/addnodefirst. A corruptpeer_ids.jsonis a hard error: a copy is saved (peer_ids.json.corrupt-<ts>), the file is left in place and startup is refused until the operator restores a good copy or consciously deletes the file (every pin would be re-learned — a MITM window on a hostile network). - Rotating the node identity (
rotatep2pidentity, node running): the current key signs a new one, and for 60 days every handshake carries that signature, so peers that pinned the current identity move their pins. The previous secret key is not kept. A second rotation within those 60 days would cut off peers that still pin the identity before it, so it needsforce(rotatep2pidentity true). The identity file is replaced whole, but the filesystem may still hold the old file’s blocks: if the key leaked, treat the old disk as sensitive too. - The TLS files of earlier versions (
peer_certs.json,p2p_tls_key.pem,p2p_tls_cert.pem) are no longer read and are never deleted by the node; they can be removed. - Coinbase maturity is 1152 blocks (~1 day of chain time) on every
network. Mined rewards are
delayeduntil then;getbalanceshows both. - RPC binds to loopback by default. Use
--rpcuser/--rpcpasswordor the cookie file; never expose the RPC port to the internet. -
Secrets and the command line: anything passed as an argument is world-readable on the host while the process lives (
ps,/proc/<pid>/cmdline) and lands in shell history. For RPC passwords preferqanum.conf(chmod 600) or theQANUM_RPCPASSWORDenvironment variable (both binaries; used only when no flag/config value is set). Inqanum-cli, wallet passphrases and the RPC password can be read from standard input (the-stdin…options):# RPC password by stdin (first line): -stdinrpcpass echo "rpc-secret" | qanum-cli -stdinrpcpass -rpcuser bob getblockchaininfo # Wallet passphrase by stdin (prompt when interactive; pipe recommended) qanum-cli -stdinwalletpassphrase walletpassphrase 600 <<< "wallet-secret" # BOTH passphrases of walletpassphrasechange out of argv: -stdin, one per line printf 'old-secret\nnew-secret\n' | qanum-cli -stdin walletpassphrasechange # encryptwallet passphrase out of argv printf 'new-secret\n' | qanum-cli -stdin encryptwalletConsumption order when combined:
-stdinrpcpasstakes the first line,-stdinwalletpassphrasethe next,-stdinthe rest until EOF. Note: the interactive prompt does not disable terminal echo (nolibc/rpassworddependency was added — supply-chain decision); use pipes when screen echo is a concern. A one-time warning is printed to stderr when-rpcpasswordis used on the command line. - The built-in CPU miner is for demo/testing only.
10. Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
Connection refused from qanum-cli |
Node not running, wrong --addr/--network, or RPC disabled |
verifier accept-all is not available in this build… |
Signature verification is mandatory; drop the verifier option (the default is oqs) |
post-quantum crypto self-test failed… |
The liboqs backend (ML-DSA / SLH-DSA signatures or the ML-KEM-1024 key exchange of the P2P transport) gave a wrong answer at startup; the node refuses to start. Rebuild from a clean tree and report it |
No such mempool transaction. Enable -txindex… |
The tx may already be mined; enable -txindex to query confirmed txs |
NonceExhausted on mine-blocks |
An explicit --max-nonce cap was too low; raise it or drop it (default: no cap) |
| Peers don’t connect | Wrong network magic, TOFU mismatch (removetofuentry <ip:port> or resettofu), or onlynet restriction |
| Blocks not imported / sync stuck | Check the log for [chain.fork] / [net.sync] lines; qanumd reindex if chainstate is corrupt |
[chain] GENESIS MISMATCH / ParentUnknown(<hash>) on sync |
The peer’s chain starts from a different genesis than yours — usually an old binary or an old datadir with a previous network genesis. Compare qanum-cli getblockhash 0 on both nodes; upgrade and re-init the outdated node (qanumd -chain=<net> reset keeps wallet_keys.json) or use a fresh --datadir |
datadir-info shows missing files |
The node hasn’t started on that datadir yet (init first) |
Command cheatsheet
# Node lifecycle (same flags on both binaries)
qanumd -chain=regtest init
qanumd -chain=<main|test|regtest> [options] # run daemon
qanumd -regtest chainstate-info # offline info
qanumd -testnet reindex # offline rebuild
qanumd -chain=test reset # back to genesis
# Chain state
qanum-cli -chain=regtest getblockchaininfo
qanum-cli -chain=<net> getblockcount
qanum-cli -chain=<net> getblock <hash-or-height>
qanum-cli -chain=<net> getblockheader <hash-or-height>
qanum-cli -chain=<net> gettxout <txid> <vout>
# Mempool / txs
qanum-cli -chain=<net> getmempoolinfo
qanum-cli -chain=<net> getrawmempool
qanum-cli -chain=<net> getrawtransaction <txid> 1
qanum-cli -chain=<net> sendrawtransaction <hex>
# Network / peers
qanum-cli -chain=<net> getnetworkinfo
qanum-cli -chain=<net> getpeerinfo
qanum-cli -chain=<net> addnode <ip:port> add|remove|onetry
qanum-cli -chain=<net> generatetoaddress <n> <addr> # regtest
# Wallet
qanum-cli -chain=<net> getnewaddress
qanum-cli -chain=<net> getbalance
qanum-cli -chain=<net> listunspent
qanum-cli -chain=<net> sendtoaddress <addr> <qan> <fee_qan>
# Threshold spending (P2MULTI): build, sign with m seats, assemble, broadcast
qanum-cli -chain=<net> buildp2multispend <txid> <vout> <value>j <anchor> <dest> <value>j <fee>j
qanum-cli -chain=<net> --wallet=<seat> signp2multi <unsigned_hex> <anchor> <value>j <key_id> [input_index]
# Native Qanum diagnostics
qanum-cli -chain=<net> list-blocks
qanum-cli -chain=<net> datadir-info
qanum-cli -chain=<net> reorg-plan
reorg-planprints the fork-realignment plan (detach/attach hashes, common ancestor) only when the local chain is not the best one; otherwise it printsnull— including right afterinvalidateblock, because the invalidation performs the detach itself.