摘要:NULS,让区块链更简单!
为什么需要区块管理模块
区块链是一种加密的分布式记账技术,由全网所有节点共同维护区块链上的交易记录,最终形成一个分布式账本。在这个账本中,所有交易不是简单的堆叠在一起,而是按照交易时间,以区块为单元,相互串联起来的。
所以,我们也可以这样理解:交易组成区块,区块串联起来,组成账本。
区块作为区块链中保存交易的基本单元,有其对应的业务职责和处理逻辑。例如,当节点刚加入某条链时,需要先同步区块数据;节点将交易打包成区块之后,需要对区块进行签名、广播、验证等操作;节点不仅要自己参与打包区块,还要验证和存储其他节点广播给自己的区块。
NULS2.0是一个通用的区块链基础设施,采用的是微服务架构,会根据区块链系统的业务,将底层拆分成不同的模块。区块链系统对区块的处理,有许多共性,将系统中跟区块相关的处理逻辑,单独用一个功能模块实现,不仅可以很好地为其他模块提供统一的区块数据服务,还能进一步提升系统的模块化程度。
区块管理模块功能
区块管理模块功能体现了该模块的作用,也是开发该模块需要达到的目标。读者阅读本文档的目标是,理解区块管理模块具有哪些功能,以及这些功能的实现流程。
在NULS2.0中,共识模块负责发起区块打包操作,从区块打包开始,一个区块处理的全流程就开始了。根据区块的处理流程,我们将区块管理模块的主要功能分为:区块验证、区块存储、区块广播、区块转发、区块同步、分叉链切换。
下面我们详细介绍以上功能。
区块验证
当共识模块组装好区块,完成区块签名之后,会将区块发送给区块管理模块,由区块管理模块进行区块验证。区块验证主要分为基础验证、分叉/孤儿区块验证。
基础验证:当区块管理模块收到共识模块发送来的区块信息之后,首先是对协议版本、时间戳、梅克尔哈希等区块头信息,进行正确性验证。
分叉/孤儿区块验证:
要明白分叉/孤儿区块验证,就需要先明白区块与链的四种关系,我们假设存在区块a和链A,它们可能存在的四种关系如下:
1、区块a在链A上已经存在,说明区块a在链A中属于重复区块,将会被直接丢弃;
2、区块a属于链A上的分叉块,这意味着链A上,此时在同一高度存在不低于2个有可能会被记录上链的区块(出现了分叉链),此时区块管理模块会在内存中存储区块a的区块头信息,等待后续处理;
3、区块a能与链A之前的区块能串联起来,说明区块a是可以直接进行存储的;
4、区块a与链A以及其他分叉链都没有关联关系,说明区块a是孤儿块。
在进行分叉/孤儿区块验证的时候,会依次根据以上4种情况进行验证:
如果验证结果是重复区块,直接丢弃,否则,进行第2种情况的验证;如果验证结果是分叉块,在内存中暂存分叉块的区块头信息,否则,进行第3种情况的验证;如果验证结果是能与主链串联起来的块,则等待执行区块存储流程,否则,进行第4中情况的验证;如果验证结果是孤儿块,内存中会暂时缓存孤儿块的区块头。
区块存储
在NULS2.0中,无论是节点自己打包的区块,还是其他节点广播或转发过来的区块,都会先进行区块验证,然后再进行存储。
区块存储主要分为两种情况:主链区块存储和分叉链/孤儿链区块存储。
存储主链区块
在NULS2.0中,支持多链并行和跨链,所以NULS主网需要支持多条链的数据存储,不同链的区块存到不同的表中,表名加chainID作为后缀,进行区分。一个完整的区块由区块头和交易组成,区块头与交易分别进行存储,区块头存储在区块管理模块中,交易存储在交易管理模块中。在区块管理模块中,主链区块存储表的数据结构如下图所示:
在存储主链区块时,主要有以下三个流程:
进行区块验证,验证通过,执行下面的流程;区块管理模块发起交易业务验证,交易模块执行交易验证的相关流程,对交易的业务规则、合法性等进行验证;以上验证全部通过,由其他模块分别对自己需要负责的交易进行执行,执行成功,各个模块保存自己需要保存的数据,区块管理模块根据主链区块存储表的数据结构,保存相关数据。如果执行失败,区块管理模块回滚相关区块数据,其他模块回滚相关交易数据。
存储分叉链/孤儿链区块
存储分叉链/孤儿链区块与存储主链区块不同的是,内存中会缓存所有分叉链与孤儿链对象,但是只记录起始高度、起始Hash、结束高度、结束Hash等关键信息,在硬盘中,会缓存全量的区块数据。
这样做是因为,当需要进行分叉链切换、清理分叉链等操作时,只需读取一次数据库即可。
在进行存储时,不同的链存储在不同的表中,表名加上chainID作为后缀,进行区分。在区块管理模块中,分叉链/孤儿链区块存储表的数据结构如下图所示:
进行区块验证之后,如果是分叉/孤儿块,会根据上面的表结构存储相关数据,同时,内存中也会缓存上面说到的相关数据,但因为不是主链区块,所以还不能进行最终的确认存储,需要根据后续出块的情况,进行分叉链切换、清理分叉链等操作。
区块广播
当进行区块打包的节点完成区块验证之后,就需要将自己打包的区块广播给其他节点,让其他节点进行验证和存储。区块广播主要分为以下3个主要流程:
在完成区块验证之后,根据区块信息,将区块头和交易哈希列表组装成SmallBlockMessage,广播给与自身相连的节点;收到广播消息的节点,根据交易哈希列表,判断哪些交易是本地没有的,然后根据交易哈希列表,组装HashListMessage发送给广播节点,从广播节点获取本地没有的交易;广播节点收到HashListMessage消息之后,组装TxGroupMessage,将请求方没有的交易,发送给请求方。请求方收到包括区块头、区块交易等完整区块信息之后,区块广播完成。
区块转发
区块转发是相对区块广播而言的,如果节点A打包了一个区块a,节点A将区块a广播给了节点B,那么节点B在完成区块验证之后,将区块a发送给节点C的过程,就被称为区块转发。
区块转发主要流程:
进行区块转发的源节点,对区块进行验证之后,使用区块哈希组装HashMessage消息,转发给与自己相连的目标节点;目标节点在收到转发的HashMessage之后,会根据区块哈希,判断是否收到过对应的SmallBlock:如果没有收到过,根据区块哈希,先返回GetSmallBlockMessage消息给源节点,获取SmallBlock;如果收到过,直接进入下面的流程。目标节点判断 SmallBlock中的所有交易,本地是否都已全部保存:如果有交易本地未保存,就根据交易信息,组装HashListMessage发送给源节点,请求获取缺失的交易。源节点收到消息后,会将缺失的交易发送给目标节点;如果交易都已保存在本地,直接进入下面的流程。根据SmalBlock和完整的交易数据,目标节点会组装完整的区块,并进行保存;目标节点重复以上流程,向除了源节点以外的节点进行区块转发。
区块同步
当有新的节点被创建成功时,节点需要先同步区块,让本地的区块高度与网络中大部分节点保持一致。如果节点出现故障,中途掉线,重新启动之后,也需要先同步区块。区块同步主要分为以下4个流程:
当节点与网络正常连接之后,会从其他节点中获取所有区块,直到达到主网最新的区块高度,节点会判断本地的区块高度,是否高于网络中的区块高度:如果高于网络中的区块高度,则不进行区块同步;如果低于网络中的区块高度,则进行区块同步,进入下面的流程。根据本地区块高度与网络中的区块高度的高度差,判断需要从哪一个高度开始同步区块,然后将需要同步的区块分成若干个区块范围,向与自己建立连接的节点,分别发送不同范围高度的区块的下载请求;将从其他节点下载的区块,存放到一个集合中,直到所有区块下载成功。如果中途,有区块下载失败,会尝试重新下载;所有区块下载成功之后,根据高度依次从集合中取出区块,进行区块验证和存储。
分叉链切换
在NULS2.0中,当出现分叉链时,如果分叉链的高度已经高于主链,并超过系统设定的链切换阀值,将会进行主链与分叉链的切换,也就是遵循最长链原则。分叉链切换的主要流程如下:
找到主链与分叉链的分叉点,根据分叉点,找出主链与分叉链的链切换路径;回滚主链上处于链切换路径上的区块,回滚掉的区块组成新的分叉链,链接到新主链上;依次添加链切换路径上的区块,组成新的主链。
以上流程仅凭文字,很难理解,我们通过图示,来加以解释。
如上图所示,假设分叉链1与分叉链2,都达到了链切换阀值的高度,此时将启动分叉链切换:
首先,需要找到链切换路径。分叉链1的分叉点,到分叉链2的分叉点之间的区块,属于分叉链1中,需要切换到新主链的区块,分叉链2从分叉点开始,一直到自己最新高度的区块,都需要切换到新主链上。确定了新主链的区块,就需要回滚原主链上的区块,所以原主链上,从分叉链1的分叉点开始往后的所有区块,回滚后组成新的分叉链;分叉链1,从分叉链2的分叉点开始,一直到分叉链1的最新高度的区块,组成另一条新的分叉链。图中标有红色阴影部分的区块,都属于链切换路径上的区块,将他们串联在一起,组成新的主链。
其他
消息协议
区块高度信息HeightMessage
消息说明:用于”区块同步”过程中重试下载
Length Fields Type Remark 64 heighrt int64 区块高度
单个摘要消息HashMessage
消息说明:用于”区块转发”,”孤儿链维护”功能
Length Fields Type Remark 32 hash byte[] 交易hash
摘要列表消息HashListMessage
消息说明:用于”区块转发”功能
Length Fields Type Remark 32 blockHash byte[] 区块hash ? hashLength VarInt 数组长度 32 hash byte[] 交易hash
区块广播消息SmallBlockMessage
消息说明:用于”区块转发”、”区块广播”功能
Length Fields Type Remark ? preHash byte[] preHash ? merkleHash byte[] merkleHash 32 time Uint32 时间 32 height Uint32 区块高度 32 txCount Uint32 交易数 ? extendLength VarInt extend数组长度 ? extend byte[] extend 32 publicKeyLength Uint32 公钥数组长度 ? publicKey byte[] 公钥 ? signBytesLength VarInt 签名数组长度 ? signBytes byte[] 签名 ? txHashListLength VarInt 交易hash列表数组长度 ? txHashLength VarInt 交易hash数组长度 ? txHash byte[] 交易hash
高度区间消息HeightRangeMessage
消息说明:用于”区块同步”功能
Length Fields Type Remark 64 startHeight int64 起始高度 64 endHeight int64 结束高度
完整的区块消息BlockMessage
消息说明:用于”区块同步”
Length Fields Type Remark 32 requestHash byte[] requestHash 32 preHash byte[] 上一个区块的hash 32 merkleHash byte[] merkleHash 32 time Uint32 时间 32 height Uint32 区块高度 32 txCount Uint32 交易数 ? extendLength VarInt extend数组长度 ? extend byte[] extend 32 publicKeyLength Uint32 公钥数组长度 ? publicKey byte[] 公钥 ? signBytesLength VarInt 签名数组长度 ? signBytes byte[] 区块签名 16 type uint16 交易类型 32 time uint32 交易时间 ? remarkLength VarInt 备注数组长度 ? remark byte[] 备注 ? txDataLength VarInt 交易数据数组长度 ? txData byte[] 交易数据 ? coinDataLength VarInt CoinData数组长度 ? coinData byte[] CoinData ? txSignLength VarInt 交易签名数组长度 ? txSign byte[] 交易签名 1 syn byte 是否是为区块同步请求的区块
请求完成消息CompleteMessage
消息说明:通用消息,用于异步请求,标志异步请求处理结束。
Length Fields Type Remark 32 Hash byte[] Hash 1 success byte 成功标志
交易列表的消息TxGroupMessage
消息说明:用于”区块转发”
Length Fields Type Remark 32 blockHash byte[] blockHash ? txCount VarInt 交易数 16 type uint16 交易类型 32 time uint32 交易时间 ? remarkLength VarInt 备注数组长度 ? remark byte[] 备注 ? txDataLength VarInt 交易数据数组长度 ? txData byte[] 交易数据 ? coinDataLength VarInt CoinData数组长度 ? coinData byte[] CoinData ? txSignLength VarInt 交易签名数组长度 ? txSign byte[] 交易签名
模块配置
{
“forkChainsMonitorInterval”: 10000,
“orphanChainsMonitorInterval”: 10000,
“orphanChainsMaintainerInterval”: 5000,
“storageSizeMonitorInterval”: 300000,
“networkResetMonitorInterval”: 300000,
“nodesMonitorInterval”: 5000,
“txGroupRequestorInterval”: 1000,
“txGroupTaskDelay”: 3000,
“testAutoRollbackAmount”: 0,
“blockMaxSize”: 5242880,
“resetTime”: 180000,
“chainSwtichThreshold”: 3,
“cacheSize”: 1000,
“heightRange”: 1000,
“maxRollback”: 1000,
“consistencyNodePercent”: 60,
“minNodeAmount”: 1,
“downloadNumber”: 10,
“extendMaxSize”: 1024,
“validBlockInterval”: 60000,
“smallBlockCache”: 6,
“orphanChainMaxAge”: 10,
“singleDownloadTimeout”: 10000,
“waitNetworkInterval”: 5000,
“cachedBlockSizeLimit”: 20971520,
“genesisBlockPath”: “”
}
区块管理模块启动时需要依赖的模块