← Builder hub
QTC Consensus LabThe difficulty & proof-of-work laboratory
loading snapshot…
Apps

Quantus · QTC · consensus, read from the source

Every block renegotiates
how hard the next one is.

Quantus doesn't retarget every 2,016 blocks like Bitcoin. It retargets every single block — an Ethereum-Homestead-style formula running inside pallet_qpow, fed by the block's own timestamp. This lab replays that exact algorithm over every real block timestamp since genesis, so the difficulty curve below is the chain's actual difficulty curve — recomputed, not sampled.

⚠ Difficulty is recomputed from on-chain timestamps with the exact runtime algorithm — the indexer doesn't publish it directly. All constants read from Quantus-Network/chain source (links in Method). 100% client-side · nothing leaves your browser

The retarget, exactly

One function, run at the end of every block (on_finalize). Same formula as Ethereum's Homestead — rescaled to a 12-second target.

pallet_qpow::calculate_difficulty
block_time = max(observed_ms, 500)        // author-controlled floor
divisor    = max(target * 10 / 12, 1)     // 12_000 -> 10_000 ms
time_factor = block_time / divisor        // integer division
adjustment  = max(1 - time_factor, -99)
increment   = parent_difficulty / 2048
new = parent + increment * adjustment      // clamped to [2^17, 2^512-1]
Step simulator — drive one retarget yourself

Integer math only — the same BigInt steps the runtime executes. The live default fills in when the snapshot loads.

Retarget zones at the 12 s target

Observed block timetime_factoradjustmentEffect
Asymmetric by design: difficulty can only ever climb +1/2048 (≈+0.049%) per block — the 500 ms floor caps the upside — but it can fall up to −99/2048 (≈−4.83%) per block. It falls fast and climbs slow: after a stall, recovery is a long grind upward. The dead zone (10–20 s) means small jitter changes nothing.

Difficulty history — recomputed from genesis

Every point below comes from replaying the retarget over the real timestamps of all … indexed blocks. Toggle the scale; drag the recent window.

Block-time observatory

What the chain actually felt like: inter-block times from the last … blocks, against the memoryless ideal a perfect 12-second Poisson process would draw.

Log-spaced buckets. The amber curve is the ideal exponential with λ = 1/12 s.

Honest caveat: block time here is the author-controlled timestamp delta the runtime feeds the retarget — not a measured network latency. The 500 ms floor exists precisely because a block author could otherwise lie the timestamp downward; flooring “can only lower the adjustment, so it can never stall the chain” (source comment, pallets/qpow/src/lib.rs).

PoW anatomy: Poseidon2, not SHA-256

Quantus mining hashes with Poseidon2 — a ZK-friendly algebraic hash — instead of SHA-256. The 512-bit output is the whole point.

block_hash32 bytes
nonce64 bytes (512-bit)
Poseidon2 double-squeeze512-bit digest
valid iffdigest < 2512 / D
Expected hashes to win a block= difficulty D
Network hashrate≈ D / 12 s
Nonce space2512 (zero nonce rejected)
Hash functionqp_poseidon_core::hash_squeeze_twice
Why 512 bits matters post-quantum: Grover's algorithm halves the effective security of a hash search. SHA-256 PoW (Bitcoin) drops to ~128-bit quantum security; Quantus's 512-bit Poseidon2 output keeps ~256-bit quantum security for the mining puzzle itself. The signatures (ML-DSA) and the PoW were both sized for the quantum era — read the math in qpow-math/src/lib.rs.

Chain selection & settlement

Heaviest target chain wins

Chain work is the sum of per-block target difficulties — deliberately not the achieved difficulty of the winning hashes. From the source: every block at a given difficulty contributes an identical, deterministic amount of work, “so cumulative chain work tracks expended hash power instead of being dominated by a single lucky hash.” A freak lucky block buys no extra chain weight.

The runtime exposes this through verify_and_get_achieved_difficulty (legacy name kept on purpose): it returns the block's difficulty, and the client accumulates parent_work + difficulty. Because the metric lives on-chain, flipping the whole network to target-based work needed only a Wasm upgrade — no coordinated node-binary rollout.

The 100-block reorg bound

MaxReorgDepth = 100: the protocol's own rule for how deep a reorganization may go. Treat it as settlement guidance, not a promise:

ConfirmationsReading
1–12Fresh; fine for pocket change, reorgable in principle
13–100Deepening; a reorg here is expensive but protocol-legal
> 100Beyond the protocol's max reorg depth — the chain's own finality line

Quantus has no GRANDPA-style deterministic finality on PoW blocks; >100 confirmations is the closest thing to “settled” the protocol defines. Exchanges and bridges should key their confirmation requirements to this number.

51% cost lab

What would it cost to out-hash the network? Enter your own hardware — the lab prices the attack honestly, including the part most calculators skip: there is no public rental market for qpow hashrate.

Honest limits: qpow is CPU-mined Poseidon2 (see MINING.md upstream) — no ASICs, no known GPU miners at scale, no NiceHash-style rental market to price against. Your rig numbers are yours; the network hashrate is the recomputed live estimate. Sustaining >50% also means outrunning the honest chain's difficulty for the whole attack window while the retarget fights back downward on your private fork's slow start.

Method & sources

Claim on this pageSource
Per-block Homestead-style retarget formulapallets/qpow/src/lib.rs :: calculate_difficulty
Target block time 12,000 msruntime/src/lib.rs :: TARGET_BLOCK_TIME_MS
Genesis difficulty 99,999,999,999runtime/src/configs/mod.rs :: QPoWInitialDifficulty
Minimum difficulty 217 = 131,072pallets/qpow/src/lib.rs :: get_min_difficulty
500 ms retarget floorpallets/qpow/src/lib.rs :: MIN_RETARGET_BLOCK_TIME_MS
Max reorg depth 100runtime/src/configs/mod.rs :: MaxReorgDepth
Poseidon2 double-squeeze, validity = hash < 2512/Dqpow-math/src/lib.rs :: is_valid_nonce
Target-based chain work (not achieved)pallets/qpow/src/lib.rs :: verify_and_get_achieved_difficulty
Block timestamps & heightssqm.quantus.com/v1/graphql (public Subsquid indexer)

Difficulty history is recomputed, not read: the indexer doesn't expose QPoW.CurrentDifficulty storage, so scripts/fetch-consensus-data.mjs replays the exact runtime algorithm over every real block timestamp (genesis block uses the 12 s target, exactly as on_finalize does). All 142k+ heights indexed with zero gaps at snapshot time; re-run the script and commit to refresh. Verified against upstream 2026-09-30.