区块链核心技术演进之路 – 挖矿演进

当前位置:首页 > 币圈百科 > 区块链核心技术演进之路 – 挖矿演进

区块链核心技术演进之路 – 挖矿演进

2022-11-14币圈百科244

封面图

最后一部分讲算法进化。这一部分讲的是矿业演化。挖掘就是算法运算的过程。从计算机和代码的角度来看,就是反复执行Hash函数并检测执行结果的具体过程。和讨论算法一样,挖掘也是在采用POW共识机制的前提下讨论的。

众所周知,挖掘是从最初的CPU挖掘,到GPU挖掘,最后到现在的ASIC挖掘时代。本文分析了逻辑设计和技术实现。挖矿的进化不仅是硬件的进化,也是软件的进化,尤其是软硬件对接协议的改进。因此,本文直接将与挖掘相关的几个核心协议作为小标题,一步步展开讨论。

复习文章的时候发现“矿工”这个词比较模糊。这种情况在英国文学中也是类似的。在日常交流中,一般指拥有矿机的人。本文重点介绍区块链,采矿程序或机器统称为矿工。

挖掘

本节讨论挖掘的原理。首先,分析了比特币Blockheader的结构。我们说挖掘本质上就是执行哈希函数的过程,哈希函数是单输入单输出的函数,输入的数据就是这个块头。比特币头部有6个字段:

Snip20161108_4

如前所述,比特币的每一次挖掘都是对这80个字节进行两次连续的SHA256运算(SHA256D),运算结果是固定的32个字节(二进制256位)。

以上六个字段是不一样的,

nVersion,块版本号,升级时才会改变。HashPrevBlock,由前一个块确定。全网确定的NBits,每2016块重新调整一次,调整算法固定。

所以,以上3个字段可以理解为固定,对每个矿工都是一样的。矿工可以自由调整的是剩余的3个字段,Nonce,它提供了2 32个可能的值nTime。其实这个字段所能提供的价值空间是非常有限的,因为存在一个合理的阻塞时间范围,这个范围是根据前一个阻塞时间来确定的,如果比前一个阻塞时间太早或者太提前都会被其他节点拒绝。值得一提的是,后一个区块的区块时间比前一个区块稍早,这是允许的。一般来说,矿工会直接使用机器的当前时间戳。HashMerkleRoot理论上提供了2 256种可能性。该字段的更改来自于添加或删除块中包含的交易,或更改订单,或修改比特币基地交易的输入字段。

根据Hash函数的特性,这三个字段即使有任何一个发生变化,都会导致Hash运行结果发生很大的变化。在CPU挖矿时代,搜索空间主要由nNonce提供。矿机时代,nNonce提供的四个字节远远不够,搜索空间转向hashMerkleRoot。

p1

比特币挖矿的逻辑流程如下:

打包交易,检索待确认交易的内存池,选择要纳入区块的交易。矿工可以随意选择,甚至可以不选(挖空区块),因为每个区块都有容量限制(目前为1M),所以矿工不能无限选择。对于矿商来说,最合理的策略是先按照手续费对已确认的交易集进行排序,然后从高到低尽量包含最多的交易。构造比特币基地,确定该区块包含的交易集后,就可以统计该区块的总手续费了。结合产量规则,矿工可以计算出自己这一块的利润。为所有交易构造hashMerkleRoot和Merkle数。填写其他字段以获得完整的块标题。哈希运算,对块头执行SHA256D运算。验证结果,如果符合难度,就全网播,挖下一块;如果不符合难度,按照一定的策略改变上述字段中的一个,然后进行哈希运算和验证。

符合条件的区块条件如下:

SHA256D(Blockherder)F(nBits)

其中sha 256d(block herder)是挖掘的结果,F(nBits)是难度对应的目标值。两者都是256位,而且都是大整数处理,所以大小可以直接比较。

为了节省区块链存储空间,256位目标值通过一定的tra存储在32位nBits字段中 具体转换方法是将nBits的四个字节拆分,第一个字节代表右移位数,用V1表示,后三个字节记录数值,用V3表示,有:

F(nBits)=v _ 3 * 2(8 *(v _ 1-3))

另外,难度是比特币的最低难度值是nBits=0x1d00ffff,对应的最大目标值是0x0000000000000000

set generate

set generate协议接口代表了CPU挖矿的时代。

中本聪在他的论文中描述了“1个CPU 1张选票”的数字民主的理想概念。在最初的版本中,客户端自带了挖掘功能,客户端的挖掘非常简单。当然,在挖掘之前同步数据是必要的。现在很多算力不高的假币还是用客户端直接挖矿。开始挖掘有两种方式:

在配置文件中设置gen=1,然后启动客户端,节点会自行开始挖掘。客户端启动后,使用RPC接口setgenerate来控制挖掘。

如果使用的是经典的QT客户端,点击帮助菜单,打开调试窗口,在控制台输入以下命令:setgenerate true 2,然后回车,客户端开始挖掘。后面的数字代表挖掘线程的数量。如果要关闭挖掘,在控制台使用以下命令:setgenerate false,可以使用getmininginfo命令查看挖掘情况。

节点的挖掘过程也很简单:

构造块,初始化块头的各个字段,计算Hash,验证块,如果不合格,则增加n none,然后计算验证,以此类推。在CPU挖矿时代,nNonce提供的4字节搜索空间完全够用(4字节意味着4G的可能性,单核CPU运算SHA256D的计算能力一般在2M左右)。实际上,nNonce只遍历两个字节,然后返回重建块。

GETWORK

getwork协议代表了GPU挖矿的时代。这种需求主要是由于挖掘程序与节点客户端的分离以及区块链数据与挖掘组件的分离。

使用客户端节点的直接挖掘需要同步整个区块链。数据和程序紧密结合,即如果有多台计算机进行挖掘,每台计算机都需要单独同步一份区块链数据。其实这个没必要。对于挖掘器,至少需要一个完整的节点。同时,GPU挖掘时代的到来,也需要一种协议来与客户端节点进行交互。

p2GetWork的核心设计思想是:节点客户端构造块,然后将块头数据交给外部挖掘程序。挖掘程序遍历nNonce进行挖掘,验证后回传到节点客户端,节点客户端验证后广播到全网。

如前所述,块头共有80个字节。由于没有要确认的区块链数据和事务池,所以这四个字段即nVersion、hashPrevBlock、nBits和hashMerkleRoot的72个字节必须由节点客户端提供。挖掘程序主要以增量方式遍历nNonce,必要时可以对nTime字段进行微调。

对于图形GPU,其实不用担心Nonce的4字节搜索空间不足。而且挖掘程序从节点客户端得到一个数据后,不应该工作太久,否则很有可能这个块已经被别人挖了,继续挖下去只能是没用。对于比特币来说,虽然设计成每10分钟一个块,但是好的策略也应该是几秒钟内从节点重新申请新的挖掘数据。显卡方面,运行SHA256D的计算能力一般在200M~1G到1G之间,nNonce提供4G搜索空间,也就是说再好的显卡也能支持4秒左右。调整一次时间后,可以再挖4秒,时间绰绰有余。

节点提供RPC接口getwork,该接口有一个可选参数。如果没有参数,则是申请挖掘数据。如果有一个参数,那就是提交挖掘块数据。

无参数调用getwork,返回的数据如下:

数据字段

Snip20161108_5共有128个字节(80个块的第一个字节是48个完整字节),因为SHA256将输入数据切割成定长切片,每个切片是64个字节,总输入长度必须是64个字节的整数倍。一般输入长度不一样。其实对于挖矿来说,完井数据是固定的,这里不需要提供。外部挖矿软件可以自行完成。甚至不需要提供nNonce字段,数据至少只需要提供前76个字节。nTime字段也是必不可少的,外部挖掘程序需要参考节点提供的块时间来调整nTime。

目标字段

