Alert Source Discuss
🚧 Stagnant Standards Track: Interface

EIP-1571: EthereumStratum/2.0.0

Authors Andrea Lanfranchi (@AndreaLanfranchi), Pawel Bylica (@chfast), Marius Van Der Wijden (@MariusVanDerWijden)
Created 2018-11-09
Discussion Link https://github.com/AndreaLanfranchi/EthereumStratum-2.0.0/issues

摘要

本草案包含定义以太坊矿工与矿池服务器通信所使用的 Stratum 协议新标准的指南。

约定

本文档中的关键词 MUSTMUST NOTREQUIREDSHALLSHALL NOTSHOULDSHOULD NOTRECOMMENDEDMAYOPTIONAL 应按照 RFC 2119 中所述的方式进行解释。 术语 mining pool server 及其复数形式,应解释为 work provider,在本文档后面可以缩写为 poolserver。 术语 miner(s) 及其复数形式,应解释为 work receiver/processor,在本文档后面可以缩写为 minerclient

理由

以太坊还没有官方的 Stratum 实现。它正式只支持 getWork,这要求矿工不断地轮询工作提供者。最近 go-ethereum 实现了推送机制来通知客户端进行挖矿工作,但由于绝大多数矿工不运行节点,它的主要目的是方便矿池而不是矿工。 另一方面,Stratum 协议依赖于标准的有状态 TCP 连接,该连接允许双向交换基于行的消息。每行包含一个 JSON 对象的字符串表示,遵循 JSON-RPC 1.0JSON-RPC 2.0 的规则。 不幸的是,在没有明确定义的标准的情况下,各种 Stratum 的变体已经在以太坊挖矿中蓬勃发展,作为不同矿池实现的衍生工作。NiceHash 曾尝试定义一个标准,他们的 EthereumStratum/1.0.0 实现是这项工作的主要灵感来源。 挖矿活动,从而也是矿池和矿工之间的交互,从根本上来说非常简单,可以用“请找到一个数字(nonce),这个数字与给定哈希算法的输入数据相结合,产生一个低于特定目标的作为输出的结果”来概括。在会话期间各方可能或必须交换的其他消息是支持这个基本概念所必需的。 由于主题的简单性,提案者打算坚持使用 JSON 格式化的对象,而不是研究更繁琐的解决方案,例如 Google 的 Protocol Buffers,后者带有严格的对象定义的负担。

Stratum 设计缺陷

Stratum 主要的设计缺陷是缺乏明确定义的标准。这意味着矿工(和挖矿软件开发人员)必须与不同的变体作斗争,这使得他们在从一个矿池切换到另一个矿池时,甚至在试图“猜测”单个矿池实现的变体时,生活变得艰难。此外,所有实现仍然因对于像以太坊这样区块时间非常短的链来说,过于冗长而受到影响。一些数字可能有助于理解。一个普通的 mining.notify 消息大约重 240 字节:假设每个区块向 5 万个连接的 TCP 套接字分发 1 个工作单元,这意味着每月传输大约 1.88TB 的数据。这对于大型矿池来说可能是一个问题。但是,如果我们从矿工的角度来看相同的数字,我们完全理解挖矿的分散性如何受到互联网连接质量的严重影响。

灵感来源

规范

Stratum 协议是 JSON-RPC-2.0 的一个实例。矿工是一个 JSON-RPC 客户端,服务器是一个 JSON-RPC 服务器。所有通信都在一个 session 的范围内进行。会话从客户端打开到服务器的 TCP 连接开始,直到一方自愿关闭同一个连接或连接中断为止。如果客户端和服务器最初在第一次会话握手时协商,服务器 MAY 支持会话恢复。在会话期间,服务器和客户端之间交换的所有消息都是基于行的,这意味着所有消息都是以 ASCII LF 字符结尾的 JSON 字符串(在本文档中也可以表示为 \n)。LF 字符 MUST NOT 出现在消息的其他位置。客户端和服务器实现 MUST 假定一旦读取到 LF 字符,当前消息已完全接收并可以处理。 行消息有三种类型:

  • Requests (请求): 发布者期望收到响应的消息。接收者 MUST 单独回复每个请求。
  • Responses (响应): 先前请求所请求的消息。响应者 MUST 使用与发起请求的 id 成员完全相同的标识符来标记响应。
  • Notifications (通知): 未经请求的消息,发布者不感兴趣也不期望得到响应。尽管如此,接收者 MAY 发送响应(例如,确认)。

session 期间,双方 CAN 交换上述三种类型的消息。

