3.2 区块链1.0架构:比特币区块链

时间:2024-04-13 09:21:55

区块链1.0的典型应用是比特币应用。比特币是第一个解决“双花”问题的去中心化虚拟货币系统。下面我们来详细剖析比特币系统的架构。

中本聪在发表比特币白皮书后的第2年,就发布了比特币的第一版实施系统。据普林斯顿大学出版的《Bitcoin and Cryptocurrency Technologies》作者们推测,中本聪很可能是先写好了比特币系统,才写比特币的白皮书的[1] 。从最初的比特币源代码可以看出,比特币系统没有很明确的模块划分,很多不同功能都放在一个5000多行的Main程序中实现。因此,当初中本聪并没有从 架构上考虑太多,而是用简单直白的办法一气呵成地把比特币系统写了出来。当参与开源项目的开发者多起来的时候,一个清晰的架构就显得愈来愈重要,这样对代 码的重用、维护和扩展非常重要。在2013年12月,一个将比特币代码模块化的建议《Post-0.9modularization of Bitcoin Core》提交了上去。从目前发布的Bitcoin 0.12版本看,比特币的模块化工作还在进行过程中。因此,要很清晰地在代码层面理清比特币的架构还需要一个比较长的过程。

由于比特币是基于P2P架构的虚拟货币系统,因此它的架构和我们熟悉的分布式架构有很大不同。从一个客户端/服务端 (Client/Server)、浏览器/服务端(BS架构)或三层架构(3-Tier Architecture)等角度来看比特币架构,很容易感到困惑。比如说大多数比特币的软件,一般都叫客户端(Client),大家自然会想到一定有个 服务端(Server)在后台。但其实在比特币里,服务端这个概念被弱化了,即使是看起来很像服务端的bitcoind,也被称为没有界面的客户端。唯一 被称为服务端的是在bitcoind里的HTTP/JSON RPC服务端。该组件只是用来提供对外HTTP、JSON RPC的服务接口。

图3-1是根据目前的代码情况勾画的比特币架构图。比特币架构总体上分为两部分,一部分是前端,包括钱包(Wallet)或图形化界面;另一部分是运行在每个节点的后台程序,包括挖矿、区块链管理、脚本引擎以及网络管理等功能。

3.2 区块链1.0架构:比特币区块链

图3-1 比特币架构

接下来将逐个介绍其功能。

[1] Arvind Narayanan,Joseph Bonneau,Edward Felten,Andrew Miller,Steven Goldfeder,Bitcoin and Cryptocurrency Technologies,Princeton University Press,2016.

3.2.1 比特币前端

1.钱包

钱包保存用户的私钥数据库,并管理用户的余额,提供比特币交易(支付、转账)功能。

钱包分为两种:非决定性钱包和决定性钱包[1]

1)非决定性钱包(Nondeterministic Wallet):该类钱包直接保存私钥,私钥数据保存在Berkeley DB上。所谓决定性(deterministic),指的是私钥是否由种子生成。如果是就叫“决定性”,反之就叫“非决定性”。非决定性钱包因为直接保存 私钥,如果私钥被盗窃,钱包里的比特币就会被盗走。因此非决定性钱包的安全性不高。另外如果私钥比较多,该类钱包管理起来就比较麻烦,备份也不方便。而且 比特币系统出于提倡隐私保护的缘故,不鼓励地址重用,因此虽然比特币核心带有一个非决定性钱包,但目前并不被推荐使用。

2)决定性钱包(Deterministic Wallet):目前建议使用的一种钱包。该类钱包所有的私钥都由一个私钥种子(Seed)通过单向哈希算法生成,因此备份该类钱包非常容易,只要备份私 钥种子,就可以利用种子一次性恢复所有的私钥。决定性钱包又分两种类型,类型1是普通决定性钱包,其私钥种子直接生成所有私钥;类型2是层级决定性钱包 (Hierarchical Deterministic Wallet),它的私钥保存在一个树形结构,由一个总私钥生成父私钥,父私钥生成子私钥等。

钱包从部署场景来说,分为4种,分别为:移动钱包、桌面钱包、互联网钱包,以及纸钱包。

移动钱包顾名思义是运行在智能手机、移动终端的轻量级钱包。该类钱包由于运行在资源有限的环境,一般不会下载整条区块链,而多数采用一种叫“简化支付验证”[2] (Simplified Payment Verification,SPV)的方法验证交易。这种钱包也叫SPV钱包。该方式依靠网络上的可信任节点查询所有区块的区块头,以及按照交易的确认数 (相当于黑客攻击的难度),再有就是能否在相应的区块中找到该笔交易来验证支付的真伪。

3.2 区块链1.0架构:比特币区块链

图3-2 比特币Android钱包

图3-2所示的比特币Android钱包,是一个基于bitcoinj实现的开源Android钱包,于2011年3月7日发布。它支持Android 4.0以上或黑莓OS 10以上版本。到2015年6月,已经有80万用户使用该钱包。

