Skip to content

Latest commit

 

History

History
207 lines (181 loc) · 18.6 KB

main.md

File metadata and controls

207 lines (181 loc) · 18.6 KB

Stratum挖矿协议官方文档中文版

由 SlushPool.com 提供 本文档英文原文
由 庞四焘 翻译 pangsitao@hotmail.com
bitcoin address:16vyL77ucDkU26P1XJk488cM5ffvadz2EV
doge address:DGEnmucfwvBWNTUEmXczrn4XAS49Q9uQSR


这是关于轻量级比特币挖矿协议的官方文档。

内容

  1. 为什么要更改之前能用的协议?
  2. 如何修正这些问题?
  3. 挖矿软件开发者相关
  4. 下载
  5. 可兼容矿机

本页是一个技术文档,也是新的比特币挖矿协议的倡导书。如果你是一个散户矿工,或者是一个普通比特币使用者,你没必要看懂本文档的所有内容。

设计这个协议并且实现开源矿池服务的原因是,现在的getwork&LP挖矿协议已经有很多缺陷了,并且它没办法在大规模算力上应用。ASIC专业矿机在2012年底可能就会出现,所以比特币社区明显需要一些解决方案--当矿池的每个用户都拥有每秒T-hash级别规模的算力时,也能轻易测量这些算力的方案。


为什么要更改之前能用的协议?

HTTP:会话是被矿机主导控制的

因为当客户端矿机需要新的挖矿任务的时候,矿池服务器知道得更多。HTTP协议是为了WEB网页浏览设计的,当人们访问网页的时候,客户端向服务器请求一些指定的内容。而挖矿并不是这样:服务器非常了解客户端需要什么,并且能够用更有效率的方式控制这次会话。换个规则吧,离开这样的业务流程。

Ntime滚动:高性能矿机得不到足够多的任务

目前,每个从服务器收到的任务,矿机只可以修改 ntime 和 nonce随机数两个字段。Nonce随机数是一个32位整形数(42亿次迭代空间)。Ntime是一个存储了应该反映当前时间的UNIX时间戳的32位整形数,尽管优化过的矿机会用反映稍稍靠后时间的 Ntime来滚动迭代,这样可以给矿机更多的组合空间(nonce范围*ntime范围)。然而,一个过度被修改的ntime产生的区块可能会被比特币网络拒绝。
从getwork的严格规范来说,一个getwork任务足够4.2Gh/s的矿机使用。利用上ntime滚动,这样的任务可以使用1分钟,或者在新区块到达之前都可以使用(这取决于两者谁先发生)。所以这样计算,4的2Gh/s的矿机得需要10个getwork请求,但是通常来说会需要更多,因为为了避免网络延迟而导致的矿机空转,矿机都会有一些预先缓存的策略。那么马上到来的1Th/s算力的ASIC专业矿机怎么办?我们需要一个让网络加载不再限制矿机性能的解决方案。

长轮询:一个反面模板

getwork是一个构建独立矿机的简单解决方案(记得当年比特币官方客户端是唯一的挖矿软件吗?),在我搭建我第一个比特币矿池之前,频繁的轮询本地比特币守护进程并不是一个问题。当矿池加入游戏之后,人们发现他们必须在短轮询间隔和长轮询间隔之间做决定,前者有较低的失效率但是有更高的网络负载,后者网络和服务不会过载负荷但是会引起相当高的拒绝率。所以长轮询模板胜出。长轮询使用标准的web技术实现实时更新。但是像我之前已经提到的那样,web技术不是比特币挖矿的理想技术。
长轮询情况下,客户端分离的连接服务器,这给服务器一侧带来大量的问题,像是多后台的连接负载均衡。根据源IP地址哈希或者HTTP Seeion粘滞会话保持来让负载达到均衡,只是为了保持整个事情能够工作的权宜之计。
客户端们会在长轮询广播之后尝试重新连接服务器,数据包风暴会带来另一个问题。有时很难去分辨一个长轮询重新连接是有效的还是DDoS拒绝服务攻击。这些使得矿池架构变得很复杂也很难维护,会降低矿池服务的可信赖性,也会给矿工带来实实在在的影响。
联系前一点来解决这个问题,通过服务器来驱动负载而不是几千个被稀奇古怪实现的、一直在攻击性尝试连接服务器的矿工们来驱动。