JSON-RPC-2.0 兼容性

根据 JSON-RPC-2.0 规范,请求和响应与通知的不同之处在于 JSON 对象中的标识符 (id) 成员:

  • 请求 MUST 具有 id 成员
  • 响应 MUST 具有 id 成员,其值与此响应所针对的请求的 id 成员完全相同
  • 通知 MUST NOT 具有 id 成员

JSON-RPC-2.0 差异

为了在会话/对话的参与者之间获得最简洁的消息,本实现强制执行以下差异:

  • JSON 成员 jsonrpc(始终设置为“2.0”)MUST ALWAYS BE OMITTED(必须始终省略)
  • JSON 成员 id MUST NOTnull。当成员存在时(强制存在于请求和响应中),它 MUST 被赋值为一个从 0 到 65535 的整数 Number。请注意,带有 "id": 0 的消息 MUST NOT 被解释为通知:它是一个标识符为 0 的请求。
  • JSON 成员 id MUST 只能是 Number 原始类型。删除其他标识符类型(即字符串)的原因是为了减少传输的字节数。

约定

  • JSON 对象的表示,在其基础上,是一个字符串
  • 客户端和服务器之间的对话由字符串组成。每个以 LF(ASCII 字符 10)结尾的字符串表示一个 line。每个行 MUST 仅包含一个 JSON 根对象。最终,根对象 MAY 在其成员中包含其他 JSON 对象。
  • 除了 LF 分隔符之外,每个 line MUST 由范围在 32..126 内的可打印 ASCII 字符组成
  • 隐含且强制的是,每个行消息都对应于一个格式良好的 JSON 对象:请参阅 JSON 文档
  • JSON 对象由 members(成员)组成,成员的类型可以是:字符串/数字原始类型、JSON 对象、JSON 数组
  • JSON member 的名称是字符串,MUST 仅由 ASCII 范围 48..57(数字)和 97..122(小写字母)中的可打印字符组成。
  • JSON member 的名称 MUST NOT 以数字开头。
  • JSON 值 arrays:尽管 JSON 符号允许在同一数组中插入不同的数据类型,但这种行为通常不被编码语言所接受。因此,通过 EthereumStratum/2.0.0,所有实现者 MUST 假定数组由相同数据类型的元素组成。
  • JSON 值 booleans:JSON 符号允许将布尔值表示为 truefalse。在 EthereumStratum/2.0.0 中,为了更好地与数组兼容,布尔值将以“0”(false)或“1”(true)的十六进制形式表示
  • JSON 值 strings:任何字符串值 MUST 仅由 ASCII 范围 32..126 中的可打印 ASCII 字符组成。每个字符串由开头和结尾的 " (ASCII 34) 分隔。如果字符串值包含一个 ",则必须转义为 \"
  • Hex 值:数字的十六进制表示实际上是一种字符串数据类型。作为一种约定,为了减少传输的字节数,MUST 始终省略前缀“0x”。此外,任何十六进制数 MUST 仅传输其有效部分,即 MUST 省略非有效零(例如:十进制456必须表示为 "1c8",而不是 "01c8",尽管转换产生相同的十进制值)。此指令 DOES NOT APPLY (不适用) 于哈希和 extraNonce。
  • Hex 值的大小写:十六进制值中的所有字母 MUST 为小写。(例如:十进制 456 必须表示为 "1c8",而不是 "1C8",尽管转换产生相同的十进制值)。此指令 DOES NOT APPLY (不适用) 于哈希。
  • Numbers (数字):任何非小数的数字 MUST 通过其十六进制表示形式进行传输

请求

request 对象的 JSON 表示形式由以下部分组成:

  • 强制性的 id 成员,类型为 Integer:由发布者建立的标识符
  • 强制性的 method 成员,类型为 String:要在接收方调用的方法的名称
  • 可选的 params 成员:如果接收方的方法调用需要应用其他参数才能执行。类型 CAN 为 Object(具有不同类型的命名成员)或单类型 Array。如果是数组,则参数将按其顺序位置应用。如果请求在接收器上调用的方法不需要应用其他参数,则 MUST NOT 存在此成员。不允许使用 "params" : null 符号 IS NOT PERMITTED

响应

