区块链原理、架构与应用
上QQ阅读APP看书,第一时间看更新

2.4 区块链运行机制

比特币作为一种新型的数字货币,其发行、所有权证明、存储和安全性等各种问题,均是通过区块链这个底层技术得以可靠运行至今。现在的很多区块链应用都借鉴了比特币区块链网络中的一些设置。下面以比特币为例,介绍区块链的基本原理和运行机制。

2.4.1 区块结构

每个数据区块包含区块头和区块体。区块头封装了当前版本号、前一区块哈希值、当前区块PoW要求的随机数(nonce)、时间戳以及Merkle根信息。区块体则包括当前区块经过验证的、区块创建过程中生成的所有交易记录,这些记录通过Merkle树的哈希过程生成唯一的Merkle根并记入区块头。下面分别详细介绍Merkle树和区块的组成。

1. Merkle树

Merkle树,又被翻译为默克尔树,也被称为Merkle Hash Tree,因为树中的每个节点存储的都是哈希值。Merkle树可以是二叉树或多叉树,根据实际需要决定。

一个区块需要携带的交易信息实际上是一个很大的值,如果存储这些原始信息要求更多的存储空间,具体应用中并不划算。Merkle树提供了一种更简易的表示方式,不仅使得用较小的存储空间容纳更多的数据信息成为可能,实现了交易信息的快速归纳,而且提供了利用部分交易的哈希值就能够验证全部交易的方法。同时利用哈希的形式表示提高了安全性,某个交易中,哪怕只更改了一个小数点,最后展现出来的Merkle根也是完全不一样的。以比特币网络中的区块链为例,Merkle树被用来归纳一个区块的所有交易信息,具体结构如图2-16所示。

如图2-16所示,Merkle树按层归纳交易信息。叶子节点通过对交易信息进行哈希运算,获得交易的哈希值。两个不同的交易哈希进行二次哈希,生成中间节点。逐层往上,递归地对节点进行哈希运算,最终获得将所有交易信息的哈希值放入Merkle根节点,存储在区块头中,无论交易数量,最终的交易信息都只占据32字节。

具体的流程大致可描述如下:

(1)将未包含在之前区块中的未存储在Merkle树中的交易数据进行哈希,将哈希值存储在相应的叶子节点中:

H~A~=SHA-256(SHA-256(交易A))

(2)串联相邻的叶子节点的哈希值,再进行哈希。这些节点就被归纳为父节点。

H~AB~=SHA-256(SHA-256(H~A~+H~B~))

图2-16 Merkle树结构示意图

(3)递归完成类似过程。

(4)直到最后只剩下顶部一个节点,即Merkle根部节点,将其存储在区块头。

Merkle树的存储结构在另一方面也简化了验证特定交易的流程,大幅度提升了验证速度。如图2-17所示,节点只需要下载区块头,然后通过验证该特定交易到Merkle根的路径的哈希值,即可完成特定交易的验证。例如节点需要验证交易K的相关信息,节点只需要下载HK、HL、HIJ、HKL、HIJKL、HMNOP、HIJKLMNOP、HABCDEFGH和Merkle根的哈希值。如果不使用Merkle树结构,即使存储的均为哈希序列,也需要获取全部的交易信息,对所有交易进行遍历,而这些交易信息可能就有几个G的大小。Merkle树结构只需要计算log2N个32字节的哈希值即可完成特定交易的验证,这种验证方法又被称作简单支付验证(SPV)。

图2-17 Merkle树结构图

2. 区块结构

区块链的基本组成单位是区块,区块类似于账本中的账页。以比特币为例,一个区块可以看作由两部分组成,包含基础字段信息的区块头和跟在区块头之后的一长串存储为Merkle树形式的交易数据,如图2-18所示。

图2-18 区块字段示意图

除过区块头和交易信息,区块中还包含表明该区块大小的字段、记录交易数量的交易计数器。具体表示如表2-4所示。

表2-4 区块结构

区块头是一个区块中最重要的部分。主要包括版本信息字段、父区块哈希值、Merkle树根、时间戳、难度目标和nonce值。

(1)版本信息标识了该区块中交易的版本和所参照的规则。

(2)父区块哈希值实现了区块数据间的链状连接。

(3)Merkle树的根值实现了将区块中所有交易信息逐层成对地整合归纳,最终通过一个哈希值将所有信息包含在区块头中。

(4)时间戳以UNIX纪元时间编码,即自1970年1月1日0时到当下总共流逝的秒数。

(5)难度目标定义了矿工需要进行挖矿的工作量证明的难度值,根据实际新区块挖掘出的速度,难度目标值会进行调整,最终保证平均10min出一个新区块。

(6)nonce是一个随机值,初始值为0,矿工挖矿就是找到一合适的nonce值,使得区块头的哈希值小于难度目标。

区块主体中主要存储交易信息,矿工将经过全网验证的交易通过Merkle树的方式表示。如图2-18所示,假设有8笔交易,分别为交易1、交易2、…、交易8,Merkle树首先对交易内容进行哈希计算,每笔交易得出对应的哈希值,然后再对交易哈希值进行两个一组的哈希计算,以此类推,最后的哈希值就是存储在区块头中的Merkle根。Merkle根通过哈希计算的方式实现了对区块中所有交易记录的有效总结。另外,根据哈希运算的特性,Merkle根能够快速验证交易数据的完整性和准确性,只要有人对其中一笔交易进行了篡改,哪怕只有一个小数点,Merkle根便会直观地显示出来。

2.4.2 区块产生

比特币作为一种P2P形式的去中心化数字货币,并不依靠特定的货币机构发行,而是通过特定的算法实现了一套去中心化的发行机制。比特币只能通过矿工的挖矿行为产生,系统通过将比特币作为奖励激励矿工参与记账,挖掘产生新的区块。区块头中的难度目标、时间戳和nonce字段与矿工之间的挖矿竞争息息相关。

挖矿的过程是找到一个使整个区块的哈希值能够小于区块头中难度目标的理想nonce值,通过不断重复试验nonce值的过程被称作工作量证明,简要表示为:

    {
    if Hash(区块头)<难度目标
    挖矿成功;
    else
    nonce+1;
    }

难度目标是一个动态值,系统根据新区块的产生速度进行调节,理想目标是平均10分钟产生一个新区块。但实际上系统并不是每次产生新区块后都要对生成速度进行瞬时调整,而是将最新生成的2016个区块的花费时长与20 160分钟(理想平均10分钟产生一个新区块的总时间)进行比较。根据实际时长和期望时长的比值,系统会决定是否进行难度调整。为了防止难度目标变换过快,每个周期的调整幅度必须小于一个固定因子,一般设定为4,当比较值大于4时,就按照4倍进行调整,进一步的调整会在下一个周期完成。

New Difficulty=Old Difficulty∗(Actual Time of Last 2016 Blocks/20160 minutes)

一个新区块的产生时间取决于矿工的算力,随着矿工算力的日益增长,出块的难度也会日益增加,最终目的都是保证平均10分钟一块的出块速度。而难度是对矿工挖矿困难程度的一种度量。目标是一个256位的数值,难度的计算公式为:

difficulty=difficulty_1_target/current_target

difficulty_1_target=

0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

difficulty_1_target为定值,由此能够计算出不同目标哈希实际区块的产生难度。

从另一方面来说,哈希值是由数字和大小字母组成的字符串,每一位都有62种可能性。每种字符出现的概率是相等的,那么第一位出现0的概率为1/62,理论上需要尝试62次哈希运算才会出现理想的结果。如果前两位均为0,那么需要进行622次尝试,前n位为0,则需要62n次尝试。随着前n位0的个数的增加,难度目标的越来越小,而难度随之增加。类似于掷骰子,目标为6时,投掷出的点数有5/6的可能满足小于点数的条件;当目标为2时,只有1/6的概率满足条件。

依据现在比特币网络的难度,矿工至少需要尝试1015次,才能找到一个合适的nonce值,使得目标区块的哈希符合条件。从现在的挖矿市场来看,更多的算力被大型矿池公司所垄断,以个人为单位的矿工基本不可能赢得这场竞争。

2.4.3 区块连接

在比特币区块链网络中,每一个区块都在区块头中存储了父区块哈希值字段,父区块哈希由对前一个区块的区块头数据进行哈希运算得出,新区块通过父哈希和现在网络中的主链进行连接。区块间通过父区块哈希字段实现了区块间的链状结构,区块间的这种数据结构也被称为区块链。

按照平均10分钟产生一个新区块的速度,矿工从交易池中选取未被主链包含的交易记录通过Merkle树的形式进行打包存储,不同矿工产生的新区块中包含的交易记录可能会存在差异,但这些交易记录均为未被全网承认的待确认交易。

当矿工成功得出新区块的nonce值,会将新区块向全网广播,每个节点都会对新区块的内容进行独立校验,确认无误后会将新区块组装进节点现在存储的区块链中。

节点会查看新区块的父哈希字段,从已存在的区块链中找出父区块。如果已存在的区块中找不到父区块,那该类新区块会被当作孤块存储在孤块池中,直到找到它的父区块,或者因为孤块池中的存储饱和被节点随机丢弃。当相邻的两个区块在很短的时间内被挖掘出来,一些节点因为距离原因首先收到子区块,那么新区块就会被投入到孤块池中。

矿工挖掘的新块如果未能被成功纳入主区块链,那么就不会得到系统提供的挖矿奖励。因此在算力竞争中,如果有两个区块几乎在同一时间被挖掘出来,那么此时起到决定性作用的就是区块的传播速度。一般情况下,具有较多节点连接的区块所挖掘的新区块有更大的概率获得胜利,从而能够实现让下一个区块接续在自己的区块之后,获得系统的挖矿奖励。

TradeBlock曾对此做过一项调研分析(TradeBlock,主要提供数字货币区块链数据挖掘和研究分析),如图2-19所示。在2015.04.23~2015.06.10,平均每天产生1%的孤块,即有约为1%的孤块率。而对于产生孤块的比较典型的原因是由于矿工节点连接的节点数目较少,当全网活跃的比特币节点大约有6000个时,只要连接节点达到3000个,那么一个新区块赢得孤块竞赛的概率为90%。似乎在孤块竞赛中获得成功的区块通常是第一个被广播到全网的。

图2-19 TradeBlock网站截图

在调查中同时发现,区块的传播速度和容纳的交易数量成负相关。这很容易理解,每个接收到新区块的节点都需要独立对区块进行校验。但就交易记录,节点需要将新区块中包含的交易记录和自身交易池中存储的交易记录进行对比,那么一旦区块中的交易数量越多,除了会延长数据传播的时间,还会因为每笔交易在网络中的跳动花费更多的时间。在连接相同节点数量的情况下,区块大小和传播时间存在直接关系。一个700KB的区块传播需要17s,但是200KB的区块传播只需要6s,所以矿工可能为了降低挖矿收益的风险,选择在相同区块容积下打包更少的交易。这种风险同时提醒比特币或其他区块链应用的社区在面对扩容问题的时候,不能只将期望值放在扩充区块容积上,而是需要更多其他的方法和思路。

2.4.4 区块传播

通过coinbase的奖励机制,比特币实现了节点自发保持实时在线的状态去完成记账工作。但是账本储存在每个节点中,去中心化需要保证每个节点中的数据一致,需要防止某些被篡改的恶意账本影响整个网络的交易。因此矿工成功算得工作量证明解后,需要将区块内容进行全网广播,只有获得其他矿工节点的验证通过后,新挖掘的区块才能加入区块链。同时每个节点都会独立地对新区块进行校验,如果区块中包含无效信息,该节点就会将该区块丢弃,各节点的独立校验会确保只有有效的区块内容才能在网络进行传播,不会造成资源的浪费。

基于比特币采用的P2P分布式网络协议,临近节点会首先接收到广播信息。当节点接收到新区块后,会依照验证标准对所有字段信息进行验证。验证内容包括:

(1)区块数据结构的有效性。

(2)区块大小和各字段有效长度的合法性,在长度限制要求之内。

(3)是否拥有正确的nonce值,区块头的哈希值需满足当前目标值。

(4)验证区块头中的MerkleRoot是否由区块交易得到,根据区块交易数据进行Merkle树重构,需与区块数据中保持一致。

(5)对时间戳显示时间进行校验。

(6)区块的第一个交易为coinbase交易,其他交易都不是。

(7)遍历区块中所有交易,检查交易的合法性。

以上校验标准可以从比特币核心客户端的CheckBlock函数中获得,源代码如下:

只有当上述所有标准都确保无误,邻近节点才会继续新区块的传播行为,最终直到全网大部分节点接收新区块的信息。

2.4.5 最长链原则

比特币区块是依靠矿工们不断进行数学运算而产生的,每个区块都必须引用其上一个区块,因此最长的链也是最难以推翻和篡改的,所以节点永远认为最长链才是有效的区块链,只有在最长链上挖矿的矿工才能够获得奖励,即比特币最长链原则。

1. 区块的选择

对于比特币区块链生态系统而言,网络允许任何人都可以进行数据的读取和写入,参与者不需要经过审查和批准就能够进行比特币交易和新区块的挖掘开发。而在没有第三方机构的控制评定下,比特币网络在面对可能出现的差异时,需要能够进行仲裁的方法。

在所有矿工参与的挖掘新区块的算力竞争中,可能会出现两名矿工在较短的时间差内,在几乎相同的时间算得同一新区块的工作量证明解,并各自向全网广播,将新区块传播给相邻节点。其余节点会率先验证接收到区块信息。通常将未被全网承认的但已经传播的区块称为候选区块。结果会造成一些节点收到一个候选区块,而另一些节点收到了另一个候选区块,这两个候选区块可能包含的交易大致相同,只是在交易顺序上有些不同。这时网络中就会出现两个不同版本的区块链,即区块链分叉,大致如图2-20所示。

区块链网络需要在没有第三方干涉的情况下,对类似分叉的这种可能出现的差异情况进行判定。比特币网络中按照最长链原则解决此类问题,因为总有一方能够抢先发现新的工作量证明解并传播出去,所有节点会统一接收更长的链,在已经分叉的情况下重新达成共识。最长链指的是该条链累计的工作量证明最大,一般情况下,也是包含最多区块的链条,这条最长的区块链通常被称为“主链”。在比特币主链上其实也存在着分支,这些分支被当作备用链,如果新添加的区块使备用链累积了更多的工作量,那么这条备用链将被作为新的主链。实际上,去中心化程度越高的区块链因为开放程度高,允许任何人对区块数据进行写入和读取,引起分叉的风险就越大。

图2-20 区块链分叉示意图

2. 区块的组装

节点接收到新的区块数据,需要将新区块和原本的区块链数据进行组装,区块间通过区块头的父哈希字段实现链状连接。

通过验证的区块,节点会根据新区块的父哈希字段,在已有的区块中找寻其父区块。在大多数情况下,这个父区块都是主区块链的最后一个区块,即顶点区块,这样新区块就实现了对原本区块链的延长。但有时候,新区块也有可能延长了备用链,此时根据最长链原则,比较现有的主区块链和备用链的工作量难度,如果备用链累计的工作量证明更大或难度更高,那么节点将收敛于备用链,备用链会成为新的主区块链,现有的主区块链则变为备用链;否则保留原有的区块链为主链;如果出现节点现有的区块信息中没有收到的新有效区块的父区块的信息,那么该区块就会被保存在孤块池中,直到它的父区块被找到。

例如图2-21所示,当有两名矿工在几乎同时都求得了工作量证明解,立即传播他们的新区块(分别是#3458和#3458')到网络中。当这两个区块传播时,一些节点首先收到#3458,一些节点首先收到#3458',这两个候选区块(通常这两个候选区块会包含几乎相同的交易)都是主链的延伸,就会分叉出有竞争关系的两条链。收到#3458区块的挖矿节点会立刻以这个区块为父区块来产生新的候选区块#3459。同样,收到#3458'区块的挖矿节点也会以这个区块为父区块开始生成新的候选区块#3459'。假设以#3458'为父区块的工作量证明首先解出,即新区块#3459'先被广播,当原本以#3458为父区块求解的节点在收到#3458'和#3459'之后,会立刻将该链作为主链(因为#3458那条链已经不是最长链了)继续挖矿。节点也有可能先收到其他节点发来的#3459",再收到#3458",收到#3459"时,会被认为是“孤块”(因为还找不到#3459"的父区块#3458")保存在孤块池中,一旦收到父块#3458"时,节点就会将孤块从孤块池中取出,并且连接到它的父区块,让它作为区块链的一部分。

无论哪种情况,节点选择都是当前工作量证明最大、难度累计最多的链,所有节点最终会在全网范围内达成共识,暂时性差异也会得到解决。比特币将区块间隔设计为10分钟,是在更快速的交易确认和更低的分叉概率间做出的妥协。更短的区块产生间隔会让交易确认更快地完成,也会导致更加频繁的区块链分叉。与之相对地,长的间隔会减少分叉数量,却会导致更长的确认时间。

图2-21 区块的组装