Stratum
The node includes a Stratum server for miners. It listens on 127.0.0.1 port
3416, shifted per network like every other node port, and it runs only when
enable_stratum_server = true. That key defaults to false on every network, so a generated
epic-server.toml always has to be edited before a miner can connect, usernet included. See
the port table and
epic-server.toml.
The transport is raw TCP carrying newline-delimited JSON. Each JSON-RPC message is one line terminated by a newline, with no HTTP headers, paths or status codes.
Methods and error codes
Five methods are dispatched. Anything else returns -32601.
| Method | Params | Result on success |
|---|---|---|
login | login, pass, agent, all required | "ok". Records the worker name and agent (servers/src/mining/stratumserver.rs:928) |
getjobtemplate | algorithm | A job template object |
submit | height, job_id, nonce, pow | "ok", or "blockfound - <block hash>" when the share is a block |
keepalive | none | "ok" |
status | none | id, height, difficulty, accepted, rejected, stale for the calling worker |
Error codes, as the server sends them:
| Code | Message |
|---|---|
-32000 | Node is syncing - Please wait |
-32501 | Share rejected due to low difficulty |
-32502 | Failed to validate solution |
-32503 | Solution submitted too late |
-32600 | Invalid Request |
-32601 | Method not found |
32603 | Internal error |
Requesting work
getjobtemplate requires an algorithm parameter naming which of the three proof-of-work algorithms
you intend to mine. Epic runs all three concurrently, so a job is algorithm-specific.
The algorithm in the reply is the one the policy requires at that height, not necessarily the one
you asked for, so a miner needs one worker per algorithm it intends to mine. only_randomx narrows
the policy to a single algorithm on a private chain. See
the block policy.
A node returns -32000 Node is syncing - Please wait until it has reached
peer_min_preferred_outbound_count outbound peers. On usernet, which has no peers by design, set that
key to 0. See run a local network.
Job template fields (servers/src/mining/stratumserver.rs:176):
| Field | Type |
|---|---|
height | u64 |
job_id | u64, the index of the block version the job was built from |
pre_pow | hex string of the serialised header up to and including the pre-nonce fields |
algorithm | cuckoo, randomx or progpow |
difficulty | array of [algorithm_name, minimum_share_difficulty] pairs |
block_difficulty | array of [algorithm_name, current_block_difficulty] pairs |
epochs | array of [start_height, end_height, seed_hash] triples, for RandomX seed selection |
Both difficulty lists cover Cuckatoo, RandomX and ProgPow, in that order.
Submitting shares
submit takes height, job_id, nonce as a number, and pow. pow is an externally tagged enum,
so it is a single-key object whose value is a tuple
(servers/src/mining/stratumserver.rs:161):
| Algorithm | pow |
|---|---|
| Cuckoo and Cuckatoo | {"Cuckoo": [edge_bits, [nonces]]}, edge bits first, then 42 nonces |
| RandomX | {"RandomX": [32 bytes]} |
| ProgPow | {"ProgPow": [32 bytes]} |
A share whose difficulty clears block_difficulty is processed as a block and the result reads
blockfound - <block hash>. A share that clears only difficulty is counted and the result is "ok".
Message reference
Serialisation is defined by the structs in
servers/src/mining/stratumserver.rs.
Requests
id is an integer or a string, jsonrpc is "2.0", and params is the method's parameter object or
null (servers/src/mining/stratumserver.rs:64).
{"id":"1","jsonrpc":"2.0","method":"getjobtemplate","params":{"algorithm":"randomx"}}
{"id":"2","jsonrpc":"2.0","method":"keepalive","params":null}
{"id":"3","jsonrpc":"2.0","method":"status","params":null}
{"id":"4","jsonrpc":"2.0","method":"submit","params":{
"height":1000001,
"job_id":7,
"nonce":13476209874311234567,
"pow":{"RandomX":[10,27,44,3,255,18,9,72,64,31,88,120,4,17,203,55,
91,6,142,77,38,199,15,60,231,2,113,49,86,170,25,8]}
}}
{"id":"5","jsonrpc":"2.0","method":"submit","params":{
"height":1000001,"job_id":7,"nonce":42,
"pow":{"Cuckoo":[31,[1042,20984,31006]]}
}}
Replies
Every reply carries the same four keys, and echoes the method that produced it
(servers/src/mining/stratumserver.rs:73). result and error are
both present, one of them null.
{"id": <same as the request>, "jsonrpc": "2.0", "method": <the method called>,
"result": <method result> | null, "error": {"code": <int>, "message": <string>} | null}
result is "ok" for login and keepalive, the string "blockfound - <hash>" or "ok" for
submit, the job template object for getjobtemplate, and the worker status object for status.
Watch a live session
One request, one reply, with nc:
# Enable the server first: stratum_mining_config.enable_stratum_server = true
printf '{"id":"1","jsonrpc":"2.0","method":"getjobtemplate","params":{"algorithm":"randomx"}}\n' \
| nc 127.0.0.1 3416
One job per algorithm, with the replies pretty-printed:
"""Request one Stratum job per algorithm and print the node's replies.
Standard library only. Run an `epic` node with `enable_stratum_server = true` first.
See https://devdocs.epiccash.com/mining/stratum
Override the defaults for another network:
STRATUM_HOST=127.0.0.1 STRATUM_PORT=23416 python stratum_probe.py
"""
from __future__ import annotations
import json
import os
import socket
from typing import Any
HOST = os.environ.get("STRATUM_HOST", "127.0.0.1")
PORT = int(os.environ.get("STRATUM_PORT", "3416"))
ALGORITHMS = ("randomx", "progpow", "cuckatoo")
def request(stream: Any, request_id: int, method: str, params: Any) -> Any:
"""Send one newline-delimited JSON-RPC request and return the decoded reply.
The transport is raw TCP: one JSON object per line, no HTTP framing.
"""
payload = {"id": str(request_id), "jsonrpc": "2.0", "method": method, "params": params}
stream.write(json.dumps(payload) + "\n")
stream.flush()
line = stream.readline()
if not line:
raise ConnectionError("stratum connection closed before a reply arrived")
return json.loads(line)
def probe(host: str = HOST, port: int = PORT) -> None:
"""Ask for a job template for each algorithm and print what comes back."""
with socket.create_connection((host, port), timeout=15) as sock:
stream = sock.makefile("rw", encoding="utf-8", newline="\n")
for request_id, algorithm in enumerate(ALGORITHMS, start=1):
reply = request(stream, request_id, "getjobtemplate", {"algorithm": algorithm})
print(f"{algorithm}: {json.dumps(reply, indent=2)}")
if __name__ == "__main__":
probe()
Next
EpicCash/epic-miner is the reference miner. The node's foreign JSON-RPC surface offers the same work
over HTTP through get_block_template, finalize_block_template and submit_block.