response 对象的 JSON 表示形式由以下部分组成:

  • 强制性的 id 成员,类型为 Integer:此响应对应的请求的标识符
  • 可选的 error 成员:如果在解析方法期间或执行期间发生错误,则 MUST 存在并赋值此成员。如果未发生任何错误,则 MUST NOT 存在此成员。有关 error 成员的详细结构,请参见下文。
  • 可选的 result 成员:如果相应的请求需要用户返回结果,则必须设置此值。如果在调用相应函数时没有发生任何错误,则 MUST 存在此成员,即使一个或多个信息为 null。类型可以是 Object 或单类型 Array 或 Primitive string/number。如果没有数据要返回给发布者(该方法在接收器上为空)或发生错误,则 MUST NOT 存在此成员。

您会在此处发现与标准 JSON-RPC-2.0 的一些差异。即,并非总是需要结果成员。基本上,这样的响应:

{"id": 2}

表示“已正确接收并处理请求,无需发送回数据”。

为了更好地阐明概念并清除自由解释的领域,让我们再举一个 错误响应 的例子

{"id": 2, "result": false}

此响应语法为许多解释留有空间:这是一个错误吗?false 是对已发布请求的合法响应值吗?

因此,我们重申,响应 MUST BE 有两种类型:

  • 成功响应:处理期间未发生任何错误,该请求是合法的,语法正确,并且接收器在处理过程中没有问题。这种响应 MUST NOT 包含 error 成员,如果期望将值返回给发布者,MAY 包含 result 成员。
  • 失败响应:请求、其语法、其有效期范围或服务器处理问题出现问题。这种响应 MUST HAVE error 成员,MAY 包含 result 成员。

后者值得更好的解释:失败响应可以通过严重程度来区分。 示例 1:客户端提交了一个解决方案,服务器拒绝了它,因为它低于目标。服务器 MUST 这样回复

{
  "id": 31,
  "error": {
      "code": 406,
      "message" : "Bad nonce" // 错误的 nonce
  }
}

示例 2:客户端提交了一个解决方案,服务器 接受 了它,但是 它将该份额视为过时的。服务器 MUST 这样回复

{
  "id": 31,
  "error": {
      "code": 202,
      "message" : "Stale" // 陈旧的
  }
}

示例 3:客户端提交一个授权请求,指定一个无效的 workername。服务器授权该帐户,但拒绝 workername。服务器 MUST 这样回复

{
  "id": 1,
  "error": {
      "code": 215,
      "message" : "Invalid Worker Name" // 无效的矿工名
  }
}

示例 1 描述了严重失败的情况,而示例 2 和 3 描述了一种情况,即请求已被接受并已正确处理,但结果 MAY NOT (可能不是) 客户端所期望的。 客户端可以评估错误的严重程度,并决定是否继续。

使用正确的错误代码,矿池可以正确地告知矿工其请求的状态。错误代码 MUST 遵循以下方案:

  • 错误代码 2xx:请求已接受和处理,但 error 成员中的一些附加信息可能会给出提示
  • 错误代码 3xx:服务器由于客户端的授权不足而无法处理请求
  • 错误代码 4xx:服务器由于语法问题(找不到方法、缺少参数、错误的数据类型等)或传递的 param 值无效而无法处理请求。
  • 错误代码 5xx:服务器由于内部错误而无法处理请求

通知

通知消息具有与 request 非常相似的表示形式,唯一的区别是 MUST NOT 存在 id 成员。这意味着发布者对该消息不感兴趣,也不期望任何响应。接收者可以采取相应的措施。例如,接收者 MAY 决定执行该方法,或者,如果出现错误或不允许使用该方法,则断开连接,从而结束会话。

错误成员

如上所述,response MAY 包含一个 error 成员。如果存在此成员,则 MUST 是一个 Object,其中包含:

  • 强制成员 code:一个 Number,用于标识发生的错误
  • 强制成员 message:一个简短的、人类可读的句子,描述发生的错误
  • 可选成员 data:一个 Structured 或 Primitive 值,包含有关该错误的其他信息。此成员的值由服务器定义(例如,详细的错误信息、嵌套错误等)。

协议流程

  • 客户端通过打开到服务器的 TCP 套接字来启动会话
  • 客户端公告并请求协议兼容性
  • 服务器确认兼容性并声明准备就绪
  • 客户端启动/恢复会话
  • 客户端为每个工作线程发送授权请求
  • 服务器回复每个授权的响应报文
  • 服务器发送 mining.set,用于设置后续挖矿作业要采用的常量值
  • 服务器发送 mining.notify,包含最少的工作信息
  • 客户端在作业上进行挖矿
  • 如果找到作业的任何解决方案,客户端将发送 mining.submit
  • 服务器回复是否接受解决方案
  • 服务器可以有选择地发送 mining.set,用于设置后续挖矿作业要采用的常量值(如果某些内容已更改)
  • 服务器发送 mining.notify,包含最少的工作信息
  • …(继续)
  • 最终,任何一方都将关闭会话和 TCP 连接

