区块链挖矿演进--链新科技

日期:2020-03-17 11:06  作者:链新科技

挖矿的演进主要集中于几个方向:矿池的设计优化与稳定运行,矿场的科学部署,以及矿机工艺升级,提升算力,降低功耗等。
 
挖矿是作算法运算的过程,从计算机和代码的角度来说,是反复执行Hash函数并检测执行结果的具体过程。与讨论算法一样,挖矿也是在采用POW共识机制前提下讨论。
 
大家已经非常清楚挖矿是由最开始的CPU挖矿,过度到GPU挖矿,最终演化到当前的ASIC(专业矿机)挖矿时代,本篇解析其中的逻辑设计和技术实现。挖矿的演进是硬件的演进过程,同时也是软件的演进过程,尤其是软硬件对接协议的改进过程,因此本文直接将与挖矿有关的几个核心协议作为小标题,一步步深入讨论。
 
MINING
 
本小节讨论挖矿原理,首先解析比特币区块头(Blockheader)结构,我们说挖矿本质是执行Hash函数的过程,而Hash函数是一个单输入单输出函数,输入数据就是这个区块头。比特币区块头共6个字段:
 
如上,比特币每一次挖矿就是对这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。
 
比特币挖矿的逻辑过程如下:
 
打包交易,检索待确认交易内存池,选择包含进区块的交易。矿工可以任意选择,甚至可以不选择(挖空块),因为每一个区块有容量限制(当前是1M),所以矿工也不能无限选择。对于矿工来说,最合理的策略是首先根据手续费对待确认交易集进行排序,然后由高到低尽量纳入最多的交易。
 
构造Coinbase,确定了包含进区块的交易集后,就可以统计本区块手续费总额,结合产出规则,矿工可以计算自己本区块的收益。
 
构造hashMerkleRoot,对所有交易构造Merkle数。
 
填充其他字段,获得完整区块头。
 
Hash运算,对区块头进行SHA256D运算。
 
验证结果,如果符合难度,则广播到全网,挖下一个块;不符合难度则根据一定策略改变以上某个字段后再进行Hash运算并验证。
 
合格的区块条件如下:
 
其中,SHA256D(Blockherder)就是挖矿结果,F(nBits)是难度对应的目标值,两者都是256位,都当成大整数处理,直接对比大小以判断是否符合难度要求。
 
为了节约区块链存储空间,将256位的目标值通过一定变换无损压缩保存在32位的nBits字段里。具体变换方法为拆分利用nBits的4个字节,第1个字节代表右移的位数,用V1表示,后3个字节记录值,用V3表示,则有:
 
此外难度有最低限制,也就是说 F(nBits) 有个最大值,比特币最低难度取值nBits=0x1d00ffff,对应的最大目标值为:
 
因此挖矿可以形象的类比抛硬币,好比有256枚硬币,给定编号1,2,3……256,每进行一次Hash运算,就像抛一次硬币,256枚硬币同时抛出,落地后要求编号前n的所有硬币全部正面向上。
 
Setgenerate协议接口代表了CPU挖矿时代。
 
中本聪在论文里描述了“1 CPU 1 Vote”的理想数字民主理念,在最初版本客户端就附带了挖矿功能,客户端挖矿非常简单,当然,需要同步数据结束才可以挖矿。现在有很多算力很低的山寨币还是直接使用客户端挖矿,有两种方式可以启动挖矿:
 
在配置文件设置gen=1,然后启动客户端,节点将自行启动挖矿。
 
客户端启动后,利用RPC接口setgenerate控制挖矿。
 
如果使用经典QT客户端,点击“帮助”菜单,打开“调试窗口”,在“控制台”输入如下命令:setgenerate true 2,然后回车,客户端就开始挖矿,后面的数字代表挖矿线程数,如果想关闭挖矿,在控制台使用如下命令:setgenerate false,可以使用getmininginfo命令查看挖矿情况。
 
节点挖矿过程也非常简单:
 
构造区块,初始化区块头各个字段,计算Hash并验证区块,不合格则nNonce自增,再计算并验证,如此往复。在CPU挖矿时代,nNonce提供的4字节搜索空间完全够用(4字节即4G种可能,单核CPU运算SHA256D算力一般是2M左右),其实nNonce只遍历完两个字节就返回去重构块。
 
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,返回数据如下:
 
Data字段
 
共128字节(80区块头字节 + 48补全字节),因为SHA256将输入数据切分成固定长度的分片处理,每个切片64字节,输入总长度必须是64字节的整数倍,输入长度一般不符合要求,则根据一定规则在元数据末端补全数据。其实对于挖矿来说,补全数据是固定不变的,这里没必要提供,外部挖矿软件可以自行补齐。甚至连nNonce字段都不需要提供,data最少只需要提供前面的76字节就够了。nTime字段也是必不可少的,外部挖矿程序需要参照节点提供的区块时间来调节nTime。
 