如何修正这些问题?

现在我们知道了在当前情况下什么事情是错的,那么让我们设计出一个新的、不再重复坏决策的协议:

Stratum协议

我原本为轻量比特币客户端设计的Stratum协议叫做Electrum,稍后我发现那个协议对于挖矿来说要求也非常简单,看起来应该没问题,所以我决定重用它。不要被拗口的协议名字困扰,我已经尽可能的努力坚持标准了。
用简化的形式说,Stratum是一个基于行的使用普通TCP套接字的协议,其负载以JSON-RPC消息编码。这就是所有的含义了。客户端打开TCP套接字,然后用JSON消息格式写给服务器请求,然后通过换行符“\n”完成。客户端收到的每一行同样都是一个包含响应的有效的JSON-RPC片段。
这样解决有以下理由:这样很容易实现也很容易Debug查错,因为两边都是对人类目读友好的格式。这个协议不像其他的解决方案,可以不搞乱向后兼容性而轻易扩展。作为奖励,JSON是被全平台广泛支持,而且现在的矿机都有JSON库。所以消息的打包解包都很简单方便。
HTTP相关的开销不再有了,用HTTP头编码的挖矿扩展标记的骚操作也不再有了。对基于HTTP的getwork来说,最大的改进在于服务器可以自己驱动负载,服务器任何时候都可以给矿工发送广播消息,没有了长轮询的一套替代方法、负载均衡问题、数据包风暴。

Extranonce超随机数滚动:新维度

这可能是这个新协议最革新的部分。与当前挖矿方式不同,之前只能通过更改ntime和nonce来迭代,Stratum挖矿协议赋予了矿工在本地轻松构建独有的Coinbase币基交易数据的权力,因此它们将能够在本地产生独有区块头。我推荐迭代4字节的超随机数,这样单个TCP连接就可以给18Eh/s的矿机提供服务。矿池运营者也可以很容易的修改它。
后面会有一点技术有关的内容,我们也解释一下。区块头(在getwork响应里、被矿机做哈希运算的部分)由下面几个部分组成:

  • 块版本、nbits、前块哈希、一些填充字节们,这是一些常量
  • nonce随机数、ntime,这是矿机已经可以修改的部分
  • merkle根哈希,这是由比特币交易的哈希计算生成的
    为了能够生成更多的独有区块头(因此能够产生更多独有的哈希数)我们得修改一些东西。

每一个比特币区块包含了称为coinbase币基交易数据的东西,币基交易指出了区块奖励发送去哪个地址。幸好有一个机会不用破坏任何事情就可以修改这个交易数据。通过修改coinbase币基交易数据,merkle根将改变,我们将拥有独有的区块头可以做哈希运算。现在这种情况(产生独有币基)已经在矿池服务器上发生了。所以我们把他移到矿工上!

其他决策

JSON 对比 你偏好的协议

因为序列化和反序列化的开销,我考虑过很多解决方案。我给出过几点选择JSON的理由,现在我们再总结一下:

  • JSON载荷是对人类可读友好,易于实现易于debug测错。
  • 所有比特币矿工已经有JSON库。JSON已经被大多数语言原生支持。
  • 不同于大多数二进制协议,JSON载荷可以不破坏朝后兼容性的情况下轻易扩展。
  • JSON-RPC已经明确给出三个原生的Stratum使用的消息类型:请求,响应和通知。我们不需要重新造轮子了。
  • JSON确实有一些数据开销,但是Stratum挖矿消息典型的适合一个TCP数据包
    为什么我放弃其他的序列化库:
  • 定制文本协议是人类可读友好的也易于debug测错,但不是乍一看就能实现。我们得定义一种如何修改请求和响应的方式,因为对请求的一系列连续处理在有些平台上可能会有点棘手(是的,我指的就是我用的实现矿池的Twisted框架)。我们也得定义如何序列化像是lists列表甚至mappings映射这样的变量数据类型。JSON显然替我们解决了这些事情。
  • 定制二进制协议是最紧凑的,能省不少带宽,尤其是正好比特币挖矿里又都是对二进制数据的处理。但是写序列化器/反序列化器会是相当的烦。我要的是轻易实现的协议。在字节顺序和二进制头文件里搞,不是我要的。
  • Google提供的Protocol buffers数据标准是一个很有意思的概念。它可能也很适合,如果它不是仅被C++,Python,Java支持的话。
  • Thrift 也是一个二进制协议,我以前用过,但是对我们的目标来说它太重了。

