比特币后端全解析

扫码链接5000+新基建产业链上下游从业者,入群请备注“机智地+姓名+公司+岗位

在上节课的学习中,我们了解到比特币前端的组成,今天这节课,我们继续“拆解”比特币,从比特币中一观密码货币的“后端”是什么样子。

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

1. 比特币节点后端-区块链管理

区块链管理的代码逻辑都在main.cpp程序中实现。主要包括4个部分:

下载区块链:在比特币全节点第一次加入网络运行时,先要下载并验证整条区块链. “区块报头先行”(header first),一个初始区块链下载方式,新的节点先从邻节点下载所有的区块报头,再并行地从多个邻节点同时下载不同区间的区块大大提升了整条区块链的下载速度。

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

区块链验证:在区块链管理中,连接区块函数ConnectBlock()是一个检测“双花”交易的关键.该函数对新接收的区块中的所有交易进行检测,验证是否每个交易的比特币来源都能在当前的“尚未花比特币”(Unspend Transaction Output,UTXO)记录中找到匹配。

比特币后端全解析

重组区块链:当节点发现网络中有一条不基于它当前区块链的一条更长区块链时,它需要断开现有的区块并对区块链进行重组。

大部分重组只是一个区块的重组,多发生在不同矿工几乎同时挖到合法的区块时。

断开区块、重组区块链涉及UTXO更改,被断开的区块中交易会回退到交易内存池,这个时候“回滚”记录就可以用来回滚断开区块中的交易。

2.  比特币节点后端-区块验证

· 交易验证模块会基于以下条件检查收到的比特币交易是否合规:
· 交易的格式是数据结构必须正确
· 交易的输入和输出不能为空;(Coinbase交易没有输入)。
· 交易的大小不能超过定义的区块最大值MAX_BLOCK_SIZE(最早为1MB,后来逐渐调整)。
· 每个交易的输出,以及所有输出的合计,必须在一定范围内,也就是大于0,小于2100万。
· 交易输入的哈希值不能为零,不应该转Coinbase交易。
· nLockTime小于等于INT_MAX。
· 交易的字节大小要等于或大于100。
· 交易的签名操作数要小于签名操作的上限。
· 锁定脚本(scriptPubkey)必须是标准格式。
· 和收到的交易相匹配的交易必须能在当前交易池或是主链上某个区块中找到。
· 对交易的每个输入,如果其对应的UTXO输出能在当前交易池中找到,该交易必须拒绝(双花交易),因为当前交易池是未经记录在区块链中的· 交易,而交易的每个输入应该来自确认的UTXO,如果在当前交易池中找到,那就是双花交易。
· 对交易的每个输入,如果其对应的UTXO输出不能在主链或当前交易池中找到,该交易是一个孤儿交易,应将该交易放入孤儿交易池中。
· 对交易中的每个输入,如果其对应的UTXO输出是一个挖矿初始(coinbase)交易,该初始交易应该获得100个确认区块的确认(普通交易是6个确认区块的确认)。
· 对交易中的每个输入,其对应的输出必须是UTXO(存在且没被花掉)。
· 用对应的输出交易来获得输入的值,检查每个输入的值及其合计,应该在允许的区间(0~2100万比特币)。
· 如果一个交易的输入合计小于输出总计,则拒绝该交易。
· 如果交易的费用太低,则拒绝该交易。
· 每个输入的解锁脚本(unlocking script)必须和相应输出的锁定脚本(locking script)共同验证交易的合规性。

最后这个检查是比特币平台设计的精髓,比特币的一个很大的创新是依靠脚本来验证交易的合法性,即每一个将要花掉的比特币必须有相应的来源。

锁定脚本是由一连串堆栈命令和公钥哈希组成,公钥哈希即RIPEMD160(SHA256(公钥)),大小20字节;锁定脚本格式为:

OP_DUP OP_HASH160 <PubkeyHash> OP_EQUALVERIFY OP_CHECKSIG 

解锁脚本是由签名与公钥组成,这就保证了必须拥有私钥的用户才能对某一笔交易进行解锁.解锁脚本格式为:<Sig> <PubKey>

比特币是将解锁脚本和锁定脚本串起来一同执行,如果最后结果是逻辑值“真”(TRUE),则验证该交易的比特币来源是合法的. 脚本执行的过程和每个脚本执行后的堆栈状态如下:

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

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

· 脚本OP_EQUALVERIFY把堆栈头端的两个哈希值都弹出,并进行比较.如果不一样,验证就出错,交易不合法.如果验证通过,堆栈只剩下<sig>和<pubKey>。

· 最后脚本OP_CHECKSIG将两者弹出,执行检查签名的脚本,验证该交易签名是否是由拥有该公钥对应用户用其私钥签的,如果是,交易就是合法交易,否则就是不合法交易。


比特币后端全解析

3.  比特币节点后端-内存池管理

比特币内存池(mempool)管理也就是交易池管理。

节点将通过验证的交易放在一个交易池中,准备放在一个挖到的区块中。当准备挖矿时,它按一定的优先级次序从交易池中选出交易。

优先级是按交易中的输入对应的UTXO的“链龄”和交易额的大小来划分的。越老UTXO的交易以及交易额越大的交易优先级会越高。

优先级(Priority)采用以下公式计算:Priority=Sum(Value of input×InputAge)/ Transaction Size,其中Value of input是按比特币基础单位(satoshi,聪)计算,1个satoshi等于一亿分之一(10-8)个比特币。InputAge按已在链上记录该交易的区块为起点,按后面有多少个区块来计算,也就是计量该区块在区块链的“深度”。交易的大小以字节为单位。

当区块填满后,剩下的交易会留在内存池,等待下一个区块的到来。随着它们的“链龄”的逐渐增加,它们以后被选中的几率也会逐渐增加。

内存池的比特币的交易不会过期,但是内存池的交易不保存在硬盘上,当挖矿节点重启时,内存池的交易会被清空。

如果在一定时间内一个交易一直不能被矿工包括在区块链上,钱包软件需要重新发送该交易,可以附上较高的交易费,获得优先权。

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

4.  比特币节点后端-邻节点管理

当一个新比特币节点做初始启动(bootstrap)的时候,它需要发现网络中的其他节点,并与至少一个节点连接。

一般是与一个已知的节点在8333端口建立TCP连接。

连接的“握手”流程发送一个版本信息,包括:P2P协议版本,本节点支持的服务,当前时间,对方节点IP地址,本节点IP地址,比特币软件版本,以及本节点当前区块链的长度。

对方节点收到握手信息后会回复一个收到确认的信息。

新节点如何发现邻节点:

第一个办法是用一些“DNS种子”查询DNS。

第二个办法是直接把一个已知的邻节点作为种子节点,然后通过它发现更多的邻节点。

如果一个连接上一段时间内没有信息交互,节点会定期发一些信息去维护连接。

如果一个节点和邻节点的连接在超过90分钟里没有联系,该邻节点会被认为下线,节点会寻找一个新的邻节点来进行连接。

比特币网络能动态地调节节点的连接,以保证比特币网络的正常运行。

5.  比特币节点后端-共识管理

比特币里广义的共识管理(Consensus)应该包括挖矿、区块验证和交易验证规则。

由于比特币的关键是在陌生P2P环境建立共识机制,因此共识管理至关重要。

在比特币0.12.0版本中,一部分共识管理的代码已经移到consensus子目录。

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

6.  比特币节点后端-规则管理

比特币的共识规则是所有节点都必须遵守的规则(policy),而每个节点可以采用一些共识规则以外的个性化规则(比如一个节点可以拒绝保存、中转大于200KB的交易)。这部分的规则由规则管理模块实现,目前放在policy子目录中。

7.  比特币节点后端-密码模块

密码模块(Crypto)主要是处理比特币地址,采用RIMEMD和SHA-256算法以及Base-58编码来生成比特币地址。

比特币的公钥是通过私钥产生的,然后采用Secure Hash Algorithm(SHA)算法SHA256和RACE Integrity Primitives Evaluation Message Digest(RIPEMD)算法RIPEMD160对公钥进行处理,最后通过Base58编码形成比特币地址。

8.  比特币节点后端-签名模块

比特币采用椭圆曲线数字签名算法(ECDSA)来实现数字签名以及生成公钥。ECDSA是一种非对称密码算法,是基于椭圆曲线离散对数问题困难性的一种签名算法。

9.  比特币节点后端-脚本引擎

比特币的脚本语言是一种专门设计的、与“Forth”类似的、基于堆栈的编程脚本语言。

基于堆栈的语言的指令只按顺序执行一次,也就是说没有循环或跳转指令, 因此脚本的指令数可以决定一个程序运行时间的上限和所需的内存上限。

比特币的脚本语言非常小,只能有256个指令,每个指令是一个字节长。这256个指令中,75个是保留指令,15个已废弃。

脚本引擎是校验交易的运算平台,从对解锁脚本和锁定脚本的自动执行校验可以看出该引擎的重要作用。

新版本的比特币将脚本引擎放在script子目录中,将来可以变成可插拔引擎,使得引入新的功能更强大的引擎更为方便,不影响原有比特币的代码。

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

比特币通常使用的指令如下:


比特币后端全解析

10.  比特币节点后端-挖矿

比特币最早的挖矿程序是cpuminer,是通过CPU来挖矿的。

挖矿设备经过CPU、GPU、FPGA,到现在基本都使用ASIC挖矿设备。

针对SHA256挖矿算法优化的ASIC挖矿设备比其他挖矿设备有着性能上无可比拟的优势。

Bfgminer程序支持FPGA和ASIC挖矿设备,是目前比较流行的挖矿程序. 最新代码见: http://www.bfgminer.org/

那如何把网络的出块速度稳定在10分钟一个呢?比特币网络是通过调整挖矿难度的目标来达到这个目的. 难度计算公式是: New Difficulty = Old Difficulty × (Actual Time of Last 2016 Blocks / 20160minutes)

11.  比特币节点后端-HTTP/JSON RPC服务端

比特币在启动的时候,初始化程序init.cpp会启动HTTP/JSON RPC服务端的线程组。

该组件对外提供HTTP和JSON RPC的接口,外部程序可以通过JSONRPC接口来调比特币的API,达到控制比特币节点的功能。

该接口缺省是仅接收来自本机的客户端的连接请求。

12.  比特币节点后端-Berkeley DB和Level DB数据库

Berkeley DB是一个开源的文件数据库(Sleepycat Software公司开发),介于关系数据库与内存数据库之间,使用方式与内存数据库类似,它提供的是一系列直接访问数据库的函数,主要用来备份用户的密钥。

Berkeley DB是一个轻巧而又性能高的嵌入式数据库,可以保存任意类型的键/值对,可以为一个键保存多个数据,可以支持数千个并发线程同时操作数据库,支持最大256TB的数据.

Level DB用来存储区块的索引和UTXO(未花的比特币交易输出)记录,是一个Google实现的非常高效的键值(Key Value)数据库,目前的版本1.2能够支持几十亿级别的数据量。

Level DB的数据是冗余数据,可以用原始区块链数据来重建.如果没有Level DB的数据,比特币的校验和其他操作都会变得非常缓慢。

13.  比特币节点后端-P2P网络管理

P2P网络管理的代码主要是在P2P网络上实现和其他邻节点的通信功能。这些通信功能包括:发现邻节点;连接并管理与邻节点的Socket连接;与邻节点交换不同的P2P消息(包含区块和交易);有时在特殊情况下,会禁止异常行为的邻节点的连接。

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

比特币节点的缺省配置是主动连接8个邻节点,同时允许最多125个其他节点发起的连接请求。

比特币防止拒绝服务攻击(DoS)的方法主要是禁止异常行为邻节点的连接。如果一个邻节点传送非常明显的错误信息,该连接将被断开,而且其IP地址会被禁止,其重新连接申请会被拒绝。

比特币节点按功能分有几种:全功能节点、基础全节点和SPV节点

全功能节点带有钱包、RPC服务端,具有挖矿功能和进行节点校验区块和交易,并把区块和交易中转给与之相连接的邻节点。

基础全节点也做区块和交易的交易和中转,但不挖矿,不带钱包和RPC服务端。

SPV(Simplify Payment Verification)节点信任别的节点来对区块和交易作校验。

14. 比特币节点后端-队列管理

比特币采用Zero MQ作为消息队列管理和消息分发工具。Zero MQ号称是“史上最快的消息队列”,是基于C语言开发的。

ZMQ是一个简单好用的传输层,提供像框架一样的一个socket library,它使得Socket编程更加简单、简洁,性能更高。

ZMQ是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。

ZMQ不是一个服务器,更像一个底层的网络通信库,在Socket API之上做了一层封装,将网络通信、进程通信和线程通信抽象为统一的API接口。

比特币后端组成就说到这里啦,经历了第一阶段的演化,区块链的发展进入了“可编程”时代,区块链也逐渐往产品化、实用性发展,我们下一节课将讲述区块链2.0——可编程区块链,敬请期待。

声明: 本文由入驻基智地平台的作者撰写,观点仅代表作者本人,不代表基智地立场;基智地发布此信息的目的在于传播更多信息,与本站立场无关。

发表评论

邮箱地址不会被公开。 必填项已用*标注