移动钱包的优点是灵活方便,缺点是因为不保存整条区块链,不做交易的全验证,因此安全性不是特别好。

桌面钱包也分两种,一种是厚钱包(Thick Wallet),另一种是薄钱包(Thin Wallet)。厚钱包下载整条区块链,并进行完整的交易校验。比特币核心(Bitcoin Core)就是一个厚钱包。图3-3所示是比特币核心钱包。它提供完整的钱包功能,包括签名、钱包加密、备份、**导入、导出等。另外像armory钱 包,可以管理多个钱包,并将钱包离线保存,以防止攻击。薄钱包不下载整条区块链,而是采用SPV等方式来验证与用户相关的支付交易,例如Multibit 钱包。另外像Electrum钱包也算薄钱包,它本身不使用SPV方式,但也不保存完整区块链,而是信任它的服务端的验证。

3.2 区块链1.0架构:比特币区块链

图3-3 比特币核心钱包

厚钱包的优点是安全,缺点是有交易全验证的开销,适合于资金安全性要求高的场景,比如非小额支付等场景。薄钱包的优点是灵活高效,但安全性不高,适合于小额支付场景。

互联网钱包也不下载整条区块链。其优点是可以在任何地方、任何设备管理钱包。互联网钱包依托第三方平台提对用户隐私的 保护。互联网钱包也和桌面薄钱包一样有安全性不高的问题,但使用起来更灵活方便。例如Blockchain.info、CoinCorner等。如图 3-4所示是Blockchain.info提供的互联网钱包。

3.2 区块链1.0架构:比特币区块链

图3-4 Blockchain.info提供的互联网钱包

纸钱包用于将私钥进行冷备份,可以用于防范由于电脑或USB介质损坏所造成的私钥丢失。同时由于纸钱包离线存放,也能 防范黑客通过网络攻击盗窃私钥,但也要防范纸钱包被人物理偷窃或复制。如图3-5所示为一个纸钱包,左边的二维码是比特币的地址信息,右边是用户的私钥二 维码。

3.2 区块链1.0架构:比特币区块链

图3-5 纸钱包

2.HTTP/JSON RPC API

比特币提供HTTP/JSON RPC API接口,供外部通过接口控制比特币节点。当运行比特币核心bitcoin-qt传入-server选项时,或运行bitcoind时,比特币提供一个 HTTP/JSON RPC服务端,外部程序可以通过JSON RPC API来访问比特币节点。缺省情况下该服务端只允许来自同一机器的客户端访问,开放给任意机器进行远程访问是非常危险的,也是不建议的做法。

使用不同语言编写的程序可以方便地通过HTTP/JSON RPC API接口访问比特币节点。例如Bitcoin-JSON-RPC-Client是一个轻量级的Java客户端程序。

3.命令行工具bitcoin-cli

bitcoin-cli提供一个命令行工具来控制比特币节点。该命令行工具通过JSON RPC API接口访问比特币后台bitcoind。用户可以通过发命令来完成比特币的各项功能,例如查询余额、支付、转账等。

4.比特币浏览器bx

比特币提供一个跨平台的C++libbitcoin库,该库支持比特币全节点服务端和浏览器(BitCoin Explorer(bx))作为客户端命令行工具。比特币浏览器命令提供与bitcoin-cli基本一样的功能。但同时bx提供bitcoin-cli 没有的一些**管理功能和处理工具,包括对类型2决定性(type 2deterministic)**管理、助于记忆的代码(mnemonic code)的**生成(例如用易于记忆的词汇作为私钥种子)、隐秘地址、支付和查询支持。

5.图形开发工具(Qt)

比特币核心是比特币使用最广的客户端,它是使用C++开源用户界面开发工具Qt所开发的桌面客户端。Qt是一个跨平台 的C++图形用户界面应用程序框架。它提供给开发者建立图形用户界面所需的功能,广泛用于开发GUI程序,也可用于开发非GUI程序。Qt是完全面向对象 的,很容易扩展,并且允许真正的组件编程。Qt支持更多的平台(包括Microsoft Windows、GNU/Linux、Mac OS X、Android、iOS、WinCE、UNIX家族等)。

[1] Mastering Bitcoin,O’Reilly Media,Inc.

3.2.2 比特币节点后端

比特币节点后台负责参与比特币网络的通信互联,维护区块链,验证区块、交易,广播、转播传递区块交易信息。比特币的后 台程序主要是由bitcoind,以及挖矿节点程序构成。比特币核心bitcoin-qt实际上是包含前后端(除挖矿功能以外)的一体化节点。下面简单介 绍比特币后端组件的功能。

1.区块链管理

区块链管理涉及初始区块链下载、连接区块、断开区块、校验区块和保存区块,以及发现最长链条的顶区块。区块链管理的代码逻辑都在main.cpp程序中实现。