会话处理 - Hello

到目前为止,最令人烦恼的事情之一是,服务器在套接字连接的时刻,没有提供任何关于已实现的 stratum 风格的有用信息。这意味着客户端必须通过迭代尝试通过不同的协议风格连接来开始对话。该提案修正了这种情况,强制服务器向客户端公告自身。 当一个新客户端连接到服务器时,服务器 MUST 发送一个 mining.hello 通知:

已经注意到,在会话的第一个消息中由服务器承担公告自身的责任,对于使用欺骗 IP 地址的流量放大攻击或传统的 DDos 攻击,可能会造成危害,在传统 DDos 攻击中,攻击者只需花费很少的资源就可以强制服务器发回一个大的数据包。 因此,第一次公告的职责仍然由客户端承担,客户端将发出一个如下所示的 mining.hello 请求:

{
  "id" : 0,
  "method": "mining.hello", 
  "params": 
  { 
    "agent": "ethminer-0.17",
    "host" : "somemininigpool.com",
    "port" : "4d2",
    "proto": "EthereumStratum/2.0.0"
  }
}

params 成员对象必须具有以下强制成员:

  • agent (string) 挖矿软件版本
  • host (string) 客户端希望连接到的服务器的主机名
  • port (hex) 客户端希望连接到的服务器的端口号
  • proto (string) 报告请求的 stratum 风格以及服务器期望实现的 stratum 风格;

发送主机和端口的原因是它启用了虚拟主机,其中虚拟矿池或私有 URL 可能用于 DDoS 保护,但这些 URL 会在 Stratum 服务器后端进行聚合。与 HTTP 类似,服务器 CANNOT 信任主机字符串。单独包含端口是为了与 client.reconnect 方法(见下文)并行。

如果服务器准备好按此类要求启动/恢复会话,则 MUST 使用如下所示的响应回复:

{
  "id" : 0,
  "result": 
  { 
    "proto" : "EthereumStratum/2.0.0",
    "encoding" : "gzip",
    "resume" : "1",
    "timeout" : "b4",
    "maxerrors" : "5",
    "node" : "Geth/v1.8.18-unstable-f08f596a/linux-amd64/go1.10.4"
  } 
}

其中 result 是一个由 5 个强制成员组成的对象

  • proto (string) MUST 与客户端请求的精确版本匹配
  • encoding (string) 其值说明所有 后续消息 是否应进行 gzip 压缩。可能的值为“gzip”或“plain”
  • resume (hex) 其值说明主机是否可以恢复先前创建的会话;
  • timeout (hex) 报告服务器在没有收到客户端消息后允许断开连接的秒数
  • maxerrors (hex) 服务器在突然关闭连接之前将承受的最大错误数
  • node (string) 矿池的基础节点软件版本

当服务器使用 encoding":"gzip" 回复客户端时,双方 MUST 使用 gzip 压缩所有后续消息。如果客户端无法进行压缩,则 MUST 立即关闭连接。 如果服务器在此回复后收到其他作为纯文本的消息,则 MUST 关闭连接。

最终客户端将继续执行 mining.subscribe(在下面进一步描述)

否则,如果出现错误或拒绝开始对话,服务器 MAY 使用错误进行回复,向另一方提供有用的信息,或者,由服务器维护者自行决定,突然关闭连接。

{
  "id" : 0,
  "error": 
  { 
      "code": 400,
      "message" : "Bad protocol request" // 错误的协议请求
  } 
}

{
  "id" : 0,
  "error": 
  { 
      "code": 403,
      "message" : "Forbidden - Banned IP address" // 禁止 - 禁止的 IP 地址
  } 
}

以上两个 JSON 错误值仅是示例 最终服务器将关闭连接。

为什么矿池应该公告节点的版本?这是一个透明度问题:矿工应该知道矿池是否已升级到节点软件的最新补丁/版本。

会话处理 - Bye

断开连接在 Stratum 中未得到妥善处理。客户端与矿池断开连接可能是由于多个错误造成的,这导致服务器端浪费 TCP 套接字,这些套接字等待 keepalive 超时触发。有用的通知是 mining.bye,一旦经过处理,允许会话双方停止接收并妥善关闭 TCP 连接

{
  "method": "mining.bye"
}

