长安链源码学习--账本 (七)

作者:明神特烦恼
公众号:明神特烦恼

在共识完成后进行区块提交阶段,对调用账本模块来对区块以及交易等进行持久化,本章节将一起分析一下账本模块实现过程。

带着问题读代码:
1)存储方式有哪些?
2)账本存储哪些内容,有哪些索引?
3)在写如多张数据库表,如果发生意外掉电,如何保证原子性?如何回滚?

第一个问题:存储方式有哪些?

   长安链支持nosql、sql两种形式的存储,其中nosql支持rocksdb、leveldb,sql支持mysql、sqlite。多种存储形式满足不同应用场景及业务要求。
   无论何种存储方式,上面已经高层封装,屏蔽掉底层存储逻辑,化简其他模块的操作复杂度,后面的分析以nosql为例。

const (
    UnknownDb EngineType = 0
    LevelDb   EngineType = 1
    RocksDb   EngineType = 2
    MySQL     EngineType = 3
    Sqlite    EngineType = 4
)

第二个问题:账本存储哪些内容,有哪些索引?(以nosql为例)

1.写块流程:

1)记录最新写入的块号lastBlockNumKeyStr

2)记录BlockHeightKeyBlockInfo关系:

  • BlockHeightKey = blockNumIdxKeyPrefix + blocknum
  • BlockInfo = 序列化(Header + Dag + TxIds + AdditionalData)

3)记录BlockHashKeyBlockHeight关系

  • BlockHashKey = blockHashIdxKeyPrefix + blockhash
  • BlockHeightKey

4)记录TxidKeyTxInfo 关系

  • TxidKey = txIDIdxKeyPrefix + txid
  • TxInfo = 序列化(Transaction)

5)记录BlockTxidKeyBlockHeightKey 关系

  • BlockTxidKey = blockTxIDIdxKeyPrefix + txid
  • BlockHeightKey

6)记录LastConfigKeyBlockHeightKey 关系(只有配置块记录该关系)

  • LastConfigKey = lastConfigBlockNumKey
  • BlockHeightKey

   账本模块通过上述记录的内容提供服务有:获取最后一个区块块号;通过块号获取块内容;通过块Hash获取块号;通过txid获取交易详情;通过txid获取其所在块号;获取最新配置块号。

2.写世界状态流程:

1)记录当前世界状态对应的最新块号stateDBSavepointKey

2)记录写入数据的KV值 Or 删除Key

  • Key:合约Name + WriteKey
  • Value:世界状态值
  • OP: 插入

Or

  • Key:合约Name + WriteKey
  • OP: 删除
3.写入历史数据库流程:

1)记录当前历史数据库对应的最新块号historyDBSavepointKey

2)记录操作的Key与块号、Txid关系

  • Key:合约Name + WriteKey + BlockHeight + Txid

3)记录操作账户与块号、Txid关系

  • Key:账号ID + BlockHeight + Txid

4)记录合约与块号、Txid关系

  • Key:合约名称 + BlockHeight + Txid
4. 写入合约结果流程:

1)记录当前合约结果数据库对应的最新块号:resultDBSavepointKey
2)记录RWSetsTxid与读写集之间的关系

  • RWSetsTxid:txRWSetIdxKeyPrefix + txid
  • 读写集:序列化(TxRWSet)

第三个问题:在写如多张数据库表,如果发生意外掉电,如何保证原子性?如何回滚?

   上面提到了多个流程,每个流程是原子操作,保证了事务一致性。但多个流程之间是非原子的,也就是世界状态写入完成,而写块流程还未完成,如果此时发生掉电(进程kill等),那么节点自身的数据将不完整,这是不被允许的。
   为解决掉电问题,长安链处理办法如下:
    - 同步写binlog,binlog非随机写入,机械硬盘执行速度较快。
    - 并发完成写块流程、写世界状态流程、历史数据库流程等。
    - 写入完成后,异步清理binlog。

   当进行crash后重启检测及恢复流程:
    - 创建存储Handler时判断binlog是否有内容,如果有下一步。
    - 获取binlog中记录最后一个log的块高度logSavepoint
    - 获取写块流程的最新块号lastBlockNumKeyStr,如果logSavepoint > lastBlockNumKeyStr,表示有区块数据未执行写块流程,调用CommitBlock进行写块。
    - 其他几个流程同理。

注意:
1) 这里要保证每个流程自身内部是原子的。
2) 恢复流程中并未删除binlog,这是因为binlog的处理是每100个块删除一次,并不需要实时删除,也不会产生存储泄露。
3)恢复流程没有回滚,只有继续写入。

你可能感兴趣的:(长安链源码学习--账本 (七))