(1)下载区块链

在比特币全节点第一次加入网络运行时,先要下载并验证整条区块链。当区块链的容量不断增大时,要下载整条链的时间就会 越来越长,在网络速度低的环境下,甚至要几天时间才能完成整条链的下载。2014年,比特币0.10.0版本正式发布一个新的初始区块链下载方式,叫“区 块报头先行”(header first)。该方式可以大大提高初始区块链的下载速度。在该模式下,新的节点先从邻节点下载所有的区块报头,区块报头只有80字节,相比一个1MB的区 块来说要小得多。当它下载了所有的区块报头之后,节点可以并行地从多个邻节点同时下载不同区间的区块,大大提升了整条区块链的下载速度。

(2)接收区块链

现有节点在开启时会先将整个区块链的索引从LevelDB调进内存。需要注意的是,该区块链的索引不是单条的链,而可能是一个树,也就是说每个区块只有一个父区块,但可能有多个子区块,因为子区块形成暂时分叉,需要逐渐发现哪个子区块属于最长的链条。

按比特币的挖矿难度设计,在整个网络中新区块产生的速度被动态地调节在10分钟左右。一个节点接收到一个新区块,如果 该区块包含的指向前区块的区块头哈希值与节点当前顶端区块哈希值相同时,该节点会尝试连接新接收的区块,并将其作为当前链条的最顶端区块,从而延伸节点所 拥有的区块链。这是因为在区块链中,每个区块都保存有前个区块的区块头哈希值,并通过这个方式来链接区块。

(3)区块链验证

在区块链管理中,连接区块函数ConnectBlock()是一个检测“双花”交易的关键。该函数对新接收的区块中的 所有交易进行检测,验证是否每个交易的比特币来源都能在当前的“尚未花比特币”(Unspend Transaction Output,UTXO)记录中找到匹配。在网络延迟情况下,节点接收的区块可能不按顺序到达,在这种情况下,有些交易的比特币来源可能在UTXO记录中 暂时找不到,但当后面收到延迟到来的区块后,UTXO记录会被更新,区块链条会链接起来。举个例子说明就会比较清楚,假设节点当前的区块链顶端区块是 B1,由于网络延迟问题它先收到B3,在验证B3中的交易的时候,有些交易的比特币来源可能在当前的UTXO记录中找不到。但当后面接收到B2 后,UTXO记录会被更新,如果B2和B3的交易都被校验后,当前的区块链条也从B1延展到B2、B3,同时也会建立回滚记录并存放在磁盘中。回滚记录将 用于断开现有区块。区块校验的流程图如图3-6所示。

3.2 区块链1.0架构:比特币区块链

图3-6 区块验证流程图

(4)重组区块链

当节点发现网络中有一条不基于它当前区块链的一条更长区块链时,它需要断开现有的区块并对区块链进行重组。大部分重组 只是一个区块的重组,多发生在不同矿工几乎同时挖到合法的区块时。比如有A和B区块,有些网络节点先接收A,有些节点先接收B,其后的链条会发生分叉,假 如有B区块的链条变得更长,有A区块的网络节点都会很快通过断开A、连接B来重组它的区块链。断开区块、重组区块链涉及UTXO更改,被断开的区块中交易 会回退到交易内存池(mempool),这个时候“回滚”记录就可以用来回滚断开区块中的交易。

2.区块验证

交易验证模块会基于以下条件检查收到的比特币交易是否合规:

·交易的格式是数据结构必须正确;

·交易的输入和输出不能为空;

·交易的大小不能超过定义的区块最大值MAX_BLOCK_SIZE;

·每个交易的输出,以及所有输出的合计,必须在一定范围内,也就是大于0,小于2100万比特币;

·交易输入的哈希值不能为零,不应该转播挖矿(coinbase)交易;

·nLockTime小于等于INT_MAX;

·交易的字节大小要等于或大于100;

·交易的签名操作数要小于签名操作的上限;

·解锁脚本(scriptSig)只能把数字放入堆栈,锁定脚本(scriptPubkey)必须是标准格式;

·和收到的交易相匹配的交易必须能在当前交易池或是主链上某个区块中找到;

·对交易的每个输入,如果其对应的UTXO输出能在当前交易池中找到,该交易必须拒绝(双花交易),因为当前交易池是未经记录在区块链中的交易,而交易的每个输入应该来自确认的UTXO,如果在当前交易池中找到,那就是双花交易;

·对交易的每个输入,如果其对应的UTXO输出不能在主链或当前交易池中找到,该交易是一个孤儿交易,应将该交易放入孤儿交易池中;

·对交易中的每个输入,如果其对应的UTXO输出是一个挖矿初始(coinbase)交易,该初始交易应该获得100个确认区块的确认;

·对交易中的每个输入,其对应的输出必须是UTXO(存在且没被花掉);