是当前块难度的目标值。它是小端顺序,在使用之前需要翻转。实际上,对于外部挖掘程序,可以使用数据和目标字段进行正常挖掘。但getwork协议充分考虑了各种情况,尽量帮助外部挖掘程序做力所能及的事情,并提供了两个额外的字段。数据字段返回该想法的完整且完整的数据。

中间状态字段

如上所述,SHA256对输入数据进行切片。矿工得到数据后,第一个切片(前64个字节)是固定的,Midstate是第一个切片的计算结果,节点帮助计算。因此,在midstate字段的帮助下,外部挖掘程序通常可以挖掘甚至只有44个字节的数据:来自midstate 32个字节的第一个片的剩余12(76-64)个字节的数据。

Hash1字段

比特币挖矿每次需要连续执行SHA256两次。第一次执行结果为32字节,需要补充32字节数据补足64字节作为SHA256第二次执行的输入。Hash1是完成数据,同样,hash1是固定的。

外部挖掘程序挖掘出符合条件的块后,再次调用getwork接口,将修改后的数据字段提交给节点客户端。客户端要求的数据也必须是128字节。

每次有外部无参数调用getwork时,节点客户端都会构造一个新的块。在返回数据之前,新块应该完全存储在内存中,并使用hashMerkleRoot作为唯一标识符。该节点使用一个Map来存储所有构造的块,当下一个块已经被其他人挖走时,Map应该立即被清空。

GetWork收到一个参数后,首先从参数中提取hashMerkleRoot,在Map中找出之前保存的块,然后从参数中提取nNonce和nTime填充块的对应字段,这样就可以验证块了。如果难度符合要求,说明挖到了一个块,节点广播到全网。

getwork协议是最早的挖掘协议版本,实现了节点和挖掘的分离。经典的GPU挖掘驱动cgminer和sgminer,以及cpuminer,都是使用getwork协议进行挖掘。Get cgminer一直是非常经典的合作。很多新的算法在推出的时候都很快移植到了CG Miner上。即使现在,除了BTC和LTC,许多其他竞争货币仍在使用getwork协议进行采矿。矿机出现后,挖矿速度大大提高。目前比特币矿机的计算能力已经达到10T/s级别。但getwork只为外部挖矿程序提供了32字节的4G搜索空间。如果继续使用getwork协议,矿机需要频繁调用RPC接口,显然不可行。如今,BTC和LTC节点都禁用了getwork协议,转而使用更新、更高效的getblocktemplate协议。

GetBlocktemplate

GetBlocktemplate协议诞生于2012年中期,当时挖矿池已经出现。矿池使用getblocktemplate协议与节点客户端交互,使用stratum协议与矿工交互,这是最典型的矿池构建模型。

与getwork相比,getblocktemplate协议最大的不同在于,getblocktemplate协议允许矿工自己构造块。因此,节点和挖掘是完全分离的。对于getwork来说,区块链是黑暗的。getwork对区块链一无所知。他只知道如何修改4字节的数据字段。 对于getblocktemplate,整个区块链是透明的。getblocktemplate拥有与区块链上的采矿相关的所有信息,包括待确认的交易池。getblocktemplate可以自己选择要包含在块中的事务。

getblocktemplate在开发之后就不是静态的了。在后续版本中,客户端进行了升级和改动,主要是增加了一些字段,但核心概念和核心字段保持不变。目前比特币客户端返回的数据如下。考虑到空间限制,事务字段中只保存一个事务数据。事实上,根据目前的实际情况,交易池中还有数万笔交易需要实时确认。目前区块基本满了(1M容量限制),再加上附加信息。所以每次调用getblocktemplate基本都有1.5M左右的返回数据,和getwork的几百字节不一样。

无标题我们来简单分析一下其中的一些核心领域。Version、PreviousblockHash和Bits分别指块版本号、前一个块Hash和难度。矿工可以直接在块头的相应字段中填充值。

