Stratum
节点内置了一个供矿工使用的Stratum服务器。它监听127.0.0.1端口3416,与其他节点端口一样按网络类型偏移,且仅在enable_stratum_server = true时运行。该键在所有网络上的默认值为false,因此生成的epic-server.toml在矿工连接之前必须手动编辑,包括usernet。参见端口表和epic-server.toml。
传输方式为原始TCP,承载以换行符分隔的JSON。每条JSON-RPC消息为一行,以换行符结尾,不含HTTP头、路径或状态码。
方法与错误码
共分发五个方法。其他任何方法均返回-32601。
| 方法 | 参数 | 成功时的结果 |
|---|---|---|
login | login、pass、agent,均为必填 | "ok"。记录矿工名称和代理(servers/src/mining/stratumserver.rs:928) |
getjobtemplate | algorithm | 任务模板对象 |
submit | height, job_id, nonce, pow | "ok",若该份额为区块则返回"blockfound - <block hash>" |
keepalive | 无 | "ok" |
status | 无 | 调用矿工的id、height、difficulty、accepted、rejected、stale |
服务器发送的错误码:
| 代码 | 消息 |
|---|---|
-32000 | 节点正在同步,请等待 |
-32501 | 份额因难度过低被拒绝 |
-32502 | 解验证失败 |
-32503 | 解提交过晚 |
-32600 | 无效请求 |
-32601 | 方法未找到 |
32603 | 内部错误 |
请求工作
getjobtemplate需要一个algorithm参数,用于指定你打算挖矿的三种工作量证明算法中的哪一种。Epic同时运行全部三种算法,因此任务是针对特定算法的。
回复中的algorithm是该区块高度下策略所要求的算法,不一定是你所请求的算法,因此矿工需要为每种打算挖矿的算法分别运行一个工作进程。only_randomx可将私有链上的策略限定为单一算法。参见区块策略。
节点在达到peer_min_preferred_outbound_count个出站对等节点之前会返回-32000 Node is syncing - Please wait。对于usernet(按设计没有对等节点),请将该键设置为0。参见运行本地网络。
任务模板字段(servers/src/mining/stratumserver.rs:176):
| 字段 | 类型 |
|---|---|
height | u64 |
job_id | u64,任务所基于的区块版本索引 |
pre_pow | 序列化后的区块头(含nonce前字段)的十六进制字符串 |
algorithm | cuckoo、randomx或progpow |
difficulty | [algorithm_name, minimum_share_difficulty]对的数组 |
block_difficulty | [algorithm_name, current_block_difficulty]对的数组 |
epochs | [start_height, end_height, seed_hash]三元组的数组,用于RandomX种子选择 |
两个难度列表均按Cuckatoo、RandomX、ProgPow的顺序排列。
提交份额
submit接受height、job_id、nonce(数字形式)和pow。pow是一个外部标记枚举,因此它是一个单键对象,其值为元组(servers/src/mining/stratumserver.rs:161):
| 算法 | pow |
|---|---|
| Cuckoo与Cuckatoo | {"Cuckoo": [edge_bits, [nonces]]},先为边位数,后跟42个nonce |
| RandomX | {"RandomX": [32 bytes]} |
| ProgPow | {"ProgPow": [32 bytes]} |
难度达到block_difficulty的份额将作为区块处理,结果显示为blockfound - <block hash>。仅达到difficulty的份额被计数,结果为"ok"。
消息参考
序列化由servers/src/mining/stratumserver.rs中的结构体定义。
请求
id为整数或字符串,jsonrpc为"2.0",params为该方法的参数对象或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]]}
}}
响应
每条回复包含相同的四个键,并回显产生该回复的方法(servers/src/mining/stratumserver.rs:73)。result和error均存在,其中一个为null。
{"id": <same as the request>, "jsonrpc": "2.0", "method": <the method called>,
"result": <method result> | null, "error": {"code": <int>, "message": <string>} | null}
result在login和keepalive时为"ok",在submit时为字符串"blockfound - <hash>"或"ok",在getjobtemplate时为任务模板对象,在status时为工作进程状态对象。
查看实时会话
一个请求,一个回复,使用nc:
# 首先启用服务器: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
每种算法一个任务,回复已格式化输出:
"""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()
下一步
EpicCash/epic-miner是参考矿工。节点的Foreign JSON-RPC接口通过get_block_template、finalize_block_template和submit_block以HTTP方式提供相同的工作。