NULS设计文档解读——区块管理模块

为什么需要区块管理模块

区块链是一种加密的分布式记账技术,由全网所有节点共同维护区块链上的交易记录,最终形成一个分布式账本。在这个账本中,所有交易不是简单的堆叠在一起,而是按照交易时间,以 区块 为单元,相互串联起来的。

所以,我们也可以这样理解: 交易组成区块,区块串联起来,组成账本。

区块作为区块链中保存交易的基本单元,有其对应的业务职责和处理逻辑。例如,当节点刚加入某条链时,需要先同步区块数据;节点将交易打包成区块之后,需要对区块进行签名、广播、验证等操作;节点不仅要自己参与打包区块,还要验证和存储其他节点广播给自己的区块。

NULS2.0是一个通用的区块链基础设施,采用的是微服务架构,会根据区块链系统的业务,将底层拆分成不同的模块。 区块链系统对区块的处理,有许多共性,将系统中跟区块相关的处理逻辑,单独用一个功能模块实现,不仅可以很好地为其他模块提供统一的区块数据服务,还能进一步提升系统的模块化程度。

区块管理模块功能

区块管理模块功能体现了该模块的作用,也是开发该模块需要达到的目标。读者阅读本文档的目标是,理解区块管理模块具有哪些功能,以及这些功能的实现流程。

在NULS2.0中,共识模块负责发起区块打包操作,从区块打包开始,一个区块处理的全流程就开始了。根据区块的处理流程,我们将区块管理模块的主要功能分为:区块验证、区块存储、区块广播、区块转发、区块同步、分叉链切换。

NULS设计文档解读——区块管理模块-NULS一个可定制的区块链基础设施!

下面我们详细介绍以上功能。

区块验证

当共识模块组装好区块,完成区块签名之后,会将区块发送给区块管理模块,由区块管理模块进行区块验证。区块验证主要分为基础验证、分叉/孤儿区块验证。

基础验证: 当区块管理模块收到共识模块发送来的区块信息之后,首先是对协议版本、时间戳、梅克尔哈希等区块头信息,进行正确性验证。

分叉/孤儿区块验证:

要明白分叉/孤儿区块验证,就需要先明白区块与链的四种关系,我们假设存在区块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作为后缀,进行区分。一个完整的区块由区块头和交易组成,区块头与交易分别进行存储,区块头存储在区块管理模块中,交易存储在交易管理模块中。在区块管理模块中,主链区块存储表的数据结构如下图所示:

NULS设计文档解读——区块管理模块-NULS一个可定制的区块链基础设施!

NULS设计文档解读——区块管理模块-NULS一个可定制的区块链基础设施!

在存储主链区块时,主要有以下三个流程:

  • 进行 区块验证 ,验证通过,执行下面的流程;
  • 区块管理模块发起交易业务验证,交易模块执行交易验证的相关流程,对交易的业务规则、合法性等进行验证;
  • 以上验证全部通过,由其他模块分别对自己需要负责的交易进行执行,执行成功,各个模块保存自己需要保存的数据,区块管理模块根据主链区块存储表的数据结构,保存相关数据。如果执行失败,区块管理模块回滚相关区块数据,其他模块回滚相关交易数据。

存储分叉链/孤儿链区块

存储分叉链/孤儿链区块与存储主链区块不同的是,内存中会缓存所有分叉链与孤儿链对象,但是只记录起始高度、起始Hash、结束高度、结束Hash等关键信息,在硬盘中,会缓存全量的区块数据。

这样做是因为,当需要进行分叉链切换、清理分叉链等操作时,只需读取一次数据库即可。

在进行存储时,不同的链存储在不同的表中,表名加上chainID作为后缀,进行区分。在区块管理模块中,分叉链/孤儿链区块存储表的数据结构如下图所示:

NULS设计文档解读——区块管理模块-NULS一个可定制的区块链基础设施!

进行区块验证之后,如果是分叉/孤儿块,会根据上面的表结构存储相关数据,同时,内存中也会缓存上面说到的相关数据,但因为不是主链区块,所以还不能进行最终的确认存储,需要根据后续出块的情况,进行分叉链切换、清理分叉链等操作。

区块广播

当进行区块打包的节点完成区块验证之后,就需要将自己打包的区块广播给其他节点,让其他节点进行验证和存储。区块广播主要分为以下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”: “” }

区块管理模块启动时需要依赖的模块

NULS设计文档解读——区块管理模块-NULS一个可定制的区块链基础设施!