比特币--挖矿与共识

挖矿奖励

挖矿的奖励分为两种类型:

  • 创建区块的Coinbase奖励
  • 该区块包含的所有交易的手续费

其中创建区块的奖励210,000个块后减半,因为大约每10分钟产生一个区块,所以大约每四年减半。开始时2009年1月,每创建一个区块奖励50个比特币,然后到2012年11月减半为25个比特币,再到2016年某月减半为12.5个比特币,直到大约2140年,届时所有的比特币20,999,999.98个全部发行完毕,随后矿工挖出区块的奖励将全部为交易的手续费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 计算比特币总量的python脚本

# 初始的块奖励为50BTC
start_block_reward = 50
# 以10分钟为一个区块的间隔,210000个块共约4年时间
reward_interval = 210000

# 累加每块的比特币数量,每210000块减半
# 当减半达到小于0,即小于比特币最小单位聪时,跳出循环。
def max_money():
# 50 BTC = 50 0000 0000 Satoshis
current_reward = 50 * 10**8 #50比特币的聪单位表示
total = 0
while current_reward > 0:
total += reward_interval * current_reward
current_reward /= 2
return total

print "Total BTC to ever be created:", max_money(), "Satoshis"

挖矿过程

保存有用户私钥的钱包软件通过收集UTXO、提供正确的解锁脚本,构造支付给接受者的输出锁定脚本这一系列的方式来创建交易。产生的交易会被发送到临近的比特币网络中的矿工节点,从而使得该笔交易可以在比特币网络中传播。然后矿工会进行以下步骤来进行所谓的“挖矿”工作:

  1. 每个矿工节点依据综合标准对每个交易进行独立验证
  2. 每个矿工节点组装区块,通过工作量证明算法的验证,即找到合适hash,生成新有效区块并广播
  3. 每个矿工节点收到的新有效区块进行校验并组装进本地区块链
  4. 每个矿工节点对区块链独立选择,选择工作量累计最大的区块链

1.交易的独立验证

节点在收到交易后,每一个节点都会在全网广播前对这些交易进行校验,并以接受时的顺序将这些有效的交易放进交易池中。

每一个节点在校验每一笔交易时,都需要对照一个长长的标准列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
▷交易的语法和数据结构必须正确。

▷输入与输出列表都不能为空。

▷交易的字节大小是小于MAX_BLOCK_SIZE的。

▷每一个输出值,以及总量,必须在规定值的范围内 (小于2,100万个币,大于0)。

▷没有哈希等于0,N等于-1的输入(coinbase交易不应当被中继)。

▷nLockTime是小于或等于INT_MAX的。

▷交易的字节大小是大于或等于100的。

▷交易中的签名数量应小于签名操作数量上限。

▷解锁脚本(scriptSig)只能够将数字压入栈中,并且锁定脚本(scriptPubkey)必须要符合isStandard的格式 (该格式将会拒绝非标准交易,标准格式如P2PKH,P2SH等)。

▷池中或位于主分支区块中的一个匹配交易必须是存在的,即输入中引用的父交易是存在的。

▷对于每一个输入,如果引用的输出存在于池中任何的交易,该交易将被拒绝,即该输入未被打包进区块上链,没确认的父交易。

▷对于每一个输入,在主分支和交易池中寻找引用的输出交易。如果输出交易缺少任何一个输入,该交易将成为一个孤立的交易。如果与其匹配的交易还没有出现在池中,那么将被加入到孤立交易池中。

▷对于每一个输入,如果引用的输出交易是一个coinbase输出,该输入必须至少获得COINBASE_MATURITY (100)个确认。

▷对于每一个输入,引用的输出是必须存在的,并且没有被花费。

▷使用引用的输出交易获得输入值,并检查每一个输入值和总值是否在规定值的范围内 (小于2100万个币,大于0)。

▷如果输入值的总和小于输出值的总和,交易将被中止。