Stratum 对比 Getblocktemplate

比特币0.7版本出现的Getblocktemplate是一个非常先进的解决方案,是代表了从完整客户端到用单机来进行块创建的软件规范。Stratum挖矿服务器在底层使用 getblocktemplate 方法。还有一些为什么是Stratum的原因,我看来,一个对于挖矿更好的解决方案是:

  • 不太复杂,在现存的矿机上容易实现,能完美工作
  • 历史原因,getblocktemplate使用HTTP协议和长轮询方法。我之前描述过在大规模挖矿情况下这样行不通的。
  • 比特币交易数据处理量越来越大,Stratum可以更好的测量它们,因为它只传输merkle叉哈希,不同于getblocktemplate里,服务器内存池得完整的存储。
  • 相比getblocktemplate,在Stratum里对矿机提交的share进行检查统计也十分节省处理资源。
    现在Stratum比getblocktemplate更糟的只有一点,矿工没法自己选择要打包的交易数据。以我经验看,99%的真实矿工根本不在乎打包的是什么交易,他们只追求最高的块收益。矿工和矿池运营者是互利的,在这一点上,实在没啥理由为了让那1%的非要自定义区块的矿工觉得满意,就去让挖矿协议更加复杂。
    我已经有了一些Stratum挖矿协议如何扩展的想法,矿工将来也能够提议他们自己的merkle叉(我内部称为民主挖矿),这样就能解决一些交易数据生成中心化的问题。但当下我决定全力去让大多数矿工觉得舒服,扩展还是以后说吧。

挖矿软件开发者相关

Stratum协议基于JSON-RPC-2.0。这一章,我希望你熟悉这个协议,你明白像是请求,响应,通知这些术语。请阅读JSON-RPC规范了解细节内容。
更高级的理解Stratum协议的概念,请在Google文档里阅读Stratum协议规范。本文档也很重要,但只是连接Stratum服务器最基本的示例。

异常控制

Stratum定义了简单异常处理。拒绝Share的示例类似如下:

{"id": 10, "result": null, "error": (21, "Job not found", null)}

error字段是被定义为(错误码,人可读消息,追溯信息)。回溯信息可以包含debug测错时需要的附加信息。
挖矿服务的建议错误码如下:

  • 20 - 其他/未知
  • 21 - 未发现工作任务(=失效了)
  • 22 - 重复的share
  • 23 - 难度过低的share
  • 24 - 未授权的矿工
  • 25 - 未订阅

实际示例

本章包括一段运行在测试网络上的矿池真实会话日志。
000000002076870fe65a2b6eeed84fa892c0db924f1482243a6247d931dcab32

矿机连接服务器

在会话的开始,客户端为了能够收到挖矿任务要订阅当前连接

