主页 > imtoken钱包苹果 > 今天五一,我们来聊聊区块链挖矿的演进

今天五一,我们来聊聊区块链挖矿的演进

imtoken钱包苹果 2023-03-12 07:46:39

火球金融

懂区块链更懂你

总文本:5335 字

预计阅读时间 8 分钟

挖矿的演进主要集中在几个方向:矿池的设计优化和稳定运行、矿场的科学部署、矿机技术升级以提高算力和降低功耗。

挖掘是执行算法操作的过程。从计算机和代码的角度来看,就是反复执行Hash函数并检测执行结果的具体过程。和讨论算法一样,挖矿也是在采用 POW 共识机制的前提下讨论的。

大家都已经知道,挖矿从CPU挖矿开始,过渡到GPU挖矿,最后演变成现在的ASIC(专业矿机)挖矿时代。本文分析了逻辑设计和技术实现。挖矿的演进是硬件的演进,也是软件的演进,尤其是软硬件对接协议的改进。因此,本文直接讨论了与挖矿相关的几个核心协议作为字幕,并逐步进行讨论。

矿业

本节讨论采矿原理。首先,分析比特币区块头(Blockheader)的结构。我们说挖矿本质上就是执行Hash函数的过程,而Hash函数是一个单输入单输出的函数,输入的数据就是区块头。比特币区块头中有 6 个字段:

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

以上6个字段不同,nVersion,区块版本号,只会在升级过程中改变。hashPrevBlock,由前一个区块决定。nBits由全网决定,每2016个区块重新调整一次,调整算法固定。

因此,以上3个字段可以理解为固定的,对于每个矿工来说都是一样的。矿工可以自由调整的地方是剩下的3个字段nNonce,它提供了2^32个可能的值nTime。其实这个字段能提供的价值空间是非常有限的,因为有一个合理的出块时间范围。这个范围是根据上一个出块时间,如果比上一个出块时间太早或者太早,就会被其他节点拒绝。值得一提的是,后一个区块的出块时间略早于前一个区块的时间,这是允许的。一般来说,矿工会直接使用机器当前的时间戳。hashMerkleRoot,理论上提供了 2^256 种可能性。该字段的变化来自于添加或删除块中包含的交易,或者改变顺序,

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

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

打包交易,检索待处理交易的内存池,并选择要包含在区块中的交易。矿工可以任意选择,甚至不选择(挖矿区块),因为每个区块都有容量限制(目前是1M),所以矿工不能无限选择。对于矿工来说,最合理的策略是先将待确认的交易集按照手续费进行排序,然后尝试从高到低包含最多的交易。

构建好 Coinbase 后,确定该区块包含的交易集后,即可统计该区块的总费用。结合输出规则,矿工可以计算出自己的区块收益。

构造hashMerkleRoot,为所有交易构造Merkle number。

填写其他字段以获取完整的块头。

哈希运算,对区块头进行SHA256D运算。

如果验证结果符合难度,则向全网广播,挖掘下一个区块;如果不满足难度,则按照一定的策略更改上述字段之一,然后进行哈希运算并进行验证。

比特币挖矿难度的历史演进

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

SHA256D(封锁者)

其中SHA256D(Blockherder)为挖掘结果,F(nBits)为难度对应的目标值,均为256位,均视为大整数,直接比较大小判断是否满足难度要求。

为了节省区块链的存储空间,将 256 位的目标值通过一定的变换和无损压缩存储在 32 位的 nBits 字段中。具体变换方法是拆分使用4个字节的nBits,第一个字节表示右移的位数,用V1表示,后3个字节记录数值,用V3表示,有:

F(nBits)=V_3 * 2^(8*(V_1-3) )

另外,难度有一个下限,也就是说F(nBits)有一个最大值。比特币的最小难度值为nBits=0x1d00ffff,对应的最大目标值为:

因此,挖矿可以形象地类比抛硬币,比如有 256 个硬币,给定数字 1、2、3……所有数字前 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,返回数据如下:

数据字段

一共128个字节(80个块头字节+48个补码字节),因为SHA256将输入数据分成固定长度的切片,每个切片为64字节,输入总长度必须是64字节的整数倍,输入长度一般不满足要求,按照一定的规则在元数据末尾完成数据。其实对于挖矿来说,完成数据是固定的,这里不需要提供,外部挖矿软件可以自己完成。甚至不需要提供nNonce字段,数据只需要提供至少前76个字节。nTime字段也是必不可少的,外部挖矿程序需要参考节点提供的出块时间来调整nTime。

目标字段

即当前区块难度目标值,以little-endian字节顺序,需要翻转后才能使用。

实际上,对于外部挖掘程序,如果有数据和目标两个字段,则可以正常进行挖掘。但是,getwork 协议充分考虑了各种情况,并试图帮助外部挖矿程序尽其所能,提供了两个额外的字段。,数据字段返回完整的完成数据也是基于这个思路。

中州场

如上所述,SHA256 将输入数据处理成分片。矿工拿到数据后,第一个shard(前64字节)是固定的,midstate是第一个shard的计算结果,节点帮助计算Out。

因此,借助 midstate 字段,外部矿工甚至只需要 44 字节的数据即可正常挖矿:32 字节的 midstate + 剩余的 12(第一个切片的 76-64) 字节数据。

哈希1字段

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

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

每次在没有外部参数的情况下调用一次 getwork,节点客户端都会构造一个新块。在返回数据之前,新区块必须完全存储在内存中,并以 hashMerkleRoot 作为唯一标识。该节点使用一个 Map 来存储所有构造的块。区块,当下一个区块被其他人挖到时,地图立即清零。

getwork接收到一个参数后,首先从参数中提取hashMerkleRoot,在Map中找到之前保存的block,然后从参数中提取nNonce和nTime填入block的对应字段,即可验证block . 如果难度匹配请求,则表明一个区块已经被挖出,节点将其广播到全网。

getwork协议是最早版本的挖矿协议,实现了节点与挖矿的分离。经典的GPU挖矿驱动cgminer和sgminer,cpuminer都使用getwork协议进行挖矿。getwork + cgminer 一直是非常经典的组合。当许多新算法推出时,它们很快被移植到 cgminer。即使是现在,除了 BTC 和 LTC 之外,许多其他山寨币仍在使用 getwork 协议进行挖矿。矿机出现后,挖矿速度大幅提升,目前比特币矿机的算力已经达到10T/sec的水平。而getwork只为外部挖矿程序提供了4G的32字节搜索空间。如果继续使用getwork协议,矿工需要频繁调用RPC接口,这显然是不可行的。现在 BTC 和 LTC 节点都禁用了 getwork 协议并切换到更新和更高效的 getblocktemplate 协议。

比特币挖矿难度的历史演进

获取块模板

getblocktemplate 协议诞生于 2012 年年中,当时矿池出现了。矿池使用getblocktemplate协议与节点客户端交互,使用stratum协议与矿工交互,是最典型的矿池建设模式。

与getwork相比,getblocktemplate协议最大的区别在于:

getblocktemplate 协议允许矿工自行构建区块。这样,节点和挖矿就完全分离了。对于getwork来说,区块链是黑暗的,getwork对区块链一无所知,他只知道修改数据字段的4个字节。对于getblocktemplate来说,整个区块链是透明的,getblocktemplate掌握了区块链上所有与挖矿相关的信息比特币挖矿难度的历史演进,包括待确认的交易池,getblocktemplate可以自行选择区块中包含的交易。

getblocktemplate 开发后,就不是一成不变的了。在后续版本中,客户端进行了升级和改动,主要是增加了一些字段,但核心概念和核心字段保持不变。

目前,比特币客户端返回的数据如下。考虑到篇幅限制,交易字段(transactions)中只保留了一个交易数据。事实上,按照目前的实际情况,交易池中有数万笔交易需要实时确认。满了(1M容量限制),再加上额外的信息,所以每次调用getblocktemplate,返回的数据基本是1.5M左右,和getwork的几百字节不一样。

让我们简要分析一些核心领域。Version、Previousblockhash 和 Bits 分别是指区块版本号、上一个区块 Hash 和难度。矿工可以直接用值填充块头的相应字段。

Transactions,交易集合,不仅给出每笔交易的十六进制数据,还给出哈希、交易费用等信息。Coinbaseaux,如果有信息要写入区块链,放在这个字段,类似于中本聪的创世区块声明。Coinbase 值,挖掘下一个区块的最大收益值,包括新币的发行和交易费用。如果矿工在 Transactions 字段中包含所有交易,他们可以直接将此值用作 coinbase 输出。Target,区块难度目标值。mintime 是指下一个区块的最小时间戳,Curtime 是指当前时间。这两个时间作为矿工调整nTime字段的参考。高度,下一个区块的难度,

矿工拿到数据后,挖矿步骤如下:

构建coinbase交易涉及的字段包括Coinbaseaux、Coinbasevalue、Transactions、Height等,当然最重要的还是要指定一个收益地址。

构造hashMerkleRoot,将coinbase放在transactions字段中包含的交易列表之前,然后对相邻交易进行SHA256D运算,最终构建交易的Merkle树。由于 coinbase 有很多字节供矿工随意玩,交易列表也可以随意更改或添加或删除,因此 hashMerkleRoot 值空间可以认为几乎是无限的。事实上,getblocktemplate 协议设计的主要目标是让矿工获得这个巨大的搜索空间。

构造区块头,分别使用Version、Previousblockhash、Bits和Curtime填充区块头的对应字段比特币挖矿难度的历史演进,nNonce字段可以默认设置为0。

挖矿,矿工可以在nNonce、nTime、hashMerkleRoot提供的搜索空间中设计自己的挖矿策略。

提交数据。当矿工挖出一个区块时,立即使用 submitblock 接口将完整的区块数据提交给节点客户端,由节点客户端验证并广播。

需要注意的是,和上面提到的使用 getwork 进行 GPU 挖矿一样,虽然 getblocktemplate 为矿工提供了巨大的搜索空间,但矿工不应该挖掘一个请求数据的时间过长,而应该定期向节点询问最新的区块。区块和最新交易信息,提高挖矿收益。

水池

挖矿有两种方式,一种叫SOLO挖矿,一种是去矿池挖矿。如前所述,直接在节点客户端启动CPU挖矿,依靠getwork+cgminer驱动显卡直接连接节点客户端挖矿,都是SOLO挖矿。SOLO就像用自己的独资企业买彩票。中彩票并不容易。拥有它。去矿池挖矿就像一起买彩票。大家一起捐钱买了一堆彩票。中奖后,按照投资比例分配收益。

理论上矿工可以使用getblocktemplate协议链接节点客户端进行SOLO挖矿,但实际上目前还没有矿工这样做过。在写这篇文章的时候,整个比特币网络的算力是1600P+,目前最先进的矿机算力是10T左右。这样,单台矿机SOLO挖出一个区块的概率小于16万分之一。矿工(人)投入真金白银购买矿机和支付电费,因此不会进行这种高风险的投资。显然,更适合投资矿池进行分组挖矿,降低风险,获得稳定收益。因此,矿池的出现是不可避免的,无法消除,无论是否破坏了系统的去中心化原则。

矿池的核心工作是给矿工分配任务、统计工作量和分配利润。矿池将区块难度分成多个难度较低的任务,发送给矿工进行计算。矿工完成一项任务后,将工作量提交给矿池,称为提交份额。如果全网出块难度要求哈希运算结果的前 70 位全为 0,那么矿池分配给矿工的任务可能只要求前 30 位为 0(根据矿工的计算能力)。矿工完成指定难度任务后提交分享,矿池会检查前30位是否为0,看前70位是否恰好为0。

比特币挖矿难度的历史演进

矿池会根据每个矿工的算力分配不同难度的任务。矿池如何确定矿工的算力来分配合适的任务难度?调整思路与比特币区块难度相同。矿池需要依赖矿工的份额。矿池希望分配给每个矿工的任务足够矿工计算一定的时间,比如1秒。如果矿工在一秒钟内完成了几项任务,则说明矿池给出的难度较低,需要提高,反之亦然。这样,经过一段时间的调整,矿池可以给矿工分配合理的难度,计算矿工的算力。

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

