注:内容翻译自https://en.bitcoin.it/wiki/Stratum_mining_protocol
部分内容摘自:https://blog.csdn.net/qq_34352738/article/details/79330920
2.3.1 stratum简介
2012年底,Stratum协议是为了扩展支持矿池挖矿而编写的挖矿协议,并替换了废弃的getwork协议。挖矿服务技术最初是在Slush's pool's网站被公告出来。没过多久,BTCGuild提供了可替代的“备忘单”形式的文档。
2.3.2 stratum挖矿过程
【01】矿机(client)使用subscribe方法连接矿池(server)
{“id”: 1, “method”: “mining.subscribe”, “params”: []}\n //申请链接,参数可以不填
{“id”: 1, “result”: [ [ ["mining.set_difficulty", "mining.set_difficulty"], ["mining.notify", "mining.notify"]], “08000002″, 4], “error”: null}\n //返回数据
矿池返回的数据,需要矿工记录在本地,在整个挖矿过程中都要用到,其中:
1)mining.set_difficulty:用于矿池去设置矿机当前提交任务的最大难度值;
2)mining.notify:用于矿池去发送任务给矿机进行运算;
3)08000002:Extranonce1,用于构造coinbase = {cionb1,Extranonce1,Extranonce2,coinb2};//拼接
4)4 :Extranonce2_size,即Extranonce2的长度,4字节。
注:Extranonce1和Extranonce2对于挖矿很重要,用于增加搜索空间,现在,可有8个字节的搜索空间,即nNonce+Extranonce2;Extranonce1是矿池发过来的固定值,即在本次任务中是固定的。
【02】矿机(client)使用authorize方法向矿池(server)授权挖矿
在矿池注册一个账号,添加矿工,矿池允许每个账号任意添加矿工数,并取不同名字以区分。并且每个账号对应一个钱包地址,用于矿池(server)分配收益。
{“id”: 2,“params”: ["user_name", "password"], “method”: “mining.authorize”}\n
{“error”: null, “id”: 2, “result”: true}\n
注:有些矿池不用注册账号也能挖矿,比如(F2Pool),那么在授权时,user_name直接替换成钱包地址,password可以不填写。细心的朋友可能发现,为什么"id"号会发生变化,其实这个ID号是用于【矿机】区别来自【矿池】的应答消息的,因为【矿池】的应答消息结构有些很相似,那么【矿机】如何区别这些消息是对应哪个发送消息的应答。例如【矿池】返回两个消息应答,一个应答消息中的result为true,另一个应答消息中的result为false,且两个应答消息的结构一致,那么【矿机】接收到这两个应答后如何区别是哪个发送消息的应答,那么这个ID号就是来干这件事情的。然而,如何消息是【矿池】主动发给【矿机】的,比如mining.set_difficulty和mining.notify两个方法,他们的ID号为NULL。
【03】矿池(server)使用notify方法分配任务给矿机(client)
{“params”: ["bf", "4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000",
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008",
"072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000", ["76cffd68bba7ea661512b68ec6414438191b08aaeaec23608de26ac87820cbd02016","e5a796c0b88fe695949a3e7b0b7b1948a327b2f28c5dbe8f36f0a18f96b2ffef2016"],
“00000002″, “1c2ac4af”, “504e86b9″, false], “id”: null, “method”: “mining.notify”}
以上每个字段信息都是必不可少,其中:
1)bf: 任务号ID,每一次任务都有唯一的标识符。
2)4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000:前一个区块的HASH值。
3)0100000001000.....ffffffff20020862062f503253482f04b8864e5008: coinb1,构造coinbase的首部分数据。
4)072f736c757368....756a64a7a9688ef9903327048ed988ac00000000: coinb2,构造coinbase的尾部分数据。
5)["76cffd68bba7ea66151...87820cbd02016","e5a796c0b88fe....b2ffef2016"]: merkle_branch,交易列表。
6)00000002:区块版本号,nVersion。
7)1c2ac4af: 区块当前难度nBits,这个难度是全网总难度。
8)504e86b9: 当前时间戳,nTime。
9)false:【Clean Jobs(清除工作标志)】;如果这个变量为true,矿工应该退出当前工作,立即开始新的工作;如果为False,矿工仍然可以继续当前的挖矿工作,但是应该在当前nonce范围结束后开始一个新的工作。
注:有了这个信息,再加上之前得到的Extranonce1和Extranonce2_size,就可以计算挖矿了。
【04】矿机(client)开始挖矿
1)构造coinbase信息
为什么要构造coinbase信息,是因为矿机需要扩展随机数,Extranonce2,来构造属于矿机自己的merkleroot,请看下图:
其中红色的点即为coinbase(256bits),构造它是为了与其它交易列表进行HASH运算,黑色的点为矿池分配任务中的交易列表,并与coinbase进行hash得到最终merkleroot。
构造coinbase = {coinb1 , Extranonce1 , Extranonce2 , coinb2};//拼接
注:为啥可以这样,因为矿池帮矿工做了很多工作,矿池已经构建了coinbase交易,系列化后在指定位置分割成coinb1和coinb2,coinb1和coinb2包含指定信息,比如coinb1包含区块高度,coinb2包含了矿工的收益地址和收益额等信息,但是这些信息对于矿工来说无关紧要,矿工挖矿的地方只是Extranonce2的4个字节。另外Extranonce1是矿池写入区块的指定信息,一般来说,每个矿池会写入自己矿池的信息,比如矿池名字或者域名,根据这个信息统计每个矿池的全网的算力比重。
2)构建merkleroot
利用coinbase和merkle_branch两两配对成512bit,如果coinbase与merkle_branch数量之和不是偶数,那么最后一个merkle_branch与它自身进行合并得到512bits,然后把配对完成后的512bits的各个块进行SHA-256(SHA-256())运算,得到上一层的结果,然后重复上面的过程,继续配对计算,最终得到merkle root(256bits)。
3)构建区块头
填充余下的5个字段,现在,矿池可以在nNonce和Extranonce2里搜索进行挖矿,如果嫌搜索空间还不够,只要增加Extranonce2_size为多几个字节就可以轻而易举的解决。
【05】矿机(client)使用submit方法向矿池(server)提交工作量
当矿工找到一个符合难度的shares时,提交给矿池,提交信息量很少,都是必不可少的字段:
{“params”: ["user_name", "bf", "00000001", "504e86ed", "b2957c02"], “id”: 3, “method”: “mining.submit”}
{“error”: null, “id”: ,3, “result”: true}
1)00000001: Etranonce2的值。
2)504e86ed :nTime的值。
3)b2957c02 :nNonce的值。
注:矿池拿到以上5个字段后,首先根据任务号ID找出之前分配任务前存储的信息,然后重构区块,再验证shares难度,对于符合难度要求的shares,在检测是否符合全网难度。
【06】矿池(server)使用set_diffculty方法给矿机(client)调整难度值
矿池记录每个矿工的难度,并根据shares率不断调节以指定适合难度。格式如下:
{ “id”: null, “method”: “mining.set_difficulty”, “params”: [32768]}
如上,其中,params[32768]便是新难度值,这个难度值是什么意思呢,以上这条信息是F2pool矿池比特币发给矿机的难度(时间2018/07/09),这个数字的意思是派给矿机的任务难度是1个单位比特币难度值的32768倍,一个单位的比特币难度值是对少呢,答案是:0x1d00ffff,转化为256bits是0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;转化过程使用一个公式:data = (0x1d00ffff & 0x00ffffff) * 256**(0x1d - 3);其中**是指数操作;那么32768倍的比特币难度单位的值是多少呢,答案是:(0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff)/32768。
上述挖矿算法我已用C++写出来,由于我的电脑的算力太低,无法提交矿池发给我的任务难度,因此最后一个提交过程无法验证成功,测试提交,矿池会返回false。矿池从联机发第一个任务后,如果3秒内,矿机没有提交结果,矿池会再发一个任务,连续发送三个任务都没有提交的,则矿池认为矿机断开连接,需要重新连接。