快照导出为什么只导了几个ColumnFamily
Starcoin目前用到的ColumnFalimy列表在 storage/src/lib.rs里面
ColumnFamily介绍
1.accumulator
BLOCK_ACCUMULATOR_NODE_PREFIX_NAME
block_accumulator对应的column family对应的Key是HashValue(AccmulaorNode.hash()),Value对应的AccumulatorNode
(使用BCS encode)
TRANSACTION_ACCUMULATOR_NODE_PREFIX_NAME
txn_accumulator
对应的column family对应的
Key是HashValue(AccmulaorNode.hash()),Value对应的AccumulatorNode
(使用BCS encode)
2.block
BLOCK_PREFIX_NAME 对应的key是HashValue(BlockHeader.id()), value是Block(使用BCS encode),
BLOCK_HEADER_PREFIX_NAME对应的key是HashValue(BlockHeader.id()), value是BlockHeader(使用BCS encode)
BLOCK_BODY_PREFIX_NAME对应的key是HashValue(BlockHeader.id()), value是BlockBody(使用BCS encode), 存在冗余,节省空间已经 废弃
BLOCK_TRANSACTIONS_PREFIX_NAME
, 对应的key是HashValue(BlockHeader.id()), value是Vec<HashValue>(HashValue对应Transaction
.id())
BLOCK_TRANSACTION_INFOS_PREFIX_NAME
对应的key是HashValue(BlockHeader.id()), value是Vec<HashValue>(HashValue对应的TransactionInfo
.id())
3.block_info
BLOCK_INFO_PREFIX_NAME
对应的key是HashVlaue(BlockInfo.block_id), value是BlockInfo(使用BCS encode)
4.chain_info
CHAIN_INFO_PREFIX_NAME
对应几个特殊key,
STARTUP_INFO_KEY
这里主要是作为事务用的,我们的rocksdb没有完全使用rocksdb的write_batch写入,所以STARTUP_INFO_KEY作为一个事务标志确定写完那个高度区块,
GENESIS_KEY获取genesis
STORAGE_VERSION_KEY获取当去db的版本号,结合upgrade来添加删除column family
SNAPSHOT_RANGE_KEY用来处理snapshot导入用的
只导出 部分ColumnFamily原因
实际上只导出了BLOCK_ACCUMULATOR_NODE_PREFIX_NAME
, TRANSACTION_ACCUMULATOR_NODE_PREFIX_NAME
, BLOCK_PREFIX_NAME, BLOCK_INFO_PREFIX_NAME
, STATE_NODE_PREFIX_NAME
这几个columnFamily
快照导出的目的就是导出某个高度的状态,不含历史状态记录,比如导出高度为10000块的历史状态,第10000块里Alice的余额加了100块,你想读出第9999块时候Alice的余额是没有相关记录。
为什么只导出这几个ColumnFamily和交易执行的写入有一定关系
交易执行时候,会有两类树,一个是Merkle Tree, 一个是Spasre Merkle Tree,前者分别对应了BLOCK_ACCUMULATOR_NODE_PREFIX_NAME
和TRANSACTION_ACCUMULATOR_NODE_PREFIX_NAME
, 后者对应的就是STATE_NODE_PREFIX_NAME
1.Merkle Tree相关
Executor模块执行整个Block交易的最后会写入一个StartupInfo信息,这个Key是STARTUP_INFO_KEY(注意这是个常量), Value是个Hash值记录了当前Chain存储的最新高度的信息。这个Hash值实际上是BLOCK_INFO_PREFIX_NAME对应的Key, 是某个Block的Header的Hash值,对应的Value就是BlockInfo相关
这个BlockInfo有四个值block_id(这个就是对应的key), total_difficulty(第一块到当前块所有难度), txn_accumulator_info
和block_accumulator_info
,
其中block_accumulator_info
和txn_accumulator_info
对应Block的Merkle Tree对应的所有信息,Transaction的MerKle Tree对应的所有信息,我们这里导出基于Block Merkle Tree的root节点的整棵树(AccumulatorInfo
里面的accumulator_root),实际上就是导出accumulator_root的所有叶子节点,就是对应BLOCK_ACCUMULATOR_NODE_PREFIX_NAME
, Transaction Merkle Tree类似。
基于Block Merkle Tree我们能找到基于这棵树下所有高度的Block,Transaction也类似。
Chain在启动时候会读取ChainInfo,这个ChainInfo其实是读取genesis_hash和StartUpInfo合并下,就知道当前链相关信息了
2.Sparse Merkle Tree
整个交易执行经过Executor模块调用MoveVM计算完成后会产生交易执行的结果,这个就是writeset,可以认为是(key, value)的列表,就是更新了哪些key, 删除了哪些key,会把更新删除这些操作存到Sparse Mekle Tree内,而这颗当前高度的Sparse Merkle Tree会存到STATE_NODE_PREFIX_NAME的column里,(历史状态也会存到STATE_NODE_PREFIX_NAME), 这里只导出了当前高度对应的Sparse Merkle Tree的Leaf Node,
根据所有Leaf Node可以重建Sparse Merkle Tree当前状态
3. Sparse Merkle Tree为什么导入了2个高度
区块再导入快照后,进行P2P节点同步新的区块时候,需要verify_block这个步骤(具体有verify_header和verify_uncle),这里引入了2个概念
Epoch和uncle,Epoch是调整难度的,这里Epoch有start_block_number和end_block_number,
比如我们导入区块10000(new_header_height)高度的状态,
再同步下一个交易时候需要校验uncle的逻辑,(这块逻辑在BlockVerifer的verify_uncles函数里面)
这里校验uncle的里面有个逻辑,就是需要获取uncle对应的SPARSE Merkle Tree状态, 执行uncle block的verify_header,具体如下
// uncle's parent exists in current chain is checked in `can_be_uncle`, so this fork should success.
let uncle_branch = current_chain.fork(uncle.parent_hash())?;
Self::verify_header(&uncle_branch, uncle)?;
uncle_ids.insert(uncle_id);
意思就是[Epoch.start_block_number, new_header_height)之间的状态我们可能都的保存(后面可以看到这里不正确),用到的数据太多了
所以我们这里只导出Epoch.start_block_number这个高度,以Epoch为分割线保存
但是这里有个边界条件关系到打包的uncle_blocks的逻辑,这里在create_block_template
里面find_uncles
里面的逻辑,Epoch里面有个max_uncles_per_block
字段,在stcscan.io浏览器里面可以看到目前是2,于是当new_header_height == Epoch.start_block_number + 1时候,他的uncle可能是
Epoch.start_block_number + 1 -2 是上一个Epoch的end_block_number,所以这里需要把上一个Epoch的end_block_number状态也保存下来
总结,其他的column family都是execute_block_and_save
执行时候生成的附加信息,和快照导入后同步新交易没啥直接关系。如果快照导入成功后,这里有个线程执行[1, new_header_height -1]区块的交易,就能保存所有历史信息。