Transactions,transaction set,不仅给出了每笔交易的十六进制数据,还给出hash、交易费等信息。Coinbaseaux,如果有任何你想写入区块链的信息,把它放在这个字段中,类似于中本聪的创世积木宣言。Coinbasevalue,挖一个区块的最大利润值,包括发行新币和交易费用。如果挖掘器包含事务字段中的所有事务,则该值可以直接用作比特币基地输出。目标,方块难度目标值。MinTime是指下一个块的最小时间戳,Curtime是指当前时间。这两个时间作为矿工调整ntime字段的参考。身高,下一个街区的难度。当前的协议规定这个值应该被写到coinbase的指定位置。

挖掘者得到这些数据后,挖掘步骤如下:

构建coinbase事务。涉及的领域包括比特币基地、Coinbasevalue、交易、身高等。当然,最重要的是指定一个收入地址。构建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秒,如果矿工在一秒之内完成了几次任务,说明矿池当前给到的难度低了,需要调高,反之。如此下来,经过一段时间调节,矿池能给矿工分配合理难度,并计算出矿工的算力。

p3矿池一直都是一个矛盾的存在,毫无疑问,矿池是中心化的,如上图所示,全网算力集中在几个矿池手里,网络虽然几千个节点同时在线,但只有矿池链接的几个点击拥有投票权,其他节点都只能行使监督权。矿池再一次将矿工至于“黑暗”之中,矿工对于区块链再次变得一无所知,他们只知道完成矿池分配的任务。

关于矿池,还有一个小插曲,在矿池刚出现时,反对声特别强烈,很多人悲观的认为矿池最终会导致算力集中,危及系统安全,甚至置比特币于死地。于是有人设计并实现了P2P矿池,力图将“抱团挖矿”去中心化,代码也都是开源的,但由于效率远不如中心化的矿池没能吸引太多算力,所谓理想很丰满,现实很骨感。

推荐几个比较成熟的开源矿池项目,有兴趣的读者可自行研究:PHP-MPOS,早期非常经典的矿池,很稳定,被使用最多,尤其山寨币矿池,后端使用Stratum Ming协议,源码地址https://github.com/MPOS/php-mposnode-open-mining-portal,支持多币种挖矿,源码地址https://github.com/zone117x/node-open-mining-portalPowerpool,支持混合挖矿,源码地址https://github.com/sigwo/powerpool

运行一个矿池需要考虑的问题很多,比如为了得到最及时的全网信息,矿池一般对接几个网络节点,而且最好分布在地球的几大洲。另外提高出块率,降低孤块率,降低空块率等都是矿池的核心技术问题,本文不能一一展开讨论,接下来只详细讨论一个问题,即矿池与矿工的具体配合工作方式——stratum协议。

STRATUM

矿池通过getblocktemplate协议与网络节点交互,以获得区块链的最新信息,通过stratum协议与矿工交互。此外,为了让之前用getwork协议挖矿的软件也可以连接到矿池挖矿,矿池一般也支持getwork协议,通过阶层挖矿代理机制实现(Stratum mining proxy)。须知在矿池刚出现时,显卡挖矿还是主力,getwork用起来非常方便,另外早期的FPGA矿机有些是用getwork实现的,stratum与矿池采用TCP方式通信,数据使用JSON封装格式。

p4先来说一下getblocktemplate遗留下来的几个问题:

矿工驱动:在getblocktemplate协议里,依然是由矿工主动通过HTTP方式调用RPC接口向节点申请挖矿数据,这就意味着,网络最新区块的变动无法及时告知矿工,造成算力损失。

数据负载:如上所述,如今正常的一次getblocktemplate调用节点都会反馈回1.5M左右的数据,其中主要数据是交易列表,矿工与矿池需频繁交互数据,显然不能每次分配工作都要给矿工附带那么多信息。再者巨大的内存需求将大大影响矿机性能,增加成本。

Stratum协议彻底解决了以上问题。

Stratum协议采用主动分配任务的方式,也就是说,矿池任何时候都可以给矿工指派新任务,对于矿工来说,如果收到矿池指派的新任务,应立即无条件转向新任务;矿工也可以主动跟矿池申请新任务。

