剖析什么是比特币减半及减半的工作原理。

北京时间 5 月 12 日 3 时 23 分左右,比特币在区块高度 630000 处完成诞生以来第三次减半,比特币区块奖励由 12.5 枚 BTC 减至 6.25 枚 BTC,剩余待开采比特币数量仅剩约 262 万。

那么,什么是比特币减半,该事件背后的代码工作原理是什么呢?

让我们一起深入了解其中有趣的细节吧。

区块补贴和奖励减半

先来简单回顾一下一个基础知识点,所谓“矿工”,是指此时此刻分布于世界各地,正在运行硬件和软件计算下一个比特币区块哈希值的人。

如果矿工们及时解决了比特币区块链网络中的数学难题,他们就可以获得区块奖励。

以上描述里出现了不少时髦的流行语,是不是?这就来让我们对它们进行逐个解释。

什么是哈希值?

比特币整个采矿的概念设计得十分巧妙。实际上,采矿并不是一个冥思苦想解题的过程,它更多的是一种尝试猜出一个神奇数字的蛮力尝试。

比特币网络几乎在所有地方都采用了 SHA256 哈希算法。世界各地的矿工都在尝试运行的采矿功能,可以用下面这个简化版本的函数来表示:

SHA256(    $previousBlockHash,    $newTransactionsToBeIncluded,    $magicNumber);

其中 $magicNumber 也被称为随机数(nonce),在密码学中是指一个只被使用一次的数字。通过在新区块的哈希计算中包含以前区块的哈希值,实际上就形成了一个链式结构,将每个以前的区块逐个链接,一直链接到新的区块为止。因此,区块链本质上就是个高端版本的链表。

矿工们所做的就是一直不断地猜测 magicNumber 的值。他们一遍又一遍地运行相同的计算,用递增(或随机)的穷举法来试 magicNumber 的有效值。通过这样的计算方式,矿工每次都会改变上述函数的哈希值结果。

那他们什么时候算“赢”呢?

一旦他们找到一个开头 0 的数量足够多的哈希值

就是这样:开头有足够多的 0

这个答案就是如此简单而粗暴,看起来并不高大上。而采矿计算的难度就取决于哈希值的开头需要多少个 0。当第一个区块被开采出来时,它的哈希值开头只有 8 个 0:

00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048

而在编写本文时,该区块链计算出来的第 629828 个区块的哈希值,它开头有 19 个 0 :

0000000000000000000133e7bffe43530e508183ec48a89bad23a370692b16e8

而开头需要的 0 数量越多,就越难猜出这个随机数

这里多说一句,比特币这个算法的精巧之处在于,它每隔 2016 个区块会自动重新计算其难度目标(即开头需要多少个 0),但本文就不再为此赘述了。

采矿奖励

如果你猜对了这个随机数,你就会得到奖励。这种奖励以新铸造出来的比特币的形式发放。

本质上,这些比特币是凭空产生的。只不过它既不便宜也不是信手拈来。比特币网络中采矿是要花费大量计算能力和电量的。付出这些辛勤劳动后,你得到了比特币。为了维护巩固这个比特币网络,你作为一名矿工会得到理所应当的奖励。

这些新铸造的比特币是在所谓的*生成交易(Coinbase Transaction)*中创建的。这是一种独特的交易,它包括在每个区块之中,其作用是支付一定数量的比特币奖励给猜中了正确哈希值的矿工。

是的,热门的“Coinbase 加密货币交换所”的名字就来源于这个词汇,它原本是指每个区块中对矿工提供奖励的特殊交易(生成交易)的引用。

每一个被正确计算出来的区块,会自动计算出矿工奖励,并随着比特币网络的发展而对奖励金额进行自动调整。它的工作原理也设计得十分巧妙,请让我来带你了解一下背后的代码。

GetBlockSubsidy() 函数

矿工奖励减半的魔法发生在函数 GetBlockSubsidy() 中,该函数包含在源代码的 src/validt.cpp 文件中。

CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams){    int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;    // Force block reward to zero when right shift is undefined.

if (halvings >= 64)plain        return 0;

CAmount nSubsidy = 50 * COIN;

// Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years.nSubsidy >>= halvings;return nSubsidy;}

那么,这个函数包含什么功能?让我们来剖析一下代码。虽然我已经有一段时间没碰 C 语言了,但幸运的是,这段代码相当容易读懂。

已经发生过多少次比特币减半了?

让我们从顶部开始看:

int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;

上面最后一个共识参数 consensusParams.nSubsidyHalvingInterval,在 src/chainparms.cpp 文件中被定义为共识规则的一部分。这些是比特币网络上每个人都必须遵守的一套公共规则。

这里需要说明的是,对这些共识规则的任何更改都将创建一个“硬分叉(Hard fork)”,即一组不再遵循原始区块链规则的新规则。

consensus.nSubsidyHalvingInterval = 210000;

这个常量表示,每产生 210,000 个区块,就会出现一次比特币减半。其中变量 nHeight 指的是当前的区块高度(Height)。或者换句话说,已经开采出来的区块的数量。

而 5 月 12 日比特币开采数量就达到 630,000 个区块了。所以此时,这个等式就变为:

int halvings = 630000 / 210000;

这将使减半次数值变为 3。这也是比特币网络第三次将其区块奖励减半。

但是如果结果是一个浮点值,显然这个结果并不符合该变量所声明的 int 类型,那会发生什么事情呢?比如:

int halvings = 620000 / 210000;

这将得到 2.952380952 的计算结果。但是,由于变量被定义为整数类型,因此会通过直接抹去小数点后面的值,而取整为该结果范围内最小的整数值。所以,这时的减半次数是 2。

区块奖励结束

让我们来看看这个代码片段:

// Force block reward to zero when right shift is undefined.if (halvings >= 64)    return 0;

因为 nSubsidy 是个 64 位的有符号整数,在执行右移操作时可能出现未定义行为,这意味着 x >> 65 操作会变成 x >> 1,就会导致数值环回,所以这里需要保留对减半次数大于等于 64 的检查。这对于上面所贴的后续代码很重要。

最初的比特币核心代码并没有包含这个 bug 的修复,直到 2014 年才合并了这个 pull request 。

这部分经常被误解为“将会有 64 次比特币减半”。这并不正确。其实只会有 33 次比特币减半,后面我们会看到关于这一点的解释。

你能得到多少比特币作为奖励?

“减半”一词其实指的是对矿工可以获得的比特币数量进行限制。每采出 210,000 个区块,这个奖励金额就会减为一半。

CAmount nSubsidy = 50 * COIN;nSubsidy >>= halvings;return nSubsidy;

一开始,在 10 多年前,在网络上每开采出一个区块,就会奖励矿工 50 个比特币。

然后,在 210,000 个区块被开采之后,这个奖励金额被减半为 25 个比特币。又采出 210,000 个区块之后,变成了 12.5 个比特币。这就是截止到这次减半之前的情况。

而这次减半之后,比特币采矿奖励又会被减为一半,也即是说矿工只能得到 6.25 个比特币的奖励。之后再采出 210,000 个区块,奖励就会变成 3.125。以此类推。

这段代码中有一个巧妙的位运算操作,我想在这里强调一下:

nSubsidy >>= halvings;

我打赌你并不是每天都能在自己的代码中看到这样的 >>= 运算符。

让我们为每个变量填上实际的值,重写代码如下:

CAmount nSubsidy = 50 * 100000000;nSubsidy >>= 3;return nSubsidy;

区块奖励有一个固定的初始值 50。而按照 src/amount.h 文件中的定义,每枚比特币又可分为 1 亿个更小的基本单位(Satoshi,即“聪”)。如果想要说得更准确的话,矿工不会得到 1 整个“比特币”作为奖励,而是将 1 个比特币均分为 1 亿份,然后得到“1 亿”份这样的基本单位作为奖励。

而这样 1 亿份基本单位加在一起等于 1 个比特币。

这给出了 nSubsidy 的初始值为 50 亿基本单位。如果用二进制来表示,可以得到如下数字:

100101010000001011111001000000000

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java