接收到此消息的一方确认另一方想要停止对话并关闭套接字。发布者也会关闭。此通知的显式发布意味着会话将被放弃,因此即使在支持会话恢复的服务器上,也无法进行会话恢复。重新连接到实现会话恢复的同一服务器的客户端 SHOULD 期望一个新会话 ID,并且 MUST 重新授权其所有矿工。

会话处理 - 会话订阅

从服务器收到对 mining.hello 的响应后,如果服务器支持会话恢复,则客户端 MAY 请求使用 mining.subscribe 请求恢复先前中断的会话:

{
  "id": 1,
  "method": "mining.subscribe", 
  "params": "s-12345"
}

其中 params 是客户端想要恢复的会话的 ID。

否则,如果客户端想要启动一个新会话 OR 服务器不支持会话恢复,则订阅请求 MUST 省略 params 成员:

{
  "id": 1,
  "method": "mining.subscribe"
}

会话处理 - 订阅响应

接收到客户端会话订阅的服务器 MUST 回复

{
  "id": 1,
  "result": "s-12345"
}

接收到订阅请求的服务器,其中 params 是一个字符串,保存会话 ID。这些情况可能适用

  • 如果不支持会话恢复,则 result 将保存一个新的会话 ID,它 MUST 是与客户端在先前的 mining.subscribe 方法中发布的 session 成员不同的值
  • 如果支持会话恢复,它将从缓存中检索工作值,并且 result 将具有客户端请求的相同 ID。这意味着一个会话已“恢复”:因此,服务器 CAN 开始推送作业,而无需在它们之前加上 mining.set(请参见下文),并且客户端 MUST 继续使用在同一会话范围内最后接收到的值。此外,客户端 CAN 省略重新授权其所有工作线程。
  • 如果支持会话恢复,但请求的会话已过期或其缓存值已被清除,则result 将保存一个新的会话 ID,该 ID MUST 与客户端在先前的mining.subscribe 方法中发布的 session 成员不同。因此,服务器 MUST 等待客户端请求对其工作线程的授权,并且 MUST 在推送作业之前发送 mining.set 值。客户端 MUST 准备好进行新的会话,放弃所有先前缓存的值(如果有)。

实现会话恢复的服务器 MUST 缓存:

  • 会话 ID
  • 每个会话的任何活动作业
  • extraNonce
  • 任何授权的工作线程

服务器 MAY 按照它们自己的时间表从缓存中删除条目。由服务器来强制执行对同一代理和/或 IP 的会话验证。

成功订阅并恢复会话的客户端(服务器响应中的 session 值与客户端在 mining.subscribe 上请求的 session 值相同)CAN 省略为其工作线程发布授权请求。

会话处理 - Noop

在某些情况下,矿工很难在合理的时间内找到解决方案,因此,如果由于没有通信而触发服务器施加的超时(实际上,服务器可能认为客户端已断开连接),则可能会触发此问题。为了缓解此问题,可以由客户端请求一个新的方法 mining.noop(没有其他参数)。

{
  "id": 50,
  "method": "mining.noop"
}

会话处理 - 重新连接

在某些情况下,服务器可能需要释放一些资源和/或将矿工重新定位到另一台机器。到目前为止,服务器的唯一选择是突然关闭连接。在矿工端,此操作被解释为服务器故障,并且他们通常会切换到故障转移矿池。 mining.reconnect 通知的实现有助于客户端更好地与大型矿池的处理逻辑融合。

{
  "method": "mining.reconnect",
  "params": {
      "host": "someotherhost.com",
      "port": "d80",
      "resume": "1"
  }
}

此通知仅适用于从服务器到客户端。如果服务器收到这样的通知,它将简单地忽略它。在正确发送通知后,服务器可以关闭连接,而客户端将采取适当的措施来重新连接到建议的端点。 params 对象中的 host 成员 SHOULD 报告主机 DNS 名称,而不是 IP 地址:TLS 加密连接需要验证证书中的 CN 名称,在 99% 的情况下,该名称是一个主机名。 params 对象的第三个成员 resume 设置接收服务器是否准备好进行会话恢复。 服务器发出此通知后,客户端不应期望收到任何其他消息,并且 MUST 断开连接。

工作线程授权