现在最核心的问题是怎么样让矿工获得更大的搜索空间,如果参照getwork协议,仅仅给矿工可以改变nNonce和nTime字段,则交互的数据量很少,但这点搜索空间肯定是不够的。想增加搜索空间,只能在hashMerkleroot下功夫,如果让矿工自己构造coinbase,那么搜索空间的问题将迎刃而解,但代价是必要要把区块包含的所有交易都交给矿工,矿工才能构造交易列表的Merkleroot,这对于矿工来说压力更大,对于矿池带宽要求也更高。

Stratum协议巧妙解决了这个问题,成功实现既可以给矿工增加足够的搜索空间,又只需要交互很少的数据量,这也是Stratum协议最具创新的地方。

p5再来回顾一下区块头的6个字段80字节,这个很关键,nVersion,nBits,hashPrevBlock这3个字段是固定的,nNonce,nTime这两个字段是矿工现在就可以改变的。增加搜索空间只能从hashMerkleroot下手,这个绕不过去。Stratum协议让矿工自己构造coinbase交易,coinbase的scriptSig字段有很多字节可以让矿工自由填充,而coinbase的改动意味着hashMerkleroot的改变。从coinbase构造hashMerkleroot无需全部交易,如上图所示,假如区块将包含13笔交易,矿池先对这13笔交易进行处理,最后只要把图中的4个黑点(Hash值)交付给矿工,同时将构造coinbase需要的信息交付给矿工,矿工就可以自己构造hashMerkleroot(图中的绿点都是矿工自行计算获得,两两合并Hash时,规定下一个黑点代表的hash值总是放在右边)。按照这种方式,假如区块包含N笔交易,矿池可以浓缩成log2(N)个hash值交付给矿工,这大大降低了矿池和矿工交互的数据量。

Stratum协议严格规定了矿工和矿池交互的接口数据结构和交互逻辑,具体如下:

1. 矿工订阅任务

启动挖矿机器,使用mining.subscribe方法链接矿池

Snip20161108_6返回数据很重要,矿工需本地记录,在整个挖矿过程中都用到,其中:b4b6693b72a50c7116db18d6497cac52:给矿工指定初始难度,ae6812eb4cd7735a302a8a9dd95cf71f:订阅号ID08000002:学名Extranonce1 ,用于构造coinbase交易4:学名Extranonce2_size ,即Extranonce2的长度,这里指定4个字节

Extranonce1,和 Extranonce2对于挖矿很重要,增加的搜索空间就在这里,现在,我们至少有了8个字节的搜索空间,即nNonce的4个字节,以及 Extranonce2的4个字节。

2. 矿池授权在矿池注册一个账号 ,添加矿工,矿池允许每个账号任意添加矿工数,并取不同名字以区分。矿工使用mining.authorize 方法申请授权,只有被矿池授权的矿工才能收到矿池指派任务。

3. 矿池分配任务

Snip20161108_7

以上每个字段信息都是必不可少,其中:

Snip20161108_8bf:任务号ID,每一次任务都有唯一标识符4d16b6f85af6e2198f44ae2a6de67f78487ae5611b77c6c0440b921e00000000:前一个区块hash值,hashPrevBlock

01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20020862062f503253482f04b8864e5008

:学名coinb1 ,构造coinbase的第一部分序列数据

072f736c7573682f000000000100f2052a010000001976a914d23fcdf86f7e756a64a7a9688ef9903327048ed988ac00000000

:学名coinb2 蓑衣网小编2022 ,构造coinbase的第二部分序列数据,

["76cffd68bba7ea661512b68ec6414438191b08aaeaec23608de26ac87820cbd02016","e5a796c0b88fe695949a3e7b0b7b1948a327b2f28c5dbe8f36f0a18f96b2ffef2016"]

:学名merkle_branch,交易列表的压缩表示方式,即上图的黑点00000002:区块版本号,nVersion1c2ac4af:区块难度nBits504e86b9:当前时间戳,nTime