·用对应的输出交易来获得输入的值,检查每个输入的值及其合计,应该在允许的区间(0~2100万比特币);

·如果一个交易的输入合计小于输出总计,则拒绝该交易;

·如果交易的费用太低,则拒绝该交易;

·每个输入的解锁脚本(unlocking script)必须和相应输出的锁定脚本(locking script)共同验证交易的合规性。

最后一个检查可能不容易理 解,但却是比特币平台设计的精髓。比特币的一个很大的创新是依靠脚本来验证交易的合法性,即每一个将要花掉的比特币必须有相应的来源。简单来说,比特币交 易中的输入(input)和输出(output)都由脚本和数值组成。通过比特币的脚本引擎,在一个简单的堆栈式计算平台上执行。举个例子说明可能帮助理 解。一个交易的输入部分的解锁脚本是<sig><pubKey>,而与其对应的UTXO的锁定脚本是OP_DUP OP_HASH160<pubKeyHash>OP_EQUALVERIFY OP_CHECKSIG。在最初的比特币实现中,是将解锁脚本和锁定脚本串起来一同执行,如果最后结果是逻辑值“真”(TRUE),则验证该交易的比特币 来源是合法的。图3-7展示了脚本执行的顺序和每个脚本执行后的堆栈状态。具体执行过程如下:

1)由于是堆栈式计算引擎,因此作为数值的<sig>和<pubKey>相继入栈。当执行脚本OP_DUP时,它将堆栈头的<pubKey>复制一份也压入堆栈。

2)脚本OP_HASH160执行,将把堆栈头的<pubKey>弹出,并用HASH160算法进行哈希处理,哈希结果放回堆栈。然后遇到<pubKeyHash?>数值,该数值也被压入堆栈。

3)脚本OP_EQUALVERIFY把堆栈头端的两个哈希值都弹出,并进行比较。如果不一样,验证就出错,交易不合 法。如果验证通过,堆栈只剩下<sig>和<pubKey>,最后脚本OP_CHECKSIG将两者弹出,执行检查签名的脚本, 验证该交易签名是否是由拥有该公钥对应用户用其私钥签的,如果是,交易就是合法交易,否则就是不合法交易。

3.2 区块链1.0架构:比特币区块链3.2 区块链1.0架构:比特币区块链

图3-7 比特币脚本交易验证示意

由于将解锁脚本和锁定脚本串起来一同执行会产生安全风险,为防止恶意的解锁脚本将数值压入堆栈破坏锁定脚本,从2010年开始,不再将解锁脚本和锁定脚本串起来执行,而是分开执行,执行的结果再复制到锁定脚本执行的堆栈。

3.内存池管理

比特币内存池(mempool)管理也就是交易池管理。节点将通过验证的交易放在一个交易池中,准备放在一个挖到的区 块中。当矿工挖到一个合格的区块后,他将按一定的优先级次序从交易池中选出交易放到区块中。优先级是按交易中的输入对应的UTXO的“链龄”和交易额的大 小来划分的。越老UTXO的交易以及交易额越大的交易优先级会越高。优先级(Priority)采用以下公式计算:

Priority=Sum(Value of input×Input Age)/Transaction Size

在此公式中,输入的值是按比特币基础单位(satoshi)计算,1个satoshi等于一亿分之一个比特币。 UTXO的链龄按已在链上记录该交易的区块为起点,按后面有多少个区块来计算,也就是计量该区块在区块链的“深度”。交易的大小以字节为单位。要成为高优 先级交易,一般来说优先级值要高于57600000,这个优先级相当于1个比特币交易额的交易,已有一天(24小时)的“链龄”(相当于已有144个区块 的确认),交易长度有250个字节。比特币区块中的前50K字节保留给高优先级的交易。高优先级的交易费即使是零都会被包括进区块链的优先区域。

当区块填满后,剩下的交易会留在内存池,等待下一个区块的到来。随着它们的“链龄”的逐渐增加,它们以后被选中的几率 也会逐渐增加。在内存池的比特币的交易不会过期,对于一个交易费用为零的交易,最终也会因为其对应的UTXO链龄的逐渐增加而被包括在区块链上。但是内存 池的交易不保存在硬盘上,当挖矿节点重启时,内存池的交易会被清空。所以也会出现一种情况,就是一个低优先级的交易由于挖矿节点在网络频繁离开而不存在于 任何挖矿节点的内存池中。因此,如果在一定时间内一个交易一直不能被矿工包括在区块链上,钱包软件需要重新发送该交易,并附上较高的交易费。

在一些比特币节点的实现也维护一个独立的“孤儿”交易池。如果一个交易的输入相对应的UTXO不能被找到,也就是没有“父”交易,会被当作“孤儿”交易,暂时放在“孤儿”交易池。当父交易来到后,该“孤儿”交易会被从“孤儿”交易池移到内存池。

4.邻节点管理