▷如果交易费用太低以至于无法进入一个空的区块,交易将被拒绝。

▷每一个输入的解锁脚本必须依据相应输出的锁定脚本来验证。

这些条件能够在比特币标准客户端下的AcceptToMemoryPool、CheckTransaction和CheckInputs函数中获得更详细的阐述。请注意,这些条件会随着时间发生变化,为了处理新型拒绝服务攻击,有时候也为交易类型多样化而放宽规则。

2.整合交易至区块

按照区块的结构,将交易池中的未确认的交易组装进区块体,区块体中的交易有两种类型

  • 创币交易:第一条交易,矿工构造来支付给自己的奖励和手续费
  • 交易池中未确认的交易,此处选取交易池中未确认的交易时,会根据交易块龄和交易费等按优先级选择。

创币交易

区块中的第一笔交易是笔特殊交易,称为创币交易或者coinbase交易。这个交易是当前矿工节点构造并用来奖励自己所做的贡献的。如:当前矿工节点会创建“向自己的地址支付25.09094928个比特币”这样一个交易,把生成交易的奖励发送到自己的钱包。其中包括coinbase奖励(25个全新的比特币)和区块中全部交易矿工费的总和。

与常规交易不同,创币交易没有输入,不消耗UTXO,仅仅用来创建新的比特币。它只包含一个被称作coinbase的输入,有一个输出,支付到这个矿工的比特币地址。

  1. 计算交易费:已添加到区块交易的输入和输出分别进行加总,然后用输入总额减去输出总额得到矿工费总额。
  2. 计算奖励额:基于区块高度,以50开始,每210,000个区块减半一次。比特币核心客户端算法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Function GetBlockValue, Bitcoin Core Client, main.cpp, line 1305

int64_t GetBlockValue(int nHeight, int64_t nFees)
{
//变量nSubsidy表示初始奖励额,值为COIN常量(100,000,000聪)与50的乘积,也就是说初始奖励额为50亿聪。
int64_t nSubsidy = 50 * COIN;

//紧接着,这个函数用当前区块高度除以减半间隔(SubsidyHalvingInterval函数)得到减半次数(变量halvings)。
//每210,000个区块为一个减半间隔,如区块高度为277316,则减半次数为1。
//变量halvings最大值64,如果超出这个值,代码算得的奖励额为0,整个函数将只返回矿工费总额,作为奖励总额。
int halvings = nHeight / Params().SubsidyHalvingInterval();

// 如果右移的次数未定义,区块奖励强制为零
if (halvings >= 64)
return nFees;

// Subsidy每210,000个区块减半一次,大概每4年发生一次
// 使用二进制右移操作将奖励额(变量nSubsidy)右移一位(等同与除以2),每一轮减半右移一次。
// 之所以采用二进制右移操作,是因为相比于整数或浮点数除法,右移操作的效率更高。
nSubsidy >>= halvings;

// 最后,将coinbase奖励额(变量nSubsidy)与矿工费(nFee)总额求和,并返回这个值。
return nSubsidy + nFees;
}

创币交易输入结构

尺寸 字段 说明
32个字节 交易hash 不引用任何一个交易,值全部为0
4个字节 UTXO索引 值全部为1
1–9个字节(可变整数) Coinbase数据长度 coinbase数据长度
变长 Coinbase数据 在v2版本的区块中,除了需要以区块高度开始外,其他数据可以任意填写,用于extra nonce和挖矿标签
4个字节 序列号 值全部为1,0xFFFFFFFF

Coinbase数据

创币交易输入结构不包含“解锁脚本“(又称作 scriptSig)字段,这个字段被coinbase数据替代,长度最小2字节,最大100字节。除了开始的几个字节外,矿工可以任意使用coinbase的其他部分,随意填充任何数据。

以创世块为例,中本聪在coinbase中填入了这样的数据“The Times 03/Jan/ 2009 Chancellor on brink of second bailout for banks“(泰晤士报 2009年1月3日 财政大臣将再次对银行施以援手),表示对日期的证明,同时也表达了对银行系统的不信任。

