Skip to main content

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.

MethodParamsResult on success
loginlogin, pass, agent, all required"ok". Records the worker name and agent (servers/src/mining/stratumserver.rs:928)
getjobtemplatealgorithmA job template object
submitheight, job_id, nonce, pow"ok", or "blockfound - <block hash>" when the share is a block
keepalivenone"ok"
statusnoneid, height, difficulty, accepted, rejected, stale for the calling worker

Error codes, as the server sends them:

CodeMessage
-32000Node is syncing - Please wait
-32501Share rejected due to low difficulty
-32502Failed to validate solution
-32503Solution submitted too late
-32600Invalid Request
-32601Method not found
32603Internal 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):

FieldType
heightu64
job_idu64, the index of the block version the job was built from
pre_powhex string of the serialised header up to and including the pre-nonce fields
algorithmcuckoo, randomx or progpow
difficultyarray of [algorithm_name, minimum_share_difficulty] pairs
block_difficultyarray of [algorithm_name, current_block_difficulty] pairs
epochsarray 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):

Algorithmpow
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).

client -> server, one line each
{"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}
client -> server, a RandomX share, one line
{"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]}
}}
client -> server, a Cuckatoo share, nonce list abbreviated to 3 of 42, one line
{"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.

server -> client, envelope
{"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:

examples/python/stratum_probe.py
"""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.