关于矿池,还有一个小插曲。矿池刚出现时,反对声特别强烈。许多人悲观地认为,矿池最终会导致算力集中,危及系统安全,甚至扼杀比特币。于是有人设计并实现了一个P2P矿池,试图去中心化“群挖矿”,代码也是开源的,但是因为效率远不及中心化矿池,所以并没有吸引多少算力,所以所谓的理想很充实。,现实很骨感。

推荐几个成熟的开源矿池项目,感兴趣的读者可以自行研究:

PHP-MPOS,早期非常经典的矿池,非常稳定,用的最多,尤其是altcoin矿池,后端使用的是Stratum Ming协议,源码地址

node-open-mining-portal,支持多币种挖矿,源码地址

Powerpool,支持混合挖矿,源地址

运行矿池时需要考虑很多问题。例如,为了获取全网最及时的信息,矿池一般会连接几个网络节点,最好分布在地球的几大洲。此外,提高出块率、降低孤块率、降低空块率是矿池的核心技术问题。本文无法一一讨论。接下来只详细讨论一个问题,即矿池和矿工的具体工作方式。- 层协议。

地层

矿池通过getblocktemplate协议与网络节点交互,获取区块链最新信息,通过stratum协议与矿工进行交互。另外,为了让之前使用getwork协议挖出的软件能够接入矿池进行挖矿,矿池一般支持getwork协议,通过Stratum挖矿代理机制实现。需要注意的是,矿池刚出现的时候,显卡挖矿还是主力军,getwork使用起来非常方便。另外,一些早期的FPGA矿机是用getwork实现的,stratum用TCP方式与矿池通信,数据采用JSON封装格式。

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

矿工驱动:在getblocktemplate协议中,矿工仍然通过HTTP主动调用RPC接口向节点申请挖矿数据,这意味着网络中最新的区块变化无法及时通知矿工,导致计算损失力量。

数据加载:如上所述,一个普通的getblocktemplate调用节点会返回大约1.5M的数据。主要数据是交易清单。矿工和矿池需要经常交换数据。显然,不可能每次都分配工作。给矿工附加这么多信息。此外,巨大的内存需求会极大地影响矿机的性能,增加成本。

Stratum 协议完全解决了上述问题。

Stratum 协议采用主动分配任务的方式,即矿池可以随时为矿工分配新的任务。对于矿工来说,如果收到矿池分配的新任务,应立即无条件切换到新任务;矿工也可以主动向矿池申请新任务。

现在的核心问题是如何让矿工获得更大的搜索空间。如果矿工只能参考getwork协议改变nNonce和nTime字段,交互数据量是非常少的,但是这个搜索空间肯定是不够的。如果要增加搜索空间,只能在hashMerkleroot上下功夫。如果让矿工自己构建coinbase,那么搜索空间的问题就解决了,但代价是区块中包含的所有交易都必须交给矿工,这样矿工才能构建交易列表. Merkleroot,对矿工压力更大,对矿池带宽要求更高。

Stratum 协议巧妙地解决了这个问题。成功实施不仅可以为矿工增加足够的搜索空间,而且只需要少量的数据进行交互。这也是 Stratum 协议中最具创新性的部分。

让我们回顾一下块头的 6 个字段的 80 个字节。这个非常重要。nVersion、nBits、hashPrevBlock 三个字段是固定的,nNonce 和 nTime 这两个字段现在可以由矿工更改。增加搜索空间只能从hashMerkleroot开始,无法绕过。Stratum 协议允许矿工自己构建 coinbase 交易。coinbase 的 scriptSig 字段有很多字节可以由矿工自由填充,而 coinbase 的变化意味着 hashMerkleroot 的变化。

从 coinbase 构建 hashMerkleroot 不需要所有交易。如上图所示,如果区块中包含 13 笔交易,矿池会先处理这 13 笔交易,最后只需要将图中的 4 个黑点(Hash 值)交付给矿工,同时将构建coinbase所需的信息传递给矿工,矿工可以自己构建hashMerkleroot(图中绿点是矿工自己计算的。在成对合并hash时,规定hash值表示为下一个黑点总是放在右边)。这样,如果区块中包含 N 笔交易,矿池可以将其浓缩成 log2(N) 哈希值并交付给矿工,

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