当一个新比特币节点做初始启动(bootstrap)的时候,它需要发现网络中的其他节点,并与至少一个节点连接。一 般是与一个已知的节点在8333端口建立TCP连接。连接的“握手”流程发送一个版本信息,包括:P2P协议版本,本节点支持的服务,当前时间,对方节点 IP地址,本节点IP地址,比特币软件版本,以及本节点当前区块链的长度。对方节点收到握手信息后会回复一个收到确认的信息。

那么新节点如何发现邻节点呢?

第一个办法是用一些“DNS种子”查询DNS。“DNS种子”是提供比特币节点地址的DNS服务器。比特币核心带有5个不同的“DNS种子”。DNS种子可提供稳定的比特币节点地址。

第二个办法是直接把一个已知的邻节点作为种子节点,然后通过它发现更多的邻节点。当发现新的邻节点后,新节点一般将断 开和种子节点的连接。新节点将其地址信息发给邻节点,邻节点会继续将新节点的地址转发给它们的邻节点,这样新的节点会在网络上被其他节点知道,并保持其在 网络上连接的畅通。另外新节点将发getaddr信息给邻节点,邻节点收到后将把所知的地址信息发送给新节点。新节点一般会维持与8个邻节点的连接。新节 点启动结束后,它会记住最近连上的邻节点的地址。当它重新启动的时候,它能够很快地完成和已知邻节点的连接。如果以前的邻节点都连不上,它会重新开始初始 启动流程。用户也可以通过提供一个固定IP地址列表来替换比特币系统自动管理邻节点的流程。

如果一个连接上一段时间内没有信息交互,节点会定期发一些信息去维护连接。如果一个节点和邻节点的连接在超过90分钟 里没有联系,该邻节点会被认为下线,节点会寻找一个新的邻节点来进行连接。因此,无需要中心控制,网络节点可以*加入或离开网络,即比特币网络能动态地 调节节点的连接,以保证比特币网络的正常运行。

5.共识管理

比特币里广义的共识管理 (Consensus)应该包括挖矿、区块验证和交易验证规则。但这些功能实现目前分散在不同的程序中。由于比特币的关键是在陌生P2P环境建立共识机 制,因此共识管理至关重要。目前比特币的开源社区也意识到共识管理的模块化非常重要,因为不同的比特币实现可以简单重用共识管理模块,以保障验证规则的一 致性。未来共识模块将从比特币核心中分离出去,作为独立的模块。这样未来共识逻辑的改变也不会对比特币核心带来影响。目前在比特币0.12.0版本中,一 部分共识管理的代码已经移到consensus子目录。可以预见,未来更多的共识管理逻辑将移到该子目录,成为可插拔的共识模块。

比特币的共识管理必须支持前向兼容,即使过去版本有缺陷(bug)也要保持,否则比特币网络会出现分叉。

目前在consensus子目录的共识程序有consensus.*、merkle.*、params.*和validation.*。

6.规则管理

比特币的共识规则是所有节点都必须遵守的规则(policy)。而每个节点可以采用一些共识规则以外的个性化规则。这 部分的规则由规则管理模块实现,目前放在policy子目录中。一个个性化规则的例子是将交易存放内存池的规则。比如一个节点可以拒绝保存、中转大于 200KB的交易。这个规则和共识规则不会产生矛盾。如果一个实施该规则的节点,收到一个包含大小为200KB的交易的区块,它不会拒绝该区块,只不过它 不会保存或中转大小为200KB的交易。另外像对交易费用的一些规则,也可通过规则模块来管理。

7.密码模块

密码模块(Crypto)主要是处理比特币地址,采用RIMEMD和SHA-256算法以及Base-58编码来生成 比特币地址。目前代码放在crypto的子目录中。比特币的公钥是通过私钥产生的,然后采用Secure Hash Algorithm(SHA)算法SHA256和RACE Integrity Primitives Evaluation Message Digest(RIPEMD)算法RIPEMD160对公钥进行处理,最后通过Base58编码形成比特币地址。例如对一个公钥K,处理流程先进行 SHA256哈希处理,然后再对哈希值进行RIPEMD160哈希处理,得出一个160位(20个字节)的结果,最后经过Base58Check编码,形 成可读的字符串地址。熟悉技术的读者可能对Base64编码比较了解,Base64编码主要用于基于文本的系统传送二进制数据,例如在邮件里发的附件都要 进行Base64的编码。Base58Check采用Base58编码,同时加入校验码,以防止出现不小心写错地址的情况。Base58是Base64的 子集,过滤了一些容易引起混淆的字符,例如0(数字零),O(大写o),l(小写L),I(大写i),以及“+”和“/”符号。

Base58Check的校验码对地址信息进行双重SHA256哈希处理,并取前4位做校验码,加在比特币地址的后面,因此比特币地址带有校验信息,可以防止人为错误。

8.签名模块