Target字段
 
即当前区块难度目标值,采用小头字节序,需要翻转才能使用。
 
其实对于外部挖矿程序来说,有data 和 target这两个字段就可以正常挖矿了,不过getwork协议充分考虑各种情况,尽量帮助外部挖矿程序做力所能及的事,提供了两个额外字段,data字段返回完整补全数据也是出于此理念。
 
Midstate字段
 
如上所述,SHA256对输入数据分片处理,矿工拿到data数据后,第一个分片(头64字节)是固定不变的,midstate就是第一个分片的计算结果,节点帮忙计算出来了。
 
因此,在midstate字段辅助下,外部挖矿程序甚至只需要44字节数据就可以正常挖矿:32字节midstate + 第一个切片余下的12(76-64)字节数据。
 
Hash1字段
 
比特币挖矿每次都需要连续执行两次SHA256,第一次执行结果32字节,需要再补充32字节数据凑足64字节作为第二次执行SHA256的输入。hash1就是补全数据,同理,hash1也是固定不变的。
 
外部挖矿程序挖到合格区块后再次调用getwork接口将修改过的data字段提交给节点客户端。节点客户端要求返回的数据也必须是128字节。
 
每次有外部无参调用一次getwork时,节点客户端构造一个新区块,在返回数据前,都要把新区块完整保存在内存,并用hashMerkleRoot作为唯一标识符,节点使用一个Map来存放所有构造的区块,当下一个块已经被其他人挖到时,立即清空Map。
 
getwork收到一个参数后,首先从参数提取hashMerkleRoot,在Map中找出之前保存的区块,接着从参数中提取nNonce和nTime填充到区块的对应字段,就可以验证区块了,如果难度符合要求,说明挖到了一个块,节点将其广播到全网。
 
getwork协议是最早版本挖矿协议,实现了节点和挖矿分离,经典的GPU挖矿驱动cgminer和sgminer,以及cpuminer都是使用getwork协议进行挖矿。getwork + cgminer一直是非常经典的配合,曾经很多新算法推出时,都快速被移植到cgminer。即便现在,除了BTC和LTC,其他众多竞争币都还在使用getwork协议进行挖矿。矿机出现之后,挖矿速度得到极大提高,当前比特币矿机算力已经达到10T/秒级别。而getwork只给外部挖矿程序提供32字节共4G的搜索空间,如果继续使用getwork协议,矿机需要频繁调用RPC接口,这显然不可行。如今BTC和LTC节点都已经禁用getwork协议,转向更新更高效的getblocktemplate协议。
 
getblocktemplate协议诞生于2012年中叶,此时矿池已经出现。矿池采用getblocktemplate协议与节点客户端交互,采用stratum协议与矿工交互,这是最典型的矿池搭建模式。
 
与getwork相比,getblocktemplate协议最大的不同点是:
 
getblocktemplate协议让矿工自行构造区块。如此一来,节点和挖矿完全分离。对于getwork来说,区块链是黑暗的,getwork对区块链一无所知,他只知道修改data字段的4个字节。对于getblocktemplate来说,整个区块链是透明的,getblocktemplate掌握区块链上与挖矿有关的所有信息,包括待确认交易池,getblocktemplate可以自己选择包含进区块的交易。
 
getblocktemplate 在被开发出来后并非一成不变,在随后发行的各个版本客户端都有所升级改动,主要是增添一些字段,不过核心理念和核心字段不变。
 
目前比特币客户端返回数据如下,考虑到篇幅限制,交易字段(transactions)只保留了一笔交易数据,其实根据当前实际情况,待确认交易池实时有上万笔交易,目前区块基本都是塞满的(1M容量限制),加上额外信息,因此每次调用getblocktemplate基本都有1.5M左右返回数据,相对于getwork的几百个字节而言,不可同日而语。
 
来简单分析一下其中几个核心字段, Version,Previousblockhash,Bits这三个字段分别指区块版本号,前一个区块Hash,难度,矿工可以直接将数值填充区块头对应字段。
 
Transactions,交易集合,不但给了每一笔交易的16进制数据,同时给了hash,交易费等信息。 Coinbaseaux,如果有想要写入区块链的信息,放在这个字段,类似中本聪的创世块宣言。 Coinbasevalue,挖下一个块的最大收益值,包括发行新币和交易手续费,如果矿工包含Transactions字段的所有交易,可以直接使用该值作为coinbase输出。 Target,区块难度目标值。 Mintime,指下一个区块时间戳最小值,Curtime指当前时间,这两个时间作为矿工调节nTime字段参考。 Height,下一个区块难度,目前协议规定要将这个值写入coinbase的指定位置。
 
矿工拿到这些数据之后,挖矿步骤如下:
 
