快照导出为什么只导了几个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_NAMETRANSACTION_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_infoblock_accumulator_info,

其中block_accumulator_infotxn_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]区块的交易,就能保存所有历史信息。