比特币采用椭圆曲线数字签名算法(ECDSA)来实现数字签名以及生成公钥。ECDSA是一种非对称加密算法,是基于 椭圆曲线离散对数问题的计算困难性的一种公钥密码的方法。secp256k1曲线是由国际上最著名的椭圆曲线密码技术公司Certicom所推荐的椭圆曲 线,具有比其他曲线更高的性能。原来大部分的比特币secp256k1ECDSA实现是依赖于OpenSSL库,后来由于OpenSSL库可能会带来共识 模块的bug,因此在2015年以后,比特币开源社区发布了基于secp256k1的ECDSA专有实现C库,并放在secp256k1子目录中。

9.脚本引擎

比特币的脚本语言是一种专门设计的、与“Forth”类似的、基于堆栈的编程脚本(script)语言。基于堆栈的语 言的指令只按顺序执行一次,也就是说没有循环或跳转指令。因此脚本的指令数给我们一个程序运行时间的上限和所需的内存上限。这种基于堆栈的运算平台不是图 灵完备的运算平台。

3.2 区块链1.0架构:比特币区块链提示:图灵完备是一个术语,当一组数据操作的规则(一组指令集、编程语言,或者元胞自动机)满足任意数据按照 一定的顺序可以计算出结果,被称为图灵完备(turing complete)。元胞自动机(Cellular Automaton)是一种时间和空间都离散的动力系统,由冯·诺依曼在20世纪50年代发明。散布在规则格网(Lattice Grid)中的每一元胞(Cell)取有限的离散状态,遵循同样的作用规则,依据确定的局部规则同步更新。大量元胞通过简单的相互作用而构成动态系统的演 化。图灵完备概念比较抽象,其含义不好理解,这里解释一下。简单说是指一个与通用图灵机计算能力相当的计算系统,一般来说在可计算理论中,一个计算系统如 果能模拟单带图灵机,则被称为图灵完备。

图灵模型看似简单,实际上功能非常强大,通过图灵机的组合,可以解决世界上大部分的计算问题(除了图灵“停机问 题”以及与其等价的计算问题以外。有兴趣的读者可以参考Tom Stuart著的《计算的本质:深入剖析程序和计算机》)。现代计算机归根结底都基于图灵模型,可以说图灵奠定了现代计算机科学的基础。

前面说过,比特币的脚本语言不是图灵完备的语言,它没有能力计算任意带复杂功能的任务,这也是比特币专门设计的,因为 矿工必须运行这些脚本,运行者可以是来自任意参与网络的人,我们不希望矿工有能力提交一个可能有死循环的脚本,这也是从安全角度的设计。作为一个虚拟货币 系统,这样的设计已经足够满足需求,同时还有很大的空间去扩展能力。而另一方面,以太坊的设计从一开始就是一个基于区块链的编程平台,不仅仅局限于虚拟货 币,因此以太坊的虚拟机(EVM)支持图灵完备的编程语言。但以太坊会用Gas(燃料)机制来防止出现死循环的脚本,也就是说,提交脚本必须提供一定的 Gas,相当于必须有少量的以太币来保证脚本的运行,当Gas用完,智能合约里的脚本也就不能继续执行。

比特币的脚本语言非常小,只能有256个指令,每个指令是一个字节长。这256个指令中,75个是保留指令,15个已废弃。但比特币通常使用的指令不多,见表3-1。很多指令在比特币网络上不能执行,因为每个节点可以有自己的脚本白名单,只允许运行白名单上的指令。

表3-1 常用比特币脚本指令表

3.2 区块链1.0架构:比特币区块链

脚本引擎是校验交易的运算平台,从对解锁脚本(unlocking script)和锁定脚本(locking script)的自动执行校验可以看出该引擎的重要作用。另外脚本引擎也可以用来实现合约。像以太坊的虚拟机(EVM)实际上可以看成是比特币脚本引擎的 扩展,它增加了图灵完备的运算能力。因此,新版本的比特币将脚本引擎放在script子目录中,将来可以变成可插拔引擎,使得引入新的功能更强大的引擎更 为方便,不影响原有比特币的代码。

目前script子目录里有interpreter.*、script.*和standard.*程序。script.h定义了脚本命令,interpreter.cpp解析、评估和较验脚本命令,standard.h定义了标准的交易。

10.挖矿

