Warning: Trying to access array offset on value of type bool in /www/wwwroot/blockchain/wp-content/themes/Grace8.2/functions_suxingme.php on line 1208

Warning: Trying to access array offset on value of type bool in /www/wwwroot/blockchain/wp-content/themes/Grace8.2/functions_suxingme.php on line 1209

Warning: Trying to access array offset on value of type bool in /www/wwwroot/blockchain/wp-content/themes/Grace8.2/functions_suxingme.php on line 1210

Warning: Trying to access array offset on value of type bool in /www/wwwroot/blockchain/wp-content/themes/Grace8.2/functions_suxingme.php on line 1303

挖矿原理

1篇文章
挖矿是作算法运算的过程,从计算机和代码的角度来说,是反复执行Hash函数并检测执行结果的具体过程。与讨论算法一样,挖矿也是在采用POW共识机制前提下讨论。 大家已经非常清楚挖矿是由最开始的CPU挖矿,过度到GPU挖矿,最终演化到当前的ASIC(专业矿机)挖矿时代,本篇解析其中的逻辑设计和技术实现。挖矿的演进是硬件的演进过程,同时也是软件的演进过程,尤其是软硬件对接协议的改进过程,因此本文直接将与挖矿有关的几个核心协议作为小标题,一步步深入讨论。 在复查文章时我发现“矿工”一词用的比较模糊,这种情况在英文文献也差不多,日常交流中一般指拥有挖矿机器的人,本篇着眼于区块链,挖矿的程序或者机器都统称矿工(Miner)。 MINING 本小节讨论挖矿原理,首先解析比特币区块头(Blockheader)结构,我们说挖矿本质是执行Hash函数的过程,而Hash函数是一个单输入单输出函数,输入数据就是这个区块头。比特币区块头共6个字段: int32_t nVersion; //版本号,4字节 uint256 hashPrevBlock; //前一个区块的区块头hash值,32字节 uint256 hashMerkleRoot; //包含进本区块的所有交易构造的Merkle树根,32字节 uint32_t nTime; //Unix时间戳,4字节 uint32_t nBits; //记录本区块难度,4字节 uint32_t nNonce; //随机数,4字节 如上,比特币每一次挖矿就是对这80个字节连续进行两次SHA256运算(SHA256D),运算结果是固定的32字节(二进制256位)。 以上6个字段情况又各不相同, nVersion,区块版本号,只有在升级时候才会改变。 hashPrevBlock,由前一个区块决定。 nBits,由全网决定,每2016个区块重新调整,调整算法固定。 因此以上3个字段可以理解为是固定的,对于每个矿工来说都一样。矿工可以自由调整的地方是剩下的3个字段, nNonce,提供2^32种可能取值 nTime,其实本字段能提供的值空间非常有限,因为合理的区块时间有一个范围,这个范围是根据前一个区块时间来定,比前一个区块时间太早或者太超前都会被其他节点拒绝。值得一提的是,后一个区块的区块时间略早于前一个区块时间,这是允许的。一般来说,矿工会直接使用机器当前时间戳。 hashMerkleRoot,理论上提供2^256种可能,本字段的变化来自于对包含进区块的交易进行增删,或改变顺序,或者修改Coinbase交易的输入字段。 根据Hash函数特性,这3个字段中哪怕其中任意1个位的变化,都会导致Hash运行结果巨大变化。在CPU挖矿时代,搜索空间主要由nNonce提供,进入矿机时代,nNonce提供的4个字节已经远远不够,搜索空间转向hashMerkleRoot。 比特币挖矿的逻辑过程如下: 1. 打包交易,检索待确认交易内存池,选择包含进区块的交易。矿工可以任意选择,甚至可以不选择(挖空块),因为每一个区块有容量限制(当前是1M),所以矿工也不能无限选择。对于矿工来说,最合理的策略是首先根据手续费对待确认交易集进行排序,然后由高到低尽量纳入最多的交易。 2. 构造Coinbase,确定了包含进区块的交易集后,就可以统计本区块手续费总额,结合产出规则,矿工可以计算自己本区块的收益。 3. 构造hashMerkleRoot,对所有交易构造Merkle数。 4. 填充其他字段,获得完整区块头。 5. Hash运算,对区块头进行SHA256D运算。 6. 验证结果,如果符合难度,则广播到全网,挖下一个块;不符合难度则根据一定策略改变以上某个字段后再进行Hash运算并验证。 合格的区块条件如下: SHA256D(Blockherder) 其中,SHA256D(Blockherder)就是挖矿结果,F(nBits)是难度对应的目标值,两者都是256位,都当成大整数处理,直接对比大小以判断是否符合难度要求。 为了节约区块链存储空间,将256位的目标值通过一定变换无损压缩保存在32位的nBits字段里。具体变换方法为拆分利用nBits的4个字节,第1个字节代表右移的位数,用V1表示,后3个字节记录值,用V3表示,则有: 此外难度有最低限制,也就是说 有个最大值,比特币最低难度取值nBits=0x1d00ffff,对应的最大目标值为: 0x00000000FFFF0000000000000000000000000000000000000000000000000000 因此挖矿可以形象的类比抛硬币,好比有256枚硬币,给定编号1,2,3……256,每进行一次Hash运算,就像抛一次硬币,256枚硬币同时抛出,落地后要求编号前n的所有硬币全部正面向上。 SETGENERATE Setgenerate协议接口代表了CPU挖矿时代。 中本聪在论文里描述了“1 CPU 1 Vote”的理想数字民主理念,在最初版本客户端就附带了挖矿功能,客户端挖矿非常简单,当然,需要同步数据结束才可以挖矿。现在有很多算力很低的山寨币还是直接使用客户端挖矿,有两种方式可以启动挖矿: 1) 在配置文件设置gen=1,然后启动客户端,节点将自行启动挖矿。 2) 客户端启动后,利用RPC接口setgenerate控制挖矿。 如果使用经典QT客户端,点击“帮助”菜单,打开“调试窗口”,在“控制台”输入如下命令:setgenerate true 2,然后回车,客户端就开始挖矿,后面的数字代表挖矿线程数,如果想关闭挖矿,在控制台使用如下命令:setgenerate false,可以使用getmininginfo命令查看挖矿情况。 节点挖矿过程: 构造区块,初始化区块头各个字段,计算Hash并验证区块,不合格则nNonce自增,再计算并验证,如此往复。在CPU挖矿时代,nNonce提供的4字节搜索空间完全够用(4字节即4G种可能,单核CPU运算SHA256D算力一般是2M左右),其实nNonce只遍历完两个字节就返回去重构块。 GETWORK getwork协议代表了GPU挖矿时代,需求主要源于挖矿程序与节点客户端分离,区块链数据与挖矿部件分离。 使用客户端节点直接挖矿,需要同步完整区块链,数据和程序紧密结合,也就是说,如果有多台电脑进行挖矿,需要每台电脑都单独同步一份区块链数据。这其实没有必要,对于矿工来说,最少只需要一个完整节点就可以。而以此同时,GPU挖矿时代的到来,也需要一个协议与客户端节点交互。 getwork核心设计思路是: 由节点客户端构造区块,然后将区块头数据交给外部挖矿程序,挖矿程序遍历nNonce进行挖矿,验证合格后交付回给节点客户端,节点客户端验证合格后广播到全网。 如前所述,区块头共80个字节,由于没有区块链数据和待确认交易池,nVersion,hashPrevBlock,nBits和hashMerkleRoot这4个字段共72个字节必须由节点客户端提供。挖矿程序主要是递增遍历nNonce,必要时候可以微调nTime字段。 对于显卡GPU来说,其实不用担心nNonce的4字节搜索空间不足,而且挖矿程序从节点客户端那里拿到一份数据后,不应该埋头工作太久,不然很有可能这个块已经被其他人挖到,继续挖只能做无用功,对于比特币来说,虽然设计为每10分钟一个区块,良好的策略也应该在秒级内重新向节点申请新的挖矿数据。对于显卡来说,运行SHA256D算力一般介于200M~1G,nNonce提供4G搜索空间,也就是说再好的显卡也能支撑4秒左右,调整一次nTime,又可以再挖4秒,这个时间绰绰有余。 节点提供RPC接口getwork,该接口有一个可选参数,如果不带参数,就是申请挖矿数据,如果带一个参数,就是提交挖到的块数据。 不带参数调用getwork,返回数据如下: { “midstate” : “9226a024e0b77f61d49fd5ffdf828c6b5c4330c61ea2778c606a8e49d4ad8bd6″, “data” :”00000002e9337bac28ee28a949d2140f9fb0a0ab740acfd739d7bcf67ca31c2301db858ad2ca54d92c8c1cded715922c4df2b07d9f10fa1a6cf3db7e949b320615761ed4581c76f21b12d87500000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000″, “hash1″ :”00000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000010000″, “target” : “00000000000000000000000000000000000000000000000075d8120000000000″ } Data字段 共128字节(80区块头字节 + 48补全字节),因为SHA256将输入数据切分成固定长度的分片处理,每个切片64字节,输入总长度必须是64字节的整数倍,输入长度一般不符合要求,则根据一定规则在元数据末端补全数据。其实对于挖矿来说,补全数据是固定不变的,这里没必要提供,外部挖矿软件可以自行补齐。甚至连nNonce字段都不需要提供,data最少只需要提供前面的76字节就够了。nTime字段也是必不可少的,外部挖矿程序需要参照节点提供的区块时间来调节nTime。 Target字段 即当前区块难度目标值,采用小头字节序,需要翻转才能使用。 其实对于外部挖矿程序来说,有data 和 target这两个字段就可以正常挖矿了,不过getwork协议充分考虑各种情况,尽量帮助外部挖矿程序做力所能及的事,提供了两个额外字段,data字段返回完整补全数据也是出于此理念。…

关注我们的公众号

微信公众号