数据存储
1.概述
存储模块负责持久化存储链上的区块、交易、状态、历史读写集等账本数据,并对外提供上述数据的查询功能。区块链以区块为单位进行批量的数据提交,一次区块提交会涉及到多项账本数据的提交,比如:交易提交,状态数据修改等,所以存储模块需要维护账本数据的原子性。长安链支持常用的数据库来存储账本数据,如LevelDB、BadgerDB、TikvDB、MySQL等数据库,业务可选择其中任意一种数据库来部署区块链。
账本数据主要分为5类:
区块数据,记录区块元信息和交易数据:
区块元数据包括:区块头、区块DAG、区块中交易的txid列表,additionalData等;
交易数据,即序列化后的交易体,为了提供对单笔交易数据的查询,所以对交易数据进行了单独存储。
状态数据,记录智能合约中读写的链上状态数据,即世界状态。
历史数据,长安链对每笔交易在执行过程中的状态变化历史、合约调用历史、账户发起交易历史都可以进行记录,可用于后续追溯交易、状态数据的变迁过程。
合约执行结果读写集数据,长安链对每笔交易在执行过程中的所读写的状态数据集进行了单独保存,方便其他节点进行快速的数据同步。
事件数据,合约执行过程中产生的事件日志
2.存储模块运行逻辑
针对上述5类账本数据,长安链分别实现了5个DB类,分别是:Block DB、State DB、History DB、Result DB和Contract Event DB。采用多个数据库之后,就需要维护数据库之间的数据一致性,避免仅有部分数据库提交后,发生程序中断而导致不同数据库间的数据不一致,因此,长安链引入了Block binary log组件来持久化存储区块的原始内容,用于重启过程中的数据恢复,类似于数据库中的预写式日志(wal)的功能。需要注意的是,历史数据、结果数据并不是每个节点必须保存的,节点可以根据自己的业务需要在配置文件中启用或者关闭历史数据库和结果数据库。
1.存储模块未开启区块文件存储时运行逻辑
2.存储模块开启区块文件存储时运行逻辑
3.区块提交流程
首先将序列化后的区块、读写集数据、以及最新的区块高度写入Block binary log,用于异常中断后的数据恢复。为了提高性能,加入一层cache,新区块提交请求在更新完Block binary log之后,再将区块数据写入cache,在更新完log和cache后,提交即可返回,由后台线程异步更新Block DB、State DB、ContractEvent DB、History DB和Result DB。
在Block DB中记录区块元信息与交易信息,其中交易信息以TxID作为主键存储,区块信息以BlockHeight作为主键存储,区块元信息中只记录交易ID列表,同时索引BlockHash到BlockHeight的映射关系。Block DB中额外记录了当前最新的区块高度(LastBlockHeight)作为checkpoint,用以重启后的数据恢复。
在State DB中保存state数据,key为合约名与对象主键的组合:<contractName,ObjectKey>,同时记录最新的区块高度(LastBlockHeight)作为checkpoint。
在History DB中记录交易产生的三种类型的索引:
状态变更历史,以<contractName,ObjectKey,TxId>为索引
合约调用历史,以<contractName,TxId>为索引
账户交易历史,以<accountId,TxId>为索引
在Result DB中记录交易的读写集,读写集以TxID作为key,同时记录最新的区块高度(LastBlockHeight)作为checkpoint。
在ContractEventDB中记录下交易结果的EventLog,并记录最新区块高度作为checkpoint。
4.账本恢复流程
如果区块正在提交过程中,节点因异常退出,节点在下次启动时存储模块会进入恢复流程:
分别从Block binary log、Block DB、State DB、ContractEvent DB、History DB、Result DB中获取最新的区块高度,以Block binary log中的区块高度作为基准高度,判断其他DB是否落后基准高度。
如果有某个DB落后基准高度,则从Block binary log中获取缺失的区块及读写集,依次提交到落后DB中。
所有DB同步到基准高度后,存储模块启动完成,节点进入正常流程。