构建coinbase交易,涉及到字段包括Coinbaseaux,Coinbasevalue,Transactions,Height等,当然最重要的是要指定一个收益地址。
 
构建hashMerkleRoot,将coinbase放在transactions字段包含的交易列表之前,然后对相邻交易两两进行SHA256D运算,最终可以构造交易的Merkle树。由于coinbase有很多字节可供矿工随意发挥,此外交易列表也可随意调换顺序或者增删,因而hashMerkleRoot值空间几乎可以认为是无限的。其实getblocktemplate协议设计的主要目标就是让矿工获得这个巨大的搜索空间。
 
构建区块头,利用Version,Previousblockhash,Bits以及Curtime分别填充区块头对应字段,nNonce字段可默认置0。
 
挖矿,矿工可在由nNonce,nTime,hashMerkleRoot提供的搜索空间里设计自己的挖矿策略。
 
上交数据,当矿工挖到一个块后当立即使用submitblock接口将区块完整数据提交给节点客户端,由节点客户端验证并广播。
 
需要注意的是,与上文提到的GPU采用getwork挖矿一样,虽然getblocktemplate给矿工提供了巨大搜索空间,但矿工不应对一份请求数据挖矿太久,而应循环适时向节点索要最新区块和最新交易信息,以提高挖矿收益。
 
POOL
 
挖矿有两种方式,一种叫SOLO挖矿,另一种是去矿池挖矿。前文所述的在节点客户端直接启动CPU挖矿,以及依靠getwork+cgminer驱动显卡直接连接节点客户端挖矿,都是SOLO挖矿,SOLO好比自己独资买彩票,不轻易中奖,中奖则收益全部归自己所有。去矿池挖矿好比合买彩票,大家一起出钱,能买一堆彩票,中奖后按出资比率分配收益。
 
理论上,矿机可以借助getblocktemplate协议链接节点客户端SOLO挖矿,但其实早已没有矿工会那么做,在写这篇文章时,比特币全网算力1600P+,而当前最先进的矿机算力10T左右,如此算来,单台矿机SOLO挖到一个块的概率不到16万分之一,矿工(人)投入真金白银购买矿机、交付电费,不会做风险那么高的投资,显然投入矿池抱团挖矿以降低风险,获得稳定收益更加适合。因此矿池的出现是必然,也不可消除,无论是否破坏系统的去中心化原则。
 
矿池的核心工作是给矿工分配任务,统计工作量并分发收益。矿池将区块难度分成很多难度更小的任务下发给矿工计算,矿工完成一个任务后将工作量提交给矿池,叫提交一个share。假如全网区块难度要求Hash运算结果的前70个比特位都是0,那么矿池给矿工分配的任务可能只要求前30位是0(根据矿工算力调节),矿工完成指定难度任务后上交share,矿池再检测在满足前30位为0的基础上,看看是否碰巧前70位都是0。
 
矿池会根据每个矿工的算力情况分配不同难度的任务,矿池是如何判断矿工算力大小以分配合适的任务难度呢?调节思路和比特币区块难度一样,矿池需要借助矿工的share率,矿池希望给每个矿工分配的任务都足够让矿工运算一定时间,比如说1秒,如果矿工在一秒之内完成了几次任务,说明矿池当前给到的难度低了,需要调高,反之。如此下来,经过一段时间调节,矿池能给矿工分配合理难度,并计算出矿工的算力。
 
矿池一直都是一个矛盾的存在,毫无疑问,矿池是中心化的,如上图所示,全网算力集中在几个矿池手里,网络虽然几千个节点同时在线,但只有矿池链接的几个点击拥有投票权,其他节点都只能行使监督权。矿池再一次将矿工至于“黑暗”之中,矿工对于区块链再次变得一无所知,他们只知道完成矿池分配的任务。
 
关于矿池,还有一个小插曲,在矿池刚出现时,反对声特别强烈,很多人悲观的认为矿池最终会导致算力集中,危及系统安全,甚至置比特币于死地。于是有人设计并实现了P2P矿池,力图将“抱团挖矿”去中心化,代码也都是开源的,但由于效率远不如中心化的矿池没能吸引太多算力,所谓理想很丰满,现实很骨感。
 
有需要区块链挖矿系统的可以联系链新科技,链新科技针对区块链技术、数字货币、区块链交易系统、区块链支付系统开发、区块链钱包开发等等多年行业经验,服务于大小企业100多家。  
 
1

联系方式

 
  • 服务热线:彭17158684783
微信
 

联系地址

 
深圳总部:深圳市龙华新区民治大道民治地铁站B出口(离深圳北站三公里)
香港办事处:香港九龙尖沙咀么地道尖沙咀中心东翼A座1607室
澳门办事处:澳门罗理基博士大马路商业中心一期808