coinbase前几个字节也曾是可以任意填写的,不过在后来的第34号比特币改进提议(BIP34)中规定了版本2的区块。

以区块277,316为例,Coinbase数据这个字段的十六进制值为03443b0403858402062f503253482f。下面让我们来解码这段数据。

  1. 第一个字节是03,脚本执行引擎执行这个指令将后面3个字节压入脚本栈
  2. 紧接着的3个字节——0x443b04,是以小端格式(最低有效字节在先)编码的区块高度,翻转字节序得到0x043b44,表示为十进制是277,316。
  3. 紧接着的几个十六进制数(03858402062)用于编码extra nonce,或者一个随机值,从而求解一个适当的工作量证明。
  4. coinbase数据结尾部分(2f503253482f)是ASCII编码字符 “ /P2SH/“,表示挖出这个区块的挖矿节点支持BIP0016所定义的pay-to-script-hash(P2SH)改进方案。在P2SH功能引入到比特币的时候,曾经有过一场对P2SH不同实现方式的投票,候选者是BIP0016和BIP0017。支持BIP0016的矿工将/P2SH/放入coinbase数据中,支持BIP0017的矿工将 p2sh/CHV放入他们的coinbase数据中。最后,BIP0016在选举中胜出,直到现在依然有很多矿工在他们的coinbase中填入/P2SH/以表示支持这个功能。

构造区块头

区块头需要填充下面6个字段:
大小 | 字段 | 描述
—|—|—
4字节 | 版本 | 版本号,用于跟踪软件/协议的更新,当前区块结构版本为2。
32字节 | 父区块哈希值 | 引用区块链中父区块的哈希值
32字节 | Merkle根 | 该区块中交易的merkle树根的哈希值
4字节 | 时间戳 | 该区块产生的近似时间(精确到秒的Unix时间戳)
4字节 | 难度目标 | 该区块工作量证明算法的难度目标
4字节 | Nonce | 能生成目标难度hash的幸运值

  • 版本

当前区块结构版本为2,以小段格式编码值为0x20000000。

  • 父区块哈希

引用区块链中父区块的哈希值

  • 生成merkle根值

为了向区块头填充merkle根字段,要将全部的交易组成一个merkle树。创币交易作为区块中的首个交易,后将余下的交易添至其后,如果最后总共为奇数笔交易,需复制最后一笔交易构成偶数笔,方便构造merkle树。计算每笔交易的哈希值,将这些交易逐层地,成对地组合,直到最后合并为一个根节点哈希值。

  • 时间戳

以Unix纪元时间编码,即自1970年1月1日0点到当下总共流逝的秒数。
如 1388185914对应的时间是2013年12月27日,星期五,UTC/GMT。

  • 难度目标

该字段定义所需满足工作量证明的难度,4个字节中首字节表示指数,后3字节表示系数。以区块277316为例,难度位的值为0x1903a30c,0x19是指数的十六进制格式,后半部0x03a30c是系数。

  • Nonce

初始值为0

挖矿目标

区块头完成全部的字段填充后,就可以开始进行挖矿了。

哈希函数的输入数据的长度是任意的,将产生一个长度固定且绝不雷同的值,可将其视为输入的数字指纹。对于特定输入,哈希的结果每次都一样,任何实现相同哈希函数的人都可以计算和验证。一个加密哈希函数的主要特征就是不同的输入几乎不可能出现相同的数字指纹。因此,相对于随机选择输入,有意地选择输入去生成一个想要的哈希值几乎是不可能的。

除了Nonce值外,其他值不能随意更改,所以挖矿就是通过改变Nonce值,找到一个使区块头的哈希值小于难度目标的nonce的过程。