有了以上信息,再加上之前拿到的Extranonce1 和Extranonce2_size,就可以挖矿了。

4. 挖矿

构造coinbase交易

用到的信息包括Coinb1, Extranonce1, Extranonce2_size 以及Coinb2,构造很简单:

Coinbase=Coinb1 + Extranonce1 + Extranonce2 + Coinb2

为啥可以这样,因为矿池帮矿工做了很多工作,矿池已经构建了coinbase交易,系列化后在指定位置分割成coinb1和coinb2,coinb1和coinb2包含指定信息,比如coinb1包含区块高度,coinb2包含了矿工的收益地址和收益额等信息,但是这些信息对于矿工来说无关紧要,矿工挖矿的地方只是Extranonce2 的4个字节。另外Extranonce1是矿池写入区块的指定信息,一般来说,每个矿池会写入自己矿池的信息,比如矿池名字或者域名,我们就是根据这个信息统计每个矿池在全网的算力比重。

构建Merkleroot

利用coinbase和merkle_branch,按照上图方式构造hashMerkleroot字段。

构建区块头

填充余下的5个字段,现在,矿池可以在nNonce和Extranonce2 里搜索进行挖矿,如果嫌搜索空间还不够,只要增加Extranonce2_size为多几个字节就可轻而易举解决。

5. 矿工提交工作量蓑衣网小编2022

当矿工找到一个符合难度的shares时,提交给矿池,提交的信息量很少,都是必不可少的字段:

Snip20161108_9slush.miner1:矿工名字,矿池用以识别谁提交的工作量bf:任务号ID,矿池在分配任务之前,构造了Coinbase等信息,用这个任务号唯一标识00000001:Extranonce2504e86ed:nTime字段b2957c02:nNonce字段

矿池拿到以上5个字段后,首先根据任务号ID找出之前分配任务前存储的信息(主要是构建的coinbase交易以及包含的交易列表等),然后重构区块,再验证shares难度,对于符合难度要求的shares,再检测是否符合全网难度。

6. 矿池给矿工调节难度

矿池记录每个矿工的难度,并根据shares率不断调节以指定合适难度。矿池可以随时通过mining.set_difficulty方法给矿工发消息另其改变难度。

Snip20161108_10如上,Stratum协议核心理念基本解析清楚,在getblocktemplate协议和Stratum协议的配合下,矿池终于可以大声的对矿工说,让算力来的更猛烈些吧。

AUXPOW

在挖矿的发展历史上,还出现了一个天马行空的事情,即混合挖矿(Merge 蓑衣网小编2022 Mining)。域名币(Namecoin)最先使用混合挖矿模式,挂靠在比特币链条上,矿工挖比特币时,可以同时挖域名币,后来狗狗币(Dogecoin)也支持混合挖矿,挂靠在莱特币(Litecoin)链条上。混合挖矿使用Auxiliary Proof-of-Work (AuxPOW)协议实现,虽然混合挖矿不怎么流行,但是协议设计的很精巧,最初看到协议时我不禁感叹社区的力量之伟大,这种都能想出来。

以域名币的混合挖矿举例,比特币作为父链(Parent Blockchain),域名币作为辅链(Auxiliary Blockchain),AuxPOW协议的实现无需改动父链(比特币当然不会为了域名币做任何改动),但辅链需要做针对性设计,比如狗狗币改为支持混合挖矿时就进行了硬分叉。

p6AuxPOW的实现得益于比特币Coinbase的输入字段,中本聪当初不知有意无意,在此处只规定了长度限制,留了一片未定义区域。这片区域后来对比特币的发展产生深远影响,很多升级和优化都盯上这片区域,比如上文讲的Stratum协议。中本聪类似的有意无意情况还有很多,比如交易的nSequence字段,也是因为没有明确定义,被黑客盯上引发的“延展性”问题成了“门头沟”倒闭的替罪羊。再比如说nNonce,如果一开始定义的字节大一些,你比方说32字节,挖矿的演进就不需要以上讨论的那么多协议。