矿工 MUST 授权至少一个 worker 才能开始接收任务和提交解决方案或算力。矿工 MAY 在同一会话中授权多个 worker。服务器 MUST 允许在会话中授权多个 worker,并且 MUST 在开始发送任务之前,验证来自客户端的至少一个授权。worker 是一个元组,其中包含必须记入奖励的地址以及实际执行工作的机器的标识符。对于以太坊,最常见的形式是 <account>.<MachineName>。同一帐户可以绑定到多台机器。对于允许匿名挖矿的矿池,帐户是必须记入奖励的地址,而对于需要注册的矿池,该帐户是登录名。每次客户端提交解决方案时,都必须使用 Worker 标识符对其进行标记。由服务器负责维护不同地址的正确记帐。

授权请求的语法如下:

{
  "id": 2,
  "method": "mining.authorize", 
  "params": ["<account>[.<MachineName>]", "password"]
}

params 成员必须是包含 2 个字符串元素的 Array。对于匿名挖矿,”password” 可以是任何字符串值或空,但不能为 null。允许匿名挖矿的矿池将简单地忽略该值。 服务器 MUST 使用错误回复,或者在成功的情况下使用

{
  "id": 2,
  "result": "w-123"
}

其中 result 成员是一个字符串,它保存一个唯一的 token,该 token 在 session 的范围内标识已授权的 worker。对于客户端发出的每一个进一步的请求,并且与 Worker 操作有关,客户端 MUST 使用由服务器响应 mining.authorize 请求给出的 token。这减少了解决方案和/或算力提交的传输字节数。

如果客户端正在恢复以前的会话,则 CAN 省略其工作线程的授权请求,并且在这种情况下,MUST 使用原始会话中分配的 token。由服务器负责维护 token 和 worker 之间的正确映射。 服务器接收到一个授权请求,其中凭据与同一会话中先前授权的凭据匹配,MUST 使用先前生成的唯一 token 回复。

准备挖矿

大量数据通过有线多次发送,而没有用冗余。例如,seed hash 仅在每 30000 个区块(大约 5 天)更改一次,而固定 diff 矿池很少更改工作目标。此外,矿池必须在尝试为每个会话分配不同的 “startNonce”(AKA extraNonce)的矿工之间优化搜索段。 为此,notification 方法 mining.set 允许仅设置(在矿工端)那些更改频率较低的参数。服务器将跟踪会话级别的 seed、target 和 extraNonce,并且每当这些值中的任何一个(或全部)更改为连接的矿工时,都将推送 mining.set 通知。

{
  "method": "mining.set", 
  "params": {
      "epoch" : "dc",
      "target" : "0112e0be826d694b2e62d01511f12a6061fbaec8bc02357593e70e52ba",
      "algo" : "ethash",
      "extranonce" : "af4c"
  }
}

在每个 session 的开始,服务器 MUST 在任何 mining.notify 之前发送此通知。此通知传递的所有值对于所有 NEXT 作业都有效,直到新的 mining.set 通知覆盖它们为止。成员的描述如下:

  • 可选的 epoch(hex):与所有实际的 Stratum 实现不同,服务器应将 epoch 号通知客户端,而不是传递 seed hash。这是由两个原因强制执行的:主要原因是客户端只有一种方法来计算 epoch 号,这就是通过从 epoch 0 开始的线性搜索,迭代地尝试增加 epoch,直到哈希与 seed hash 匹配为止。第二个原因是 epoch 号比 seed hash 更简洁。最后,seed hash 仅用于通知客户端有关 epoch 的信息,并且不涉及挖矿算法。
  • 可选的 target(hex):这是已经针对矿池难度进行了调整的边界哈希。与 EthereumStratum/1.0.0 不同,后者提供了 mining.set_difficulty 通知,通知了_难度指数_,提案人选择直接传递边界哈希。如果省略,客户端 MUST 假设边界为 "0x00000000ffff0000000000000000000000000000000000000000000000000000"
  • 可选的 algo(string):矿工期望挖掘的算法。如果省略,则客户端 MUST 假定为 "algo": "ethash"
  • 可选的 extranonce(hex):服务器分配给客户端的起始搜索段 nonce,以便他们可能不重叠其搜索段。如果服务器想要“取消”先前设置的 extranonce,则必须传递此成员,并将其值设置为空字符串。

每当服务器检测到会话中的一个,或两个,或三个或四个值发生变化时,服务器将发出一个通知,其中包含 param 对象中的一个/两个/三个/四个成员。因此,在每个 new 会话中,服务器 MUST 传递所有四个成员。因此,指示矿工在 next 作业中调整这些值,通知该作业。 定义新的 algo 成员是为了准备可能存在的 ethash 算法变体,即 ethash1a 或 ProgPow。 提供多币种切换的矿池将注意在切换后推送任何任务之前,向矿工发送新的 mining.set。 无法支持在 mining.set 通知中提供的数据的客户端 MAY 关闭连接,或保持空闲状态,直到新值满足其配置(请参见 mining.noop)。 所有客户端的实现 MUST 准备好在会话期间接受新的 extranonce:与 EthereumStratum/1.0.0 不同,可选的客户端公告 mining.extranonce.subscribe 现在是隐式且强制性的。

