跳到主要内容

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

方法参数成功时的结果
loginloginpassagent,均为必填"ok"。记录矿工名称和代理(servers/src/mining/stratumserver.rs:928
getjobtemplatealgorithm任务模板对象
submitheight, job_id, nonce, pow"ok",若该份额为区块则返回"blockfound - <block hash>"
keepalive"ok"
status调用矿工的idheightdifficultyacceptedrejectedstale

服务器发送的错误码:

代码消息
-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):

字段类型
heightu64
job_idu64,任务所基于的区块版本索引
pre_pow序列化后的区块头(含nonce前字段)的十六进制字符串
algorithmcuckoorandomxprogpow
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接受heightjob_idnonce(数字形式)和powpow是一个外部标记枚举,因此它是一个单键对象,其值为元组(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)。

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]]}
}}

响应

每条回复包含相同的四个键,并回显产生该回复的方法(servers/src/mining/stratumserver.rs:73)。resulterror均存在,其中一个为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}

resultloginkeepalive时为"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

每种算法一个任务,回复已格式化输出:

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()

下一步

EpicCash/epic-miner是参考矿工。节点的Foreign JSON-RPC接口通过get_block_templatefinalize_block_templatesubmit_block以HTTP方式提供相同的工作。