写点什么

深入剖析实现比特币减半的十五行代码

  • 2020-05-15
  • 本文字数:3799 字

    阅读完需:约 12 分钟

深入剖析实现比特币减半的十五行代码

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


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


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

区块补贴和奖励减半

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


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


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

什么是哈希值?

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


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


SHA256(    $previousBlockHash,    $newTransactionsToBeIncluded,    $magicNumber);
复制代码


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


矿工们所做的就是一直不断地猜测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
复制代码


nSubsidy >>= 3 表示将位右移 3 位。这就得到了:


100101010000001011111001000000
复制代码


当你将该二进制值转换为其十进制表示形式时,会得到 625,000,000。换句话说,这就是 625,000,000 个基本单位或 6.25 个比特币。


在下一次减半发生时,会随着右移一位让上面的二进制数值末尾的 0 又减少一个,从而有效地再次将奖励金额减半。


因为初始值 50 亿总共只有 33 比特位,我们将在第 33 比特减半后让区块奖励变为 0,这大约会发生在 2140 年。


且这个假设的前提是,到那个时候,交易所支付的采矿奖励应该还足以补偿矿工的电力消耗和硬件成本。

结论

比特币的货币供应量通过硬编码进行了限制,每个人都可以看到和审查这段代码。


矿工数量庞大,即使不是百万数量级,至少也是数以千计,能让这么多矿工能以和谐的方式一起工作的想法真的令人感到不可思议。如果你曾经尝试设置过任意一个包含 5 个节点的集群,你就会明白要在多个节点间达成共识或多数仲裁是多么困难的一件事情,因为你需要像比特币网络一样考虑到:


  • 如何让所有节点达成共识?

  • 如何防止结果发生偏差?

  • 如何防止网络连接中断造成的影响?

  • 如何防止数据损失造成的影响?

  • 如果一个节点在执行写操作的过程中崩溃了怎么办?


而在比特币网络上这一切都正常运转,而且是在硬件、网速、节点版本和软件均为未知的条件下运行的——这都是因为设计时所包含的数学原理和社会共识,让每个人在该网络上都遵循相同的规则。


这真是令人着迷的技术啊。


作者介绍:


Mattias Geniar,独立开发人员,Linux 系统管理员和通用问题解决者。


英文原文:


Dissecting the code responsible for the Bitcoin halving


2020-05-15 17:5713699

评论

发布
暂无评论
发现更多内容

关于技术团队的考核(24/100)

hackstoic

技术管理

Ant Design Landing

云原生

SaaS React Ant Design Landing page

「架构实战营」模块九《十万级到亿万级 IM 架构实战》作业

DaiChen

作业 「架构实战营」 模块九

模块六作业-拆分电商系统为微服务

浪飞

一文带你了解 Python 中的装饰器

踏雪痕

Python 装饰器 3月程序媛福利 3月月更

Java中的序列化安全漏洞梳理

陈德伟

Java 安全 编程语言、 序列化机制

底什么是伪静态?为什么要做伪静态?

源字节1号

网站建设 SEO伪静态

【架构实战营】毕业总结

wgl

架构实战营

电商系统微服务拆分

tom

JS中的函数参数默认值是如何写的?

Changing Lin

3月月更

另一个 effective go 中文版

黑客不够黑

架构训练营 模块六

Geek_16d2b8

架构训练营 模块六

架构训练营第一期作业

Geek_bc9c8d

【架构实战营】毕业设计项目

wgl

架构实战营

一文概述:云端常见的攻防及实践

穿过生命散发芬芳

3月月更

重学架构之拆分电商系统为微服务

陈华英

架构实战营

css

wudaxue

模块六作业

blazar

「架构实战营」

云原生-模块十二

hunk

模块六作业

Leo

架构实战营

毕业总结

黄秀明

「架构实战营」

电商系统微服务拆分实践

IT屠狗辈

微服务 架构实战营 电商系统架构 架构拆分

「架构实战营」模块六 电商微服务框架设计

hxb

「架构实战营」

【模块六】拆分电商系统为微服务

yhjhero

#架构训练营

DDD实战(6):战略设计之技术决策

深清秋

DDD 软件架构 生鲜电商系统 3月月更

浏览器原理

wudaxue

Vue

wudaxue

电商系统拆分为微服务

凌波微步

「架构实战营」

模块六作业

Geek_ec866b

架构训练营

模块九作业-设计电商秒杀系统

CH

架构实战营

「架构实战营」毕业总结

DaiChen

「架构实战营」

深入剖析实现比特币减半的十五行代码_语言 & 开发_Mattias Geniar_InfoQ精选文章