接收到 extranonce 的矿工 MUST 初始化下一个作业的搜索段,将 extranonce 调整为 16 字节的十六进制,从而根据需要附加尽可能多的零。 Extranonce “af4c” 表示 “下一个作业的搜索段从 0xaf4c000000000000 开始” 如果 extranonce 的值为空字符串,或者从未在会话范围内设置过,则客户端可以随意在后续 mining.notify 作业中选择其自己的搜索段的任何起点。

“extranonce” 的详细信息

连接到矿池的矿工可能会处理完全相同的 nonce,从而浪费了大量重复的作业。nonce 是任何有效数字,将其应用于算法和作业规范,会产生低于特定目标的结果。对于服务器推送给客户端的每个作业,都有 2^64 个可能的 nonce 要测试。

需要注意的是:

  • 2^64 中的任何 nonce 都具有成为正确的 nonce 的可能性。
  • 某个哈希作业可以通过多个 nonce 解决

在数字上进行每次“测试”都称为哈希。假设矿工应该为每个区块收到一个作业,并考虑当前平均 15 秒的区块时间,这意味着矿工应该尝试

  ( 2^64 / 15 ) / 1T ~ 1,229,782.94 TeraHashes per second

此计算能力远远超出了市场上任何矿工(包括 ASIC)的能力。因此,单个矿工只能处理这个庞大范围的小块(段)。矿工选择要搜索的段的方式不在本文的范围之内。事实是,由于矿工没有协调,因此 - 对于单个矿工而言 - 没有关于其他矿工选择的段的知识。 Extranonce 概念旨在减轻这种重复作业的可能性,它要求服务器(工作提供者)在最大可能的范围内为矿工提供不同的搜索段。

考虑到以上假设,我们可以将 nonce 描绘成十六进制范围内的任何数字:

  Min 0x0000000000000000
  Max 0xffffffffffffffff

此处有意插入前缀 0x,仅为了提供更好的视觉表示效果

从根本上讲,extranonce 是服务器的消息,说客户端:“我给你第一个要从那里开始搜索的数字”。更详细地说,extranoce 是该数字最左侧的部分。 假设一个矿池通知客户端使用 extranonce ab5d,这意味着客户端会看到它的搜索段缩小为

  Min 0xab5d000000000000
  Max 0xab5dffffffffffff

推送 4 字节的 extranonce(如示例中所示)将使矿池有可能分隔 65535 个不同的矿工(或者,如果您更喜欢 0xffff 个矿工)的段,同时仍为矿工留下 2^48 个可能的 nonce 的段。 重做以上计算,搜索此段所需的计算能力是多少

  ( 2^48 / 15 ) / 1T ~ 18.76 TeraHashes per second

这仍然是一个很宽的段,矿工可以在该段中Extranonce 必须 传递所有相关的字节(不能省略左边的零),原因如下。假设 extranonce 为 “01ac”:它与 “1ac” 具有相同的十进制值,但是字节数发生了变化,从而改变了可用的搜索段。

  当 "01ac" 时              当 "1ac" 时
  段是                    段是
  最小 0x01ac000000000000   最小 0x1ac0000000000000
  最大 0x01acffffffffffff   最大 0x1acfffffffffffff

正如你所看到的, resulting 的段是完全不同的。

总而言之,pools (server) 在使用 extranonce 时,必须 遵守最大长度为 6 字节(十六进制)。

Jobs 通知

当可用时,服务器将分发 jobs 给连接的矿工,发出一个 mining.notify 通知。

{
  "method": "mining.notify", 
  "params": [
      "bf0488aa",
      "6526d5"
      "645cf20198c2f3861e947d4f67e3ab63b7b2e24dcc9095bd9123e7b33371f6cc",
      "0"
  ]
}

params 成员由 4 个强制元素组成:

  • 第 1 个元素是由 pool 指定的 jobId。 为了减少通过网络发送的数据量,pools 应该 保持他们的 job IDs 尽可能简洁。 推送一个与 headerhash 相同的 Job id 是一个不好的做法,并且非常不鼓励这样做。
  • 第 2 个元素是 block id 的十六进制数
  • 第 3 个元素是 headerhash。
  • 第 4 个元素是一个十六进制布尔值,指示之前 jobs 中最终找到的 shares 是否肯定会被认为是过时的。