{"id": 1, "method": "mining.subscribe", "params": []}\n
{"id": 1, "result": [ [ ["mining.set_difficulty", "b4b6693b72a50c7116db18d6497cac52"], ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"]], "08000002", 4], "error": null}\n

result结果包含以下三项:

  • 订阅详情 - 订阅通知的名称和描述ID构成的二元数据。理论上它也可以用于退订订阅,但是显然矿机用不上。
  • Extranonce1 超随机数1 - 16进制编码,每个连接特有的字符串,以后币基序列化会用到,保持他安全!
  • Extranonce2_size 超随机数2大小 - 代表了超随机数2期望的长度,这个是被矿机生成的

认证矿工

现在来认证一些矿工。在会话期间任何时候你想认证多少矿工都可以。这样,你能通过一个Stratum连接来掌控许多独立的挖矿设备。

{"params": ["slush.miner1", "password"], "id": 2, "method": "mining.authorize"}\n
{"error": null, "id": 2, "result": true}\n

服务器开始发送带有挖矿工作任务的通知

服务器会在被订阅之后立即发送*(最少)*一个工作任务 小的工程学标记:第一个工作任务不直接包含订阅的响应是因为 - 矿机有两种不同的方式来掌控一个响应类型;前者作为订阅响应,后者作为独立通知。钩住任务处理只是作为JSON-RPC通知听起来更适合我。

{"params": ["bf", "4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000",
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008",
"072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000", [],
"00000002", "1c2ac4af", "504e86b9", false], "id": null, "method": "mining.notify"}

现在我们终于有了一些有趣的东西了!我将按顺序描述这个通知的每个字段。

  • job_id - 任务的ID。在上传生成的share适合需要使用。
  • prevhash - 前块的哈希。
  • coinb1 - coinbase币基交易的最初部分
  • coinb2 - coinbase币基交易的最后部分
  • merkle_branch - 哈希数的列表,用于计算merkle根。不是所有交易数据的列表,只包含merkle树算法的各步的准备好的哈希值。阅读一些资料可以理解merkle树如何计算的。不幸的是这个示例不包含任何一步哈希值,算我的错。
  • version - 比特币区块版本。
  • nbits - 编码了当前网络的难度
  • ntime - 当前的ntime时间
  • clean_jobs - 如果此字段为真,服务器意思是说,根据早先的工作任务提交的share不能用,这些share都会被拒绝。如果这个标记被设置了,矿机也应该丢掉之前所有的工作任务,所以job_ids最终可能会被循环。

如何构建Coinbase币基数据

现在矿机收到了所有序列化币基数据要用到的数据:coinb1,extranonce1,extranonce2_size和coinb2。首先我们需要生成extranonce2(必须是给定的独有的job_id)。extranonce2_size告诉我们二进制结构期望的长度。要绝对确信extranonce2生成器总是能够产生正确长度的extranonce2!作为例子,我的矿池设置的extranonce2_size=4,这意味着这是有效的extranonce2(16进制):00000000
为了产生币基,我们需要连接 coinb1 + extranonce1 + extranonce2 + coinb2 。这就行了!
下列计算过程,我们用给定的币基产生两次sha256哈希值。下面是python代码,我认为哪怕你是一个写Ruby的你估计也明白这个代码的意思,简单如此:

import hashlib
import binascii
coinbase_hash_bin = hashlib.sha256(hashlib.sha256(binascii.unhexlify(coinbase)).digest()).digest()  

如何构建merkle根

下列python片段将生产merkle根。使用来自广播的merkle_branch和来自之前片段的coinbase_hash_bin作为输入:

import binascii
def build_merkle_root(self, merkle_branch, coinbase_hash_bin):
        merkle_root = coinbase_hash_bin
        for h in self.merkle_branch:
                merkle_root = doublesha(merkle_root + binascii.unhexlify(h))
        return binascii.hexlify(merkle_root)

如何构建区块头

我们快完成了!我们把所有放到一起来产生用来哈希运算的块头。

version + prevhash + merkle_root + ntime + nbits + '00000000' +
'000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000'

第一串0是空白随机数,剩余部分是附加到 uint512上这总是一样的。
注意merkle_root必须颠倒顺序。如果你是一个矿工开发者,你已经有了做这件事的方法。作为python的示例你可以看stratum挖矿代理的源代码

服务器偶尔可以要求矿工更改Share难度

默认share难度是1(难度1的大端法目标是0x00000000ffff0000000000000000000000000000000000000000000000000000),但是服务器可以在会话期间任何时候要求矿工修改它:

{ "id": null, "method": "mining.set_difficulty", "params": [2]}

这意味着收到服务器工作任务的矿工下一条任务讲改为难度2。

如何提交Share

当矿工发现符合请求难度的任务,他会提交给服务器这样的share:

{"params": ["slush.miner1", "bf", "00000001", "504e86ed", "b2957c02"], "id": 4, "method": "mining.submit"}
{"error": null, "id": 4, "result": true}

按顺序是这些字段:worker_name(早先认证的),job_id,extranonce2超随机数2,ntime,nonce 结束!


下载


可兼容矿机

对于当前所有兼容getwork的矿机,你都可以在电脑上本地运行Stratum挖矿代理来使用。一个挖矿代理就可以处理无限的接连的矿工,所以为了你所有的矿机运行一个代理是个办法。
运行下面软件的矿机原生支持Stratum协议(无需代理!)