比特币核心不带挖矿(mining)功能。比特币最早的挖矿程序是cpuminer,是通过CPU来挖矿的。随着加入 比特币的矿工增多,很快CPU就不能胜任挖矿所需的计算能力,GPU取代CPU成为挖矿的主力,具代表性的程序是cgminer。随着比特币价值的升高, 比特币挖矿成为有利可图的业务。这时采用现场可编程逻辑门阵列FPGA(Field Programmable Gate Array)和专用逻辑电路(Application Specific Integrated Circuit)的专业设备相继出现,到现在,基本上CPU、GPU、FPGA的挖矿设备相继退出历史舞台,目前使用的是清一色的ASIC挖矿设备。因为 比特币的挖矿算法是SHA256算法,因此针对该算法优化的ASIC挖矿设备比其他挖矿设备有着性能上无可比拟的优势。Bfgminer程序支持FPGA 和ASIC挖矿设备,是目前比较流行的挖矿程序。随着比特币挖矿产业的发展,比特币挖矿池成为主流。挖矿池由矿池主控制挖矿,矿工参加集体挖矿,挖到的比 特币按参与集体挖矿的矿工提交的工作量证明来分配。具体说来是矿工需按矿主需求提交工作量证明,这些工作量证明不一定是挖到的合乎比特币难度的区块,而是 接近难度要求的区块。参与矿池挖矿获得分成的几率要大于单独挖矿的几率,而且矿池越大,计算力越强,挖到比特币的几率就会越大。因此比特币的矿池也越来越 集中,目前大部分的矿池都是中国的矿池。中本聪当初设计比特币的目标是建立一个完全去中心化的虚拟货币,采用“一个CPU一票”的理念。但后面出现比特币 矿池高度集中、计算力中心化的问题却是中本聪始料未及的。矿池挖矿程序中比较流行的是Bitminer。

很多人觉得比特币挖矿很神秘,其实简单说就是:不断对区块报头进行哈希处理,每次尝试改变一个随机数,直到区块报头的 哈希值符合一定的条件,比如说起始必须有多少个零,才算挖到一个合格的区块。由于哈希处理不可逆,也就是说根据哈希算法得出的结果,不能反推出输入值,因 此不能预知输入的参数,只能随机地试。而且两个不同的输入经哈希处理后得到的结果相同的几率小得可以忽略,因此比特币网络中谁能挖得到矿是一个随机的事 件,几率的大小取决于节点的计算能力。

比特币挖矿的难度目标决定了网络大约多长时间能挖到一个块。比特币的设计目标是平均大约10分钟出一个区块。这个节奏 决定了比特币的发币频率以及比特币交易的速度。图3-8显示的是比特币区块链高度为421133的区块。上面的难度位数值是402990845,转成十六 进制是0x180526FD。比特币的难度目标是以十六进制数的前两位做指数,其余位数做系数,由下面公式计算出来:

target=coefficient×2^(8×(exponent-3))

区块421133的系数是0x0526FD,指数是0x18,根据公式计算的结果是:

target=0x0526FD×2^(8×(24-3))=337661×2^168

=0x00000000000000000526FD000000000000000000000000000000000000000000

3.2 区块链1.0架构:比特币区块链

图3-8 比特币区块信息

这是一个非常大的数,一般的计算器都会溢出。转成十六进制后,前60多位都应该是零。比特币的区块的哈希值要小于这个目标,才能算挖到一个合格的区块。

由于在网络上节点可*出入,而且从趋势来说,整个网络的计算能力保持增长的态势。那如何把网络的出块速度稳定在10 分钟一个呢?比特币网络是通过调整挖矿难度的目标来达到这个目的的。具体说来,就是每隔2016个区块,所有的节点都要重新更新比特币的挖矿难度目标。它 挖到前2016个块所需要的时间与20160分钟(基于10分钟出块速度的两周时间)相比,如果前者时间短,比特币网络将调高难度,如果前者时间长,比特 币网络会调低难度。

新难度计算公式是:

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

这样的话,区块的产出速度多会稳定在大约每10分钟一个块。

11.HTTP/JSON RPC服务端

比特币在启动的时候,初始化程序init.cpp会启动HTTP/JSON RPC服务端的线程组。该组件对外提供HTTP和JSON RPC的接口,外部程序可以通过JSON RPC接口来调用比特币的API,达到控制比特币节点的功能。该接口缺省是仅接收来自本机的客户端的连接请求。远程连接有极大的风险隐患,因此一般不推 荐。读者可以参考网页https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_calls_list 上列出的最新API列表。

12.Berkeley DB和LevelDB数据库

比特币用Berkeley DB做钱包数据库。Berkeley DB是一个开源的文件数据库,介于关系数据库与内存数据库之间,使用方式与内存数据库类似,它提供的是一系列直接访问数据库的函数,而不是像关系数据库那 样需要网络通信、SQL解析等步骤。Berkeley DB是一个轻巧而又性能高的嵌入式数据库,Berkeley DB可以保存任意类型的键/值对,而且可以为一个键保存多个数据。Berkeley DB可以支持数千个并发线程同时操作数据库,支持最大256TB的数据。

很多人在编译BitCoin软件的时候,会遇到Berkeley DB相关的错误。这个需要在操作系统中先安装Berkeley DB 4.8。有些操作系统,例如Ubuntu Kylin15.04版本,需要配置Berkeley DB的源。碰到类似问题的读者可以尝试下面命令来修正错误:



