Proof of work
Epic runs three proof-of-work algorithms on one chain at the same time, in a ratio enforced by consensus. Every block declares which algorithm produced it. All three are always valid, in defined proportions, rather than one replacing another at a fork.
The three algorithms
| Algorithm | Bottleneck | Minimum difficulty |
|---|---|---|
| Cuckoo / Cuckatoo | Memory-bound graph cycle search | 3 |
| RandomX | CPU | 4,000 |
| ProgPow | GPU | 200,000 |
Cuckoo parameters: proof size 42, minimum edge bits 19, second proof-of-work edge bits 31, base edge bits 24.
The algorithm implementations live in separate crates, EpicCash/randomx-rust and
EpicCash/progpow-rust.
Difficulty is tracked separately per algorithm, so get_status returns tip.total_difficulty as an
object with one u64 per algorithm, keyed cuckaroo, cuckatoo, randomx and progpow.
A block header names its algorithm in proof and carries the solution in solution, an externally
tagged value whose shape depends on that algorithm:
"proof": "Cuckoo" "solution": {"Cuckoo": [<u64>, ...]} 42 nonces
"proof": "RandomX" "solution": {"RandomX": "<decimal string>"} 32-byte hash, base 10
"proof": "ProgPow" "solution": {"ProgPow": [<byte>, ...]} 32-byte mix, one number per byte
"proof": "MD5" "solution": {"MD5": "<string>"} not scheduled on mainnet
Cuckatoo and Cuckaroo blocks both report Cuckoo in proof and are distinguished by edge bits. The
types are in api/src/types.rs:574.
The block policy
Which algorithm the next block must use is decided by a deterministic policy. The implementation holds a counter per algorithm and selects whichever is furthest behind its allotted share of the current 100-block window, so the mix over a window is exact rather than an average.
Allotments per 100 blocks:
| Era | RandomX | ProgPow | Cuckatoo | Cuckaroo |
|---|---|---|---|---|
| Eras 1 to 2 | 60 | 38 | 2 | 0 |
| Era 3 onward | 48 | 48 | 4 | 0 |
Cuckaroo is allotted zero on mainnet. It exists in the type system and is not scheduled.
The policy is compiled into the node and is not configurable through epic-server.toml on mainnet.
Two keys narrow it on a test network:
[server]
only_randomx = true # every block must be RandomX
no_progpow = false # or just drop ProgPow from the mix
only_randomx = true is what lets a single CPU miner advance a private chain: under the mixed policy
a RandomX-only miner idles on every ProgPow height. Both keys change the block policy, so a chain
mined under one setting is not valid under the other. Wipe chain_data when you change either.
The mainnet allotments are at
core/src/core/block/feijoada.rs:249,
next_block_bottles()
advances the window and resets it at 100 blocks,
choose_algo() makes the
selection over
PolicyConfig,
and get_bottles_default()
returns the zeroed counters each window starts from. The constants are in
core/src/consensus.rs.
Difficulty
Each algorithm has its own difficulty track, retargeted independently by
next_difficulty(). The adjustment window
is 60 blocks
(DIFFICULTY_ADJUST_WINDOW),
one hour at the 60-second target, and the window walks back over blocks of the
same algorithm only, so each track sees its own history.
All three tracks share the same clamp factor of 2 and damp factor of 3.
Consensus changes by height
Two consensus changes are keyed to mainnet block heights rather than to a version fork:
| Height | Change |
|---|---|
| 501,160 | The difficulty rule changes, and the RandomX and ProgPow minimums move from 5,000 and 100,000 to 4,000 and 200,000 |
| 880,000 | The graph weight expiry factor changes, from one year to one hundred years of blocks |
An implementation that honours only declared fork versions diverges at both. The first declared hard fork height is 9,000,000, which at 60-second blocks is more than a decade away, so later header versions are reachable only on test networks.
Next
A miner connects over Stratum on port 3416, raw TCP with newline-delimited JSON,
or over the node's foreign JSON-RPC surface with get_block_template, finalize_block_template and
submit_block. EpicCash/epic-miner is the reference mining client.