AuxPOW协议核心理念不同的地方在于:

对于经典的POW区块,规定只有难度符合要求才算一个合格的区块,AuxPOW协议对区块难度没有要求,但附加两个条件:

辅链区块的hash值必须内置于父链区块的Coinbase里。该父链区块的难度必须符合辅链的难度要求。

将辅链区块的hash值内置于父链的Coinbase,其实是利用父链作存在证明。这样就可以实现间接依靠父链的算力来维护辅链安全。一般来说,父链的算力比辅链大,因而满足父链难度要求的区块一定同时满足辅链难度要求,反之则不成立。这样一来,很多本来在父链达不到难度要求的区块,却达到辅链难度要求,矿工g=广播到辅链网络,在辅链获得收益,何乐而不为。

AuxPOW协议对两条链都有一些数据结构方面的规定,对于父链,要求必须在区块的coinbase的scriptSig字段中插入如下格式的44字节数据:

Snip20161108_2对于辅链,对原区块结构改动比较大,在nNonce字段和txn_count之间插入了5个字段,这种区块取名AuxPOW区块。

Snip20161108_3混合挖矿要求父链和辅链的算法一致,是否支持混合挖矿是矿池的决定,矿工不知道是否在混合挖矿。矿池如果支持混合挖矿,需要对接所有辅链的节点。

将辅链区块hash值内置在父链的Coinbase,意味着矿工在构造父链Coinbase之前,必先构造辅链的AuxPOW 区块并计算hash值。如果只挖一条辅链,情况较为简单,如果同时挖多条辅链,则先对所有辅链在挖区块构造Merkleroot。矿池可以将特定的44字节信息内置于上文Stratum协议中提到的Coinb1中,交给矿工挖矿。对矿工返回的shares重构父链区块和所有辅链区块,并检测难度,如果符合辅链难度要求,则将整个AuxPOW区块广播到辅链。

辅链节点验证AuxPOW区块逻辑过程如下:

依靠父链区块头(parent_block)和区块Hash值(block_hash,本字段其实没必要,因为节点可以自行计算),验证父链区块头是否符合辅链难度要求。依靠Coinbase交易(coinbase_txn)、其所在的分支(coinbase_branch)以及父链区块头(parent_block),验证Coinbase交易是否真的被包含在父链区块中。依靠辅链分支(blockchain_branch),以及Coinbase中放Hash值的地方(aux_block_hash),验证辅链区块Hash是否内置于父链区块的Coinbase交易中。

通过以上3点验证,则视为合格的辅链区块。

CONCLUSION

中本聪最初设计比特币时希望所有节点都采用CPU挖矿,一般认为只有这样才能充分保证区块链的去中心化特征,比特币在CPU时代安全度过了萌芽阶段。getwork和cgminer将挖矿带入GPU时代,国内显卡曾经一度脱销,全网算力迅速提升了一个档次,CPU挖矿惨遭淘汰。随着越来越多人参与挖矿,全网算力不断上升,催生了抱团挖矿(矿池)。然而GPU时代的繁荣历史也没能持续多久就被getblocktemplate,stratum以及矿机带入了ASIC时代。

getwork实现了数据与挖矿分离,getblocktemplate给外部挖矿程序提供了最大自由度,彻底解决了外部挖矿程序与节点交互的可扩展性问题(scalability problems),主要用于矿池与网络节点对接。stratum不但解决了搜索空间不足的问题,同时也解决了矿池与矿机交互数据量大的问题。getblocktemplate和stratum这两个协议使大型矿池,大规模矿场,大算力矿机成为可能,从此挖矿产业进入一个全新阶段,此后挖矿的演进主要集中于几个方向:矿池的设计优化与稳定运行,矿场的科学部署,以及矿机工艺升级,提升算力,降低功耗等。

区块链核心技术演进之路 – 挖矿演进 | 分享给朋友: