[学习笔记] 比特币的挖矿和共识

在介绍完比特币交易和区块的相关内容后,这篇文章记录网络节点是如何协同工作共同记账的。

比特币的网络

互联网中最常见的网络模型是“客户端-服务器”结构,中心服务器提供特定服务,响应客户端请求。

当你打开浏览器上网,打开 Outlook 收发邮件,都是在这样的模型下与中心服务器通信。一个更简单的例子是,当你使用手机时,需要电信运营商为你提供语音通信和数据流量服务。如果中心服务器掉线,你就无法使用。

另一种常见的结构是对等网络(P2P,Peer-to-Peer)。

同一 P2P 网络里的每台设备都是彼此对等的,所有节点共同提供网络服务,不存在任何特殊节点。P2P 网络没有中心服务器和中心化的服务,也没有层级结构,节点之间交互运作,协同处理,每个节点在对外提供服务的同时也使用网络中其他节点提供的服务

当你使用 BT(BitTorrent)下载资源的时候,就是在 P2P 网络模型下与其他节点通信,你从别的节点下载数据,同时也给别的节点提供下载服务,你可以随时离开而不会对整个网络带来影响。

比特币的网络是 P2P 结构,网络中的节点(运行了比特币软件的计算机)彼此对等,共同维护比特币的总账本。

节点能互相“发现”彼此,并建立连接,同步数据,就像你用 BT 下载资源一样,你从别的节点下载数据,同步自己的本地状态,同时也在传播数据,给别的节点提供同步服务。

Imgur

节点在收到新区块或新交易后,都会先验证数据的合法性,只有验证通过才会保存到本地,并继续传播数据给其他节点。

如果一个节点保存了所有比特币区块的数据(比特币所有交易的帐本),我们称这是一个全节点(Full Node)。

挖矿

网络不断产生新交易,需要不断创建新区块“整理”这些交易。

既然比特币网络中的所有节点都是对等的,那要如何一起维护这个总账本呢?谁来记账(创建区块)?凭什么让这个节点来记账?

比特币网络使用工作量证明(PoW,Proof-of-Work)的方式决定记账权。

网络中的任何全节点,都可以试图创建区块,但区块只有在至少满足下列条件时,才是合法的,才会被其他节点认可和接受。

  • 区块中包含的交易都是合法的
  • 区块哈希要小于等于一个目标值

要满足第一个条件很简单,节点只要将每笔交易都验证一遍,丢弃掉不合法的交易即可。但要满足第二个条件很难,先说说这个目标值。

nBits

区块头中的nBits字段,标识了当前区块的哈希,要小于等于的目标值(target)。

注意,区块头 SHA256 的结果有 256 位,而nBits是 4 字节只有 32 位,用nBits计算目标值的规则如下,以 区块277316 为例,nBits0x1903a30c(十进制为419668748),区块哈希为

0x0000000000000001b6b9a13b095e96db41c4a928b97ef2d944a9b31b2cc7bdc4

目标值被存成“系数/指数”格式,高 2 位十六进制数(1 字节)是幂(exponent),接下来的 6 位(3 字节)是系数(coefficient),计算公式为

$$target = coefficient \times 256^{exponent–3}$$

所以0x19是幂,0x03a30c是系数,这个区块的哈希要小于等于的目标值为

$$target = 238348 * 256^{22} = 22829202948393929850749706076701368331072452018388575715328$$

用十六进制表示,为0x0000000000000003a30c00000000000000000000000000000000000000000000

竞争记账权

让我们看看全节点是怎么试图创建新区块的。假设

  • 最新的比特币区块高度为#100
  • 在高度#100的区块构建好后,网络中又出现了 1000 笔新交易,这些交易被暂时搁在全节点的内存池中,等待被打包进区块(记入账本)

全节点开始试图构建一个新区块:

  1. 计算出这 1000 笔交易的总手续费fee,计算出当前区块需要新发行的比特币数量new_coin
  2. 创建一笔 Coinbase 交易,输出到自己的地址,金额为(new_coin + fee)
  3. 用 Merkle 树结构归纳这 1001 笔交易,得到merkle_root
  4. 根据当前nBits的值,计算出区块哈希要满足的目标值target
  5. 构建一个区块头,共 6 个字段
字段
nVersion 确定值
hashPrevBlock 区块#100的哈希
hashMerkleRoot 计算出的 merkle_root
nTime 当前 Unix 时间戳
nBits 确定值
nNonce 0
  1. 对 [5] 构造出的区块头,做两次 SHA256 运算,得到区块哈希hash

如果hash <= target则新区块创建成功:

  • 节点保存这个新区块为#101,并向网络广播区块数据,然后试图构建区块#102
  • 其他节点收到这个新区块后,验证是合法的区块,立即放弃自己正在进行的工作(试图构建区块#101),保存收到的区块为#101并继续向网络广播,根据区块#101中已经包含的交易更新自己的内存池,同时基于区块#101开始试图构建区块#102

如果hash > target则新区块创建失败,调整区块头的内容重新计算,直到找到一个合法的区块哈希:

  • nNonce字段值加 1
  • 微调nTime
  • 调整区块包含的交易,改变merkle_root
  • 调整 Coinbase 数据,改变merkle_root(搜索关键字随机值升位方案或 extra nonce solution 了解更多)

节点在收到新区块后,可以直接对区块头做哈希运算,验证这个新区块是否符合要求,同时还会验证新区块中包含的所有交易,是否都是合法的交易。

简单来说,对一个全节点有两种情况:

  • 自己最快创建出新区块并通知全网,获得奖励(Coinbase 和区块中所有交易的交易费)
  • 在找到一个合法的区块前,收到了别人发过来的、合法的新区块,任务失败,没有任何收益

创建区块可以获得奖励,不断驱使着节点之间互相竞争记账权。

算力

计算区块哈希,会用到 SHA256 哈希函数,其计算结果被认为是一致的随机序列,也就是说 SHA256 计算结果中某一位的值,为二进制01的概率,是相同的。

SHA256 的计算结果是 256 位二进制,如果抛一枚硬币,结果是正面记为0,是反面记为1

你就可以把计算 1 区块哈希,想象成抛 256 硬币。

目标值0x0000000000000003a30c00000000000000000000000000000000000000000000的前 60 位都是0,也就是说,如果要小于等于这个目标值,区块哈希至少前 60 位都是0

1 次尝试需要抛 256 下硬币,为了让某次尝试前 60 下的结果都是正面,你平均需要尝试 $2^{60}$

如果一秒钟你能计算十亿($10^9$)次 SHA256,排除运气因素,你需要大概1152921504秒才能计算出一个合法区块,约 36.5 年,而比特币网络中,平均每十分钟,就会有一个节点计算出新区块。

我们把单位时间计算哈希的次数,称为算力

为了能在竞争中获胜,需要使用专门计算 SHA256 的定制设备(矿机)。

在写这篇文章的时候(2019年1月11日),最新的蚂蚁矿机 S15,算力 28T(每秒计算 $28 \times 10^{12}$ 次哈希),功耗 1600 瓦,售价约 10000 元。

全网算力(所有节点的算力之和)为

比特币版本 全网算力
Bitcion(BTC) 42.19 Eh/s,每秒计算 $ 42.19 \times 10^{18}$ 次哈希
Bitcoin Cash(BCH) 1.25 Eh/s
Bitcoin SV(BSV) 1.04 Eh/s

矿池

将计算出一个合法区块平均要尝试的次数,定义为挖矿难度(Difficulty)。

$$1\ 难度 ≈ 2^{32}\ 次 = 4294967296\ 次 ≈ 4.295 \times 10^{9}\ 次 ≈ 4G\ 次运算$$

现在 Bitcoin(BTC)的全网难度为 5.62T。

使用 100 台 S15 独自挖矿,不考虑运气因素,计算出一个合法区块大约需要

$$\frac{5.62 \times 10^{12} \times 4.295 \times 10^9}{28 \times 10^{12} \times 100} \approx 8620679\ 秒 \approx 100\ 天$$

一度电按 0.4 元计算,你的成本约为

$$10000 \times 100 + 0.4 \times 1.6 \times 24 \times 100 \times 100 = 1153600\ 元$$

你的收益约为 12.5 Bitcoin(BTC),算力每 T 每天收益约为

$$\frac{12.5}{100 \times 28 \times 100} \approx 0.00004464\ BTC$$

Imgur

因此,在这样激烈的竞争环境下,个体矿工想要独自挖矿(Solo 挖矿)以抵消电力和矿机成本并获得稳定收益,几乎没有任何机会。

另外,为了保证平均十分钟的区块生产速度,比特币全网难度会随着全网算力的变化而变化,全网算力增加(计算的更快)则会提高难度(一般情况都是全网算力越来越多,难度越来越大)。

考虑到,挖矿也包含了不少运气成分,在刚才的例子中,你可能一个月就挖出了区块,也可能半年都无法出块。

为了减少收益的不确定性,降低个体风险,矿工们通过合作组成矿池,根据自己贡献算力的多少来分享矿池获得的区块奖励。

你可以把矿池看成一个“巨型矿工”,其拥有的算力由加入这个矿池挖矿的矿工提供。

矿池使用特定的网络协议,分割大型计算任务并分配给每个矿工,矿工完成每个小的计算任务,赚取“份额”(Share)。

当矿池中的某个矿工成功出块,矿池便获得收益。每日结算时,各矿工根据赚取“份额”的多少(一般正比于自己提供算力的大小),按比例分配奖励。

矿池的存在,在某种程度上降低了人们参与挖矿的门槛,不论你是土豪还是普通人,只需要购买矿机并按说明接入矿池即可。

你可以搜索相关文章,了解关于矿池的更多内容,这里不再赘述。下面这些工具很不错,推荐给你。

工作量证明

通过上面的描述,你会发现,只有不断投入新设备,让自己拥有更快的哈希计算速度,不断消耗能源(支付电费),才能在竞争中获胜,才能获得更多的收益,除此之外,别无他法。

并且这样的竞争毫无门槛,规则公开,任何人都可以参与,只要你愿意付出成本。

You pay, you play

为了让区块哈希满足要求,节点没有别的办法,只有不断尝试,失败后微调区块头数据重新计算,直到网络中的某个节点,最先构造出下一个合法区块。

当你成功构建出合法区块,全网络的人都知道,这是你不断尝试和计算的结果。

你无需提供任何其他证据来证明自己做了大量的计算投入了大量的成本,最快构建出下一个合法的区块,就是你工作量的最好证明

你也无需提供构建这个区块的详细过程,因为除了一次次尝试,没有其他方法。

比特币通过区块发行,试图创建一个新区块就像开采矿产资源一样,需要投入大量的成本和努力,节点之间互相竞争记账权的过程被形象的称为“挖矿”,运行全节点提供算力挖矿的人,被称为“矿工”。

一个要注意的点是,比特币软件会自动控制和调整nBits的值,以保证平均 10 分钟的区块生产速度,调整规则和计算规则固定在程序中,所有节点在同一时间都会计算出相同的目标值。如果你悄悄改了nBits的值(让目标更容易达到)然后创建一个“合法”的区块,同样无法通过其他节点的验证。

工作量证明的思想,在生活中处处可见。

你如何证明自己已经掌握了必要的专业知识,能熟练操作各种网络设备,可以胜任网络工程师的职位?提供一张 CCIE 证书就好。

常规分叉和共识

你注意到没有,只要区块哈希小于规定的目标值,这个新创建的区块就是合法的(不考虑其他限制)。

如果目标值是 100,只要区块哈希落在范围 [0, 99] 里,这个区块就是合法的。也就是说,下一个合法区块并不是唯一的。

数据在网络中传输时,会存在一定的延迟,如果不同的节点几乎同时发现了下一个合法区块,整个网络要如何应对?

  1. 目前,所有的节点状态一致,最新的区块都是星形
  1. 两个节点同时成功构建了下一个区块,并向网络中广播
  1. 随着数据的传播,区块链发生分叉,一部分节点会“跟随”白色三角形区块,在其上试图构建下一个区块,而另一部分节点会“跟随”桔色三角形区块
  1. 一段时间后,某个“跟随”白色三角形区块的节点,最先发现了下一个绿色菱形区块,向网络广播
  1. 所有节点都同步到最新的绿色菱形区块

区块链是连续的,你在计算下一个区块的时候,必须在区块头中记录当前区块的哈希。

“跟随”了某条链,就是基于这条链的最新区块,试图构建下一个区块延长这条链。

此时,所有的节点都能看到两条链,并且都会根据规则,“跟随”工作量积累最多的链(一般是最长的那条链)。

  • 所有的节点,都会“跟随”星形 > 白色三角形 > 绿色菱形这条链,并基于绿色菱形区块,计算下一个合法区块
  • 桔色三角形区块,被孤立成了孤块(Orphan),没有节点会继续延长它
  • 那些曾经“跟随”了孤块的节点会进行区块重组(re-org),并根据当前网络状态更新自己的内存池,迅速加入到下一个区块的记账权竞争中

节点会在短时间内消除分歧,重新达成共识。

Imgur

交易确认和共识攻击

当全节点收到一笔新交易时,会先放到自己的内存池中(等待打包进区块),并继续向其他节点广播,此时,这笔交易是“未确认”的。

“确认”的意思是,这笔交易已经被打包进区块,写入了比特币的全球总帐本。

通过验证的交易会按照优先级顺序打包。

假设当前(交易到达内存池时)的最新区块高度为#100,一段时间后,区块#102中包含了这笔交易,此时,我们说这笔交易有了“一个确认”。此后,随着区块链的不断延长,这笔交易上的确认数也随之增加。

确认数越多,表示这笔交易被改写的可能性越低

区块是依次相连的,如果你想改写这笔交易(例如从区块中剔除,相当于你没有支付)。因为“最长链”共识的存在,你必须能计算的足够快,从区块#102开始,重新计算一条链超过当前工作量积累最多的那条链,才会让其他节点“跟随”,才能达到改写区块链目的。交易上的确认数越多,改写需要的算力也就越大(成本越高),否则你无法计算的足够快,让自己的链成为最长链。

上面描述的场景被称为共识攻击,也常被称为“51% 算力攻击”,如果攻击者拥有大量算力,这种尝试就有可能成功(算力超过全网 51% 时,攻击尝试几乎一定会成功)。

值得注意的是,共识攻击并不能让攻击者直接从某个地址上偷取比特币或不签名就支付比特币,也不会影响加密算法的安全性,它只会影响区块链未来的共识(阻止特定地址的交易,拒绝服务攻击)或过去几个区块的共识(改写过去不久的区块,实现双重支付)

这也是为什么大额比特币转账,需要等待多个确认的原因。

Alice 去 Bob 的店里用比特币购买咖啡,产生了一笔当前未确认的交易。

Bob 愿意直接把咖啡给 Alice 而不等待交易确认,因为“小额转账遭遇双重支付”的风险和顾客购物的良好体验(Alice 能立即拿到咖啡)比起来,显得微不足道。

请注意,Bitcoin Cash(BCH)和 Bitcoin SV(BSV)是小额支付零确认安全的(必须有一定的算力才可能实现双重支付)。

Bitcoin(BTC)因为 BIP-125 引入了 RBF(Replace by Fee)特性,已经不再适用于这个场景。如果 Bob 不等交易确认的话,Alice 可以先拿走咖啡再通过 RBF 替换掉之前付给 Bob 的那笔未确认交易,实现双重支付(不需要任何算力)。

Median Time-Past

比特币的挖矿节点彼此独立,不同节点有着不同的时间精度。

区块头中的时间戳是矿工决定的,考虑到网络传输延时,共识规则允许一定的误差,以解决分散节点之间的时间精度问题,但这可能会诱惑矿工说谎,通过让区块包含仍未释放时间锁定的交易,来赚取额外的交易费。

BIP-113 引入了“过去时间中值”(MTP,Median Time-Past)的概念,通过计算最后 11 个区块的时间戳的中值,作为当前的共识时间,代码见这里

enum { nMedianTimeSpan = 11 };

int64_t GetMedianTimePast() const {
int64_t pmedian[nMedianTimeSpan];
int64_t *pbegin = &pmedian[nMedianTimeSpan];
int64_t *pend = &pmedian[nMedianTimeSpan];

const CBlockIndex *pindex = this;
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev) {
*(--pbegin) = pindex->GetBlockTime();
}

std::sort(pbegin, pend);
return pbegin[(pend - pbegin) / 2];
}

网络中所有与时间有关的计算都使用共识时间。

比特币约 10 分钟产生一个新区块,所以共识时间会比墙上时间(现实世界的时间)慢约一小时。

Fee Sniping

这是一种被称为费用狙击(Fee Sniping)的潜在攻击方式。

矿工通过选择本应在未来区块中打包的那些交易费更高的交易,试图重写过去的区块,以实现收益的最大化。

Fee-sniping is a theoretical attack scenario, where miners attempting to rewrite past blocks “snipe” higher-fee transactions from future blocks to maximize their profitability.

假设当前的区块高度是#100,诚实的矿工会试图构建区块#101,延长区块链。某些矿工为了更高的收益,会(在收到区块#100后)重新计算区块#100,选择那些交易费更高(satoshi/KB)的交易打包,这些交易可以是区块#100中的交易,也可以是当前内存池中的交易。

如果重新计算的区块#100在之后被网络接受(原来的区块#100成了孤块),则费用狙击成功,不过这并不容易实现(需要一定的算力和运气)。

在所有比特币全部发行完前,因为 Coinbase 奖励的存在,这么干显得不那么有利可图,但这种潜在风险可能在未来出现。

为了避免这种情况,可以在创建交易时使用nLocktime时间锁,限制这笔交易只能在当前区块高度之后打包。

  • 当前区块高度为 $H$
  • 创建一笔交易
    • 设置nSequence的值为0xfffffffe,开启nLocktime时间锁
    • 设置nLocktime的值为 $(H + 1)$

这样的时间锁对交易的打包时间没有任何影响。

  • 正常情况下,这笔交易被打包,最快也要等到下一个区块
  • 如果发生费用狙击,重写当前块,这笔交易也不会被选中,因为交易上的nLocktime时间锁没有释放

总结

  • 比特币通过 Coinbase 交易发行,在所有比特币发行完之前,每个新区块都会包含一笔 Coinbase 交易
  • 就像采矿一样,我们把计算新区块以获得比特币奖励的过程,称为比特币“挖矿”
  • 比特币使用工作量证明,确定某个时刻的记账权
  • 为了能在竞争中获胜,获得收益,挖矿节点必须持续投入成本
  • 节点之间不需要信任保证,它们遵循同样的规则,有统一的行为,节点会验证收到的数据,符合规则就会认可和接受
  • 矿工会打包未确认的交易,延长区块链,维护比特币网络,成功构建新区块的矿工,可以获得 Coinbase 奖励和区块中所有交易的交易费
  • 算力是比特币网络健康持续运行的保证

有算力的全节点正是在这样的共识下,互相竞争的同时又一起协作,共同维护比特币的区块链,让网络健康持续的运行。

参考