用最简单的术语来说,挖矿就是重复计算区块头的哈希值,不断修改该参数,直到与哈希值匹配的一个过程。哈希函数的结果无法提前得知,也没有能得到一个特定哈希值的模式。哈希函数的这个特性意味着:得到哈希值的唯一方法是不断的尝试,每次随机修改输入,直到出现适当的哈希值。

比特币挖矿过程使用的是SHA256哈希函数。

成功构建区块

计算出有效的区块头哈希后,挖矿节点立刻将这个区块发给它的所有相邻节点。这些节点在接收并验证这个新区块后,也会继续传播此区块。当这个新区块在网络中扩散时,每个节点都会将当前有效区块加到自身节点的区块链副本中。当挖矿节点收到并验证了这个新区块后,它们会放弃之前对构建这个相同高度区块的计算,并立即开始计算区块链中下一个区块的工作。

3.校验新有效区块

当新区块在网络中传播时,每一个节点在将它转发到其节点之前,会进行一系列的测试去验证它。这确保了只有有效的区块会在网络中传播。独立校验还确保了诚实的矿工生成的区块可以被纳入到区块链中,从而获得奖励。行为不诚实的矿工所产生的区块将被拒绝,这不但使他们失去了奖励,而且也浪费了本来可以去寻找工作量证明解的机会,因而导致其电费亏损。

当一个节点接收到一个新的区块,它将对照一个长长的标准清单对该区块进行验证,若没有通过验证,这个区块将被拒绝。这些标准可以在比特币核心客户端的CheckBlock函数和CheckBlockHead函数中获得,它包括:

1
2
3
4
5
6
7
▷ 区块的数据结构语法上有效 
▷ 区块头的哈希值小于目标难度(确认包含足够的工作量证明)
▷ 区块时间戳早于验证时刻未来两个小时(允许时间错误)
▷ 区块大小在长度限制之内
▷ 第一个交易(且只有第一个)是coinbase交易
▷ 使用检查清单验证区块内的交易并确保它们的有效性,即1中的交易验证
▷ “交易的独立校验”一节已经讨论过这个清单。

每一个节点对每一个新区块的独立校验,确保了矿工无法欺诈。在前面的章节中,我们看到了矿工们如何去记录一笔交易,以获得在此区块中创造的新比特币和交易费。为什么矿工不为他们自己记录一笔交易去获得数以千计的比特币?这是因为每一个节点根据相同的规则对区块进行校验。一个无效的coinbase交易将使整个区块无效,这将导致该区块被拒绝,因此,该交易就不会成为总账的一部分。矿工们必须构建一个完美的区块,基于所有节点共享的规则,并且根据正确工作量证明的解决方案进行挖矿,他们要花费大量的电力挖矿才能做到这一点。如果他们作弊,所有的电力和努力都会浪费。这就是为什么独立校验是去中心化共识的重要组成部分。

4.区块链的组装与选择

比特币去中心化的共识机制的最后一步是将区块集合至有最大工作量证明的链中。一旦一个节点验证了一个新的区块,它将尝试将新的区块连接到到现存的区块链,将它们组装起来。

任何时候,主链都是累计了最多难度的区块链。

当节点接收到新区块,它会尝试将这个区块插入到现有区块链中。节点会看一下这个区块的“previous block hash”字段,这个字段是该区块对其父区块的引用。同时,新的节点将尝试在已存在的区块链中找出这个父区块。大多数情况下,父区块是主块链的“顶点”,这就意味着这个新的区块延长了主链。

如果节点收到了一个有效的区块,而在现有的区块链中却未找到它的父区块,那么这个区块被认为是“孤块”。孤块会被保存在孤块池中,直到它们的父区块被节点收到。一旦收到了父区块并且将其连接到现有区块链上,节点就会将孤块从孤块池中取出,并且连接到它的父区块,让它作为区块链的一部分。当两个区块在很短的时间间隔内被挖出来,节点有可能会以相反的顺序接收到它们,这个时候孤块现象就会出现。