哥白尼开发者:挖矿难度精讲

哈希值 2018-12-06

本文由哥白尼团队何思羽创作

挖矿难度

比特币主链上,平均每十分钟会出一个块。随着数字货币的发展,参与的 miner 数量与日俱增,挖矿技术日新月异,全网的算力也是以惊人的速度增长。BTC 中为了保证主链平均的高度增加速度依然维持最初设定,进而设置了挖矿难度调整的功能。深入理解挖矿难度的概念,以及挖矿难度调整的方案,对开发人员以及 miner 都很重要,因为挖矿难度设置不合理可能会导致全网出块速度极不稳定。本文将详细介绍 BTC&BCH 挖矿难度及调整方案,我们先从 PoW 算法讲起。

1、PoW 算法

PoW (Proof-of-Work)工作量证明算法是一种对应服务与资源滥用、或是阻断服务攻击的经济对策。一般是要求用户进行一些耗时适当的复杂运算,并且答案能被服务方快速验算,以此耗用的时间、设备与能源做为担保成本,以确保服务与资源是被真正的需求所使用。

PoW 算法具有:去中心化,单向,随机性,目标难度易调整等特点,所以现在包括 BTC,BCH 在内的很多币种都采用了 PoW 共识机制。

从实现上来说,PoW 算法的输入为任意长度,输出为固定长度,比如通常使用 SHA256 算法对应输出 256-bit。在挖矿过程中 miner 用 PoW 算法计算整个块头的 hash 值,由于 SHA256 的特性:块头任意一位发生变化,得到的 hash 值会变得完全不一样,而且大小变化方向不确定。于是,我们比较 hash 值是否小于某个值(实际上这个值是保存在块头中的 nBit「解压后」的 current_target 值)来判断是否满足要求;如果小于,则广播这个区块;如果不小于,则按照当前挖矿节点的规则改变块头中可以改变的值,然后再次计算块头 hash 值,以此往复,直到结果小于目标值。

哥白尼开发者:挖矿难度精讲

由此可知,current_target 值越小,满足挖矿要求的概率就越小,挖矿难度就越大。

2、块头 & Coinbase 交易

块头的生成: 当 miner 开始新一轮打包之后,首先会创建一个空的块,块结构分为块头以及块信息两部分。先打包块信息,再根据块信息填充块头。

首先看一下已经成功打包的块。下图是写本文稿时截取最新的 BTC block 详情。

哥白尼开发者:挖矿难度精讲

块信息 存放的是从 mempool 里取出来的一系列交易信息,miner 并以此创建了一个 Merkle Tree,交易信息的 hash 值作为 leaf,最终生成的 Merkle Root 将填到块头里。值得注意的是,交易列表中的第一个是一个非常独特的交易:Coinbase Transaction。Coinbase Transaction 与普通交易主要的区别有:

1) Coinbase Transaction 不消耗 UTXO

2) input 只有一个,叫做 Coinbase

3) output 的 addresss 为 miner 的 btc/bch 地址

4) value 由挖矿奖励和交易费组成

5)更值得注意的是 input 中没有 Unlocking-script,取而代之的是 Coinbase Data (这部分数据包含 Extra Nonce,在挖矿难度非常高时,将起非常重要的作用)

Coinbase 交易 input 的结构如下:

哥白尼开发者:挖矿难度精讲

  • 2013-12-28 BTC 的一个块的 Coinbase 解析

哥白尼开发者:挖矿难度精讲

Coinbase data,该字段数据长度范围为 2-byte~100-byte:

  • block height 起初 Coinbase 是不包含块高度信息,由于重复交易的问题出现,诞⽣了 BIP30,随后第二套解决方案 BIP34)。BIP34 规定 Coinbase data 最高字节表示用于表示块高度的数据段的字节数,接下来的字节以⼩端法表示具体的块高度,创世块的高度为 0。

例如:2013-12-28 BTC 的一个块的 Coinbase 解析中 coinbase data 为 0x03443b04...,则块高度用 16 进制表示为 0x043b44,十进制为 277316;

  • extra nonce 作为中间字段,将会在后续提及的 Extra Nonce Solution 详细说明作用;
  • 上图 Coinbase data 中用以结尾的「/P2SH/」是 12 年 miner 进行投票支持 BTC 是采用 BIP16 还是 BIP17 的产物,现已弃用。(众所周知,BIP16 P2SH 获得了更多票数,被 BTC 采用)

在交易信息聚合完毕得到了 MerkleRoot 之后,接下来填充区块头。块头结构如下(其中 nBit 就是 PoW 小节提到的 current-target 的压缩版):

哥白尼开发者:挖矿难度精讲

区块头 80-byte,一共 6 个字段:

  1. 版本号,允许改变但不推荐
  2. 前一个块的 hash 值,不允许改变
  3. MerkleRoot 的 hash 值,用于存块信息里的交易的 Merkle Tree 的 root 节点的值,允许改变(改变 coinbase 中 input 中的值)
  4. 时间戳,允许基于 MTP11 进行调整改变
  5. nonce,用于 PoW 算法的随机值,允许改变
  6. nBit,PoW 算法结果必须小于这个数对应的 current_target 才能算块打包成功。这个值是在每一个块开始打包之前就确定了,不允许改变

块头中 80 bytes 任意一个值发生改变,PoW 的 Hash 结果就会发生改变。

3、挖矿难度及难度调整

理解了 PoW 运算以及运算结果可能受哪些个因素影响,接下来了解一下我们要满足的难度要求是什么。

挖矿难度的描述可以认为有三种形式,difficulty (难度值,浮点数),current_target (当前目标值,256-bit),nbits (32-bit);形式不同其实实质是表达的同一个难度要求,而且这个难度要求在每个块打包前就确定了。

difficulty 不写在区块中,而是以浮点数的形式展现,给人直观的感受难度程度。 difficulty = difficulty_1 / current_target;

difficulty_1 为常数:0x00000000ffff0000000000000000000000000000000000000000000000000000

创世块的 current_target = difficulty_1,所以创世块的 difficulty = 1.0。

nbits 就是区块头 nBits 字段的值,用长度为 32-bit 的数值表示 256-bit 的数值,是需要牺牲一定精度的 , 可以理解它为「压缩」后的 current_target。

在计算 current_target 时,我们先转换为二进制然后用公式(a)来计算 256-bit 的 current_target。(值得注意的是:current_target 是一个无符号 256-bit 的值,之所以设置一个 Sign 字段,是为了与 bitcoind 代码保持一致,保留符号位参考的是 IEEE 浮点型表示法,其实是无用的)

哥白尼开发者:挖矿难度精讲

This compact form is only used in bitcoin to encode unsigned 256-bit numbers which represent difficulty targets, thus there really is not a need for a sign bit, but it is implemented here to stay consistent with bitcoind.

计算 current_target 的公式为:

哥白尼开发者:挖矿难度精讲

例如 nBits = 0x180192d4,

current_target = 0x192d4 * 2 ^ {(8 * (0x18 - 3))}

哥白尼开发者:挖矿难度精讲

(最高 16 位为零)

相较于创世块,current_target 减小了大约 1/(236) 倍,difficulty 增加了大约 7184404942701 倍。

哥白尼开发者:挖矿难度精讲

为了维持平均每十分钟生成一个块的频率,BTC 中 current_target 设计为了一个动态值,current_target 根据全网算力的改变而做一些相应的调整,这就是挖矿难度调整。

在 BTC 中,挖矿难度调整 idea 为:以 2016 个块(两周)为一个周期,每个周期根据前一个周期的实际耗时与理论耗时之间的差别进行调整。

新目标值 = 当前目标值 * 实际 2016 个区块出块时间 / 理论 2016 个区块出块时间 (2 周)。

方案具体逻辑是:

  1. 判断是否需要更新目标值 ( 2016 的整数倍),如果不是则继续使用最后一个区块的目标值
  2. 计算 2016 个块实际使用时长:如果用时低于半周,则按半周计算,防止难度增加 4 倍以上;如果用时高于 8 周,则按 8 周计算。防止难度降低到 4 倍以下。
  3. 实际使用时长乘以当前难度,再除以 2 周
  4. 如果超过最大难度限制,则按最大难度处理

计算过程,Go 代码如下 :

哥白尼开发者:挖矿难度精讲

4、BCH 难度调整

BCH 诞生于区块高度 478558,两条链都采用相同 PoW 共识算法(平均 10 分钟生成一个块),所以 miner 可以任意在 BTC 与 BCH 间切换,但由于通常 BCH 全网算力只占有 BTC 的 7% 左右,当 BCH 获利大于 BTC 的时候,大量原 BTC miner (尤其是大的矿场)会切入 BCH,一段时间后随着算力提升,难度值也会提升,miner 会纷纷离开切回 BTC,算力降低,难度居高不下将导致接下来出块十分困难。倘若继续沿用 BTC 的难度调整方案,BCH 将无法保证出块速率稳定在平均 10mins/block,事实上 BCH 的难度值调整算法已经先后经历了两种,第一种是紧急难度调整规则(EDA),目前使用的是难度调整规则(DAA)。

紧急难度调整规则(EDA)

EDA 是在沿用 BTC 难度调整算法的基础上,增加了一个 Emergency Difficulty Adjustment 处理方案,主要是针对于出块缓慢情况及时降低难度。算法具体逻辑是:对于高度为 2016 倍数的就拿到此高度前 2016 块的 blocktime,沿用 BTC 难度调整方案;对于高度非 2016 倍数的块,则计算生成前六块的块总共耗时是否超过 12h,如果超过则降低挖矿难度 20%。 具体实现代码如下:

哥白尼开发者:挖矿难度精讲
哥白尼开发者:挖矿难度精讲

难度调整规则(DAA)

然而在运行三个多月中,EDA 表现不尽如人意(非常差),由于其应对出块速率过高并没有做相应及时调整而依赖于 BTC 原有的 2016 块一次的调整机制,所以在面对算力波动时表现并不是很好。

哥白尼开发者:挖矿难度精讲

本人分别截取了三段「典型」数据,为了对上述观点进行说明:

  1. 2017.8.26 号的算力攻击,2017.8.27 号大算力切走之后出块困难,23 小时只出 13 个块,导致连续调整(降低)挖矿难度。

哥白尼开发者:挖矿难度精讲

  1. 2017.10.2 由于矿工趋利性导致 BCH 网络算力突然增加,仅仅 30mins 就出了 20 多区块。

哥白尼开发者:挖矿难度精讲

  1. 用一个更直观的数据,BTC 和 BCH 的起跑线都是一样的,都是 2017 年 8 月 1 日,高度同为 478,558,而截止到 17 年 11 月 12 日晚 BTC 挖到了 494,079 高度,而 BCH 挖到了 503,815 高度,多了将近 10000 个块。

哥白尼开发者:挖矿难度精讲

所以优化 BCH 的难度调整方案刻不容缓,在得到几大矿池的稳定算力支持后,17 年 11 月 13 日,BCH 再次升级,就是为了优化 EDA。BCH 开发团队(并非社区)收到几份 DAA,最终采用了 BTCABC 开发团队 Amaury Sechet 的 DAA 提案。 这份 proposal 的 ieda 可以用一句俗语来形容「魔高一尺,道高一尺,魔有天花板」,根据前一天的算力为基准从而预测需要设置多少工作量才能耗掉十分钟。 其实现逻辑如下:

  1. 新算法将在高度 504031 开始生效
  2. 假设需要得到 target_height 的目标难度
  3. (prevBlock - 1)至 (prevBlock - 1 - 2 )这三块的 ntime,排序,取 ntime 在中间的那块为 lastNode
  4. 取(prevBlock - 1 - 144)至 (prevBlock - 1 - 144 - 2)这三块的 ntime,排序,取 ntime 在中间的那块为 firstNode 备注:bch 的目标是 10 分钟产生一块,一天产生 144 块
  5. 根据最近的 144 个区块的链上累计工作量(ChainWork)可以推算出满足当前算力的所需工作量 work :

哥白尼开发者:挖矿难度精讲

  1. 再通过 work 值得出目标难度。

具体实现代码如下:

哥白尼开发者:挖矿难度精讲

计算新 Target 方法如下:

哥白尼开发者:挖矿难度精讲

总结下来,DAA 算法具有以下特性:

  • 基于前 144 个块的算力来逐块设置挖矿难度;
  • 算力按指数规律变化时,网络将快速调整难度,保证公平性;
  • 避免当前算力与目标难度的不匹配导致的反馈振荡。
  • 可以一定程度上减少 timestamp manipulation 等攻击的影响。

哥白尼开发者:挖矿难度精讲

DAA 应对算力攻击的效果如何呢?

以下是两个算力变化极端场景:算力陡增两倍,算力陡然减半。 poc 代码如下:

哥白尼开发者:挖矿难度精讲
哥白尼开发者:挖矿难度精讲
哥白尼开发者:挖矿难度精讲

5、extra nonce 解决方案

目前,BTC 挖矿难度设置到了 7184404942701.792( 0x17272d92 对应的 current_target 为 0x000000000000000000272d920000000000000000000000000000000000000000)。 也就是说随机选取的数满足小于 target 的概率是 1/(272),但是块头的 nonce 字段只有 4bytes,也就是 32 位,有可能 232 个随机数都试完来仍然找不到满足 target 的 result。所以允许块头内部分其他的字段改变,用来生成新的 result。允许改变的字段在第二小节块头部分已指明。

试想一下,如果频繁更改 Coinbase Data 里的 Extra Nonce,来改变块头的 Merkle Root 会怎么样?很明显效率会很低,所以实际挖矿中策略是:尽可能减少块头中 Version,TimeStamp,Merkle Root (绿色区域数据)值的改变,而「疯狂」遍历 Nonce (红色区域)的值用于 PoW;当遍历完没找到满足 target 的 result,再改变绿色区域的值,然后继续「疯狂」遍历 Nonce。如此往复直至找到满足 target 的 result 或者这一轮 PoW 竞赛中失败开始新一轮打包。

哥白尼开发者:挖矿难度精讲

举报

链闻 ChainNews 信息平台,诚邀读者共同监督,坚决杜绝各类代币发行、投资推荐及虚拟货币炒作信息。如您发现这篇文章含有敏感信息,请点击「举报」,我们会及时调查,并进行处理。

你可能感兴趣

    App

    链闻 App

    扫码下载

    公众号 小程序