方案提交

当一个矿工为一个 job 找到一个解决方案时,他会向服务器发送一个 mining.submit 请求。

{
  "id": 31,
  "method": "mining.submit", 
  "params": [
      "bf0488aa",
      "68765fccd712",
      "w-123"
  ]
}

params 数组的第一个元素是此解决方案引用的 jobId(如服务器在 mining.notify 消息中发送的那样)。 第二个元素是 miner nonce,以十六进制表示。 第三个元素是在之前的 mining.authorize 请求中给 worker 的 token。 任何绑定到 worker 的 mining.submit 请求,如果该 worker 未经成功授权,即 token 在会话中不存在,必须 被拒绝。

您会注意到在上面的示例中,miner nonce 只有 12 个字节宽(应该是 16 个字节)。 为什么? 这是因为在前一个 mining.set 中,服务器设置了 extranonceaf4c。 这意味着完整的 nonce 是 af4c68765fccd712 在存在 extranonce 的情况下,矿工 必须 只提交要附加到 extranonce 的字符以构建最终的十六进制值。 如果没有为会话或工作设置 extranonce,则矿工 必须 发送所有 16 个字节。

服务器有责任跟踪每个会话的元组 job ids <-> extranonces

当服务器收到此请求时,它可以使用简短形式响应成功

{"id": 31}

或者,在出现任何错误或者带有详细错误对象的条件时

{
  "id": 31,
  "error": {
      "code": 404,
      "message" : "Job not found"
  }
}

客户端 应该 将错误视为“软”错误(过时)或“硬”错误(错误的 nonce 计算,找不到 job 等)。 5xx 范围内的错误是服务器错误,并建议矿工放弃连接并切换到故障转移。

Hashrate

大多数 pools 提供统计信息,以图表或通过 API 调用forms,关于矿工表示的计算 hashrate,而矿工喜欢将此数据与他们在设备上读取的 hashrate 进行比较。 关于这些信息方的通信从未在 Stratum 中编码,并且大多数 pools 采用来自 getWork 的名为 eth_submitHashrate 的方法。 在本文档中,我们提出了 mining.hashrate 请求的官方实现。 当从客户端或服务器发出此方法时,此方法的行为有所不同。

客户端将其 hashrate 传达给服务器。

{
  "id" : 16,
  "method": "mining.hashrate",
  "params": [
      "500000",
      "w-123"
      ]
}

其中 params 是一个由两个元素组成的数组:第一个是矿工在其设备上读取的 hashrate 的十六进制字符串表示形式(32 字节),后者是颁发给 worker 此 hashrate 引用的授权 token(请参见上面的 mining.authorization)。 服务器 必须 使用确认消息回复

{"id": 16 }

(可选)服务器可以回复,报告其关于 同一 worker 的计算 hashrate 的发现。

{
  "id": 16,
  "result" : [
      "4f0000",
      "w-123"
      ]
}

如果出现错误,例如客户端提交太频繁,则会出现以下错误

{
  "id": 16,
  "error" : {
    "code": 220,
    "message": "Enhance your calm. Too many requests"
  }
}

服务器将 hashrate 传达给客户端

(可选)服务器可以 通知 客户端其总体性能(根据服务器上设置的计划),其中 mining.hashrate 通知的组成如下

{
  "method": "mining.hashrate",
  "params": {
      "interval": 60,
      "hr": "500000",
      "accepted": [3692,20],
      "rejected": 0,
  }
}

其中 params 是一个对象,该对象为 整个会话 保存这些成员的值:

  • interval(数字)观察窗口的宽度(以分钟为单位)。 “在过去的 x 分钟内,我们计算了…
  • hr(十六进制)pool 为矿工计算的 hashrate 的表示形式
  • accepted 是一个由两个数字元素组成的数组:第一个是接受的 shares 的总数,第二个是过时 shares 的数量。 该数组必须解释为“总接受的 shares,其中 x 个是过时的”
  • rejected(数字)拒绝的 shares 的总数

客户端最终将采取内部操作来重置/重新启动其 workers。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Andrea Lanfranchi (@AndreaLanfranchi), Pawel Bylica (@chfast), Marius Van Der Wijden (@MariusVanDerWijden), "EIP-1571: EthereumStratum/2.0.0 [DRAFT]," Ethereum Improvement Proposals, no. 1571, November 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1571.