北京时间 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删除。