sudo apt-get install libboost-all-dev    
sudo add-apt-repository ppa:bitcoin/bitcoin    
sudo apt-get update
sudo apt-get install libdb4.8-dev libdb4.8++-dev


比特币的区块原始数据不存放在数据库中,而只是作为文件类型存放在硬盘上。在系统的目录是blocks/blk*.dat.这个主要是用来快速搜索钱包缺少的交易记录,或者重组本地区块链的不同部分,同时给外部的节点提供区块链的同步服务。

LevelDB用来存储区块的索引和UTXO(未花的比特币交易输出)记录。LevelDB是一个Google实现的 非常高效的键值(Key Value)数据库,目前的版本1.2能够支持几十亿级别的数据量。在这个数量级别下还能有非常高的性能,主要归功于它的良好的设计。

在blocks/index/*这个目录中,存放着LevelDB中的已知区块的元数据,以及如何在硬盘找到它们的索引。没有这些索引文件,查找一个区块将会非常慢。

在chainstate/*这个目录中,存放着LevelDB中的UTXO记录,以及一些这些交易来源的元数据。这些数据用来校验收到的区块和交易。

另外回滚交易记录作为原始数据存放在blocks/rev*.dat。在区块链分叉重组的时候需要用回滚记录去更新UTXO记录。

需要注意的是,LevelDB的数据是冗余数据,可以用原始区块链数据来重建。但重建会花很长时间。但如果没有LevelDB的数据,比特币的校验和其他操作都会变得非常缓慢。

13.P2P网络管理

P2P网络管理的代码主要是在P2P网络上实现和其他邻节点的通信功能。这些通信功能包括:发现邻节点;连接并管理与 邻节点的Socket连接;与邻节点交换不同的P2P消息(这些消息包含区块和交易);有时在特殊情况下,会禁止异常行为的邻节点的连接。比特币节点的缺 省配置是主动连接8个邻节点,同时允许最多125个其他节点发起的连接请求。比特币防止拒绝服务攻击(DoS)的方法主要是禁止异常行为邻节点的连接。如 果一个邻节点传送非常明显的错误信息,该连接将被断开,而且其IP地址会被禁止,其重新连接申请会被拒绝。

大部分的P2P代码集中在net.h/net.cpp。邻节点IP地址管理代码是addrman.h/addrman.cpp。地址管理程序把地址存放在peers.dat数据库中,在启动的时候再把它调入内存。

比特币节点按功能分有几种。一种是全功能节点。全功能节点带有钱包、RPC服务端,具有挖矿功能和进行节点校验区块和 交易,并把区块和交易中转给与之相连接的邻节点。全功能节点会从头开始校验区块链,虽然它也可以采用区块链文件剪枝的方式来清掉区块链上一部分老的内容以 减少硬盘空间的占用。另一种节点叫“基础全节点”,该种节点也做区块和交易的交易和中转,但不挖矿,不带钱包和RPC服务端。

还有一种比特币节点叫SPV节点,SPV是Simplify Payment Verification的缩写,前面在介绍钱包时简单介绍过SPV的概念。SPV节点信任别的节点来对区块和交易作校验。具体过程描述如下:

SPV节点一般会在与邻节点的连接中设置过滤器,在比特币中叫Bloom过滤器,该过滤器只接收包含钱包里的公钥地址 的交易。当一个邻节点看到一个交易与SPV节点的过滤器设置的条件符合,它就会用merkleblock消息来给该SPV节点发送一个区块。该 merkleblock消息包含区块头和一个连接该交易到Merkle树根的一个merkle路径。SPV节点可以利用该merkle路径来把交易和包含 交易的区块联系起来,并验证交易包含在区块中。SPV节点也用区块头来验证包含交易的区块和区块链中的其他区块能连上。这两个验证可以证明交易是记录在区 块链上。总之,SPV节点只需要接收小于1KB的区块头和merkle路径数据,这个是全节点接收数据的千分之一(全节点目前接收大小1M的区块)。

比特币P2P消息结构定义以及传输过程中的序列化/非序列化(serialization /deserialization)代码目前集中在Main.h/Main.cpp文件中。这些其实应该独立出来,因为该部分是比特币最基础的代码,可以 给不同的模块重用。但现在的架构还不够理想,未来可能会重新组织该部分代码。

14.ZMQ队列管理

比特币采用Zero MQ作为消息队列管理和消息分发工具。ZMQ是一个简单好用的传输层,提供像框架一样的一个socket library,它使得Socket编程更加简单、简洁,性能更高。它是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。

与很多人熟悉的RabbitMQ相比,ZMQ并不像一个传统意义上的消息队列服务器,事实上,它也根本不是一个服务器,它更像一个底层的网络通信库,在Socket API之上做了一层封装,将网络通信、进程通信和线程通信抽象为统一的API接口。


来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1029

'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })();
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();