美文网首页Hyperledger Fabric源码分析
Fabric源码分析-账本机制01

Fabric源码分析-账本机制01

作者: 史圣杰 | 来源:发表于2018-12-21 18:51 被阅读0次

    超级账本的最终目的是将交易记录打包为区块保存到账本中,账本模块用来保存区块,检索区块,记录账本的最终状态。本节介绍了Peer账本的初始化过程。

    1. 账本对象

    Fabric的orderer会将交易信息打包为Block,Peer会对Block进行校验,然后保存起来,最后修改key的最终状态,在此过程中,还会记录历史信息。因此,对于一个Peer账本来说,需要完成以下功能:

    • 使用Ledger维护整个账本,包括区块的校验,写入,查询
    • 使用LedgerProvider维护账本的通用配置,最终为Channel生成Ledger
    • 使用BlockStore保存区块数据
    • 使用VersionedDBProvider维护最终状态
    • 使用HistoryDBProvider维护历史记录
    账本相关对象账本相关对象

    1.1 Ledger

    common/ledger/ledger_interface.go中定义了账本Ledger的接口及其基本功能,从中我们可以对Ledger的作用有个概要了解。在Fabric中,一个Channel对应着一个账本,一个账本是包含一条由交易记录组成的区块链,以及交易导致的最终状态数据库和历史数据库。下面是Ledger的主要方法:

    • GetBlockchainInfo() 获取当前账本的区块链信息,主要是区块链的高度,当前区块的Hash值及上一个区块的Hash值
    • GetBlockByNumber(blockNumber) 返回指定编号的区块
    • GetBlocksIterator(startBlockNumber) 获取一个从指定编号开始的迭代器,用于不断获取后续的区块
    • Close() 关闭账本
    • Commit(block) 提交区块

    可以看出,Ledger主要用于提交区块,查询区块及区块链信息。其他对象中,ResultsIterator用于迭代器,可以不断查询下一个区块,QueryResult是查询结果,PrunePolicy是账本的修剪策略。

    1.2 PeerLedger

    PeerLedger在core/ledger/ledger_interface.go中定义,实现了1.1 Ledger中的方法,并且添加了额外的其他方法。在common/ledger中,主要是定义通用的Ledger,在core//ledger中是实现。
    PeerLedger类额外实现的功能主要是通过Block中的交易txid/hash值获取区块,初次之外,提供了TxSimulator交易模拟器,QueryExecutor查询器,HistoryQueryExecutor历史查询器等功能。

    1.3 PeerLedgerProvider

    PeerLedgerProvider 保存了账本的通用信息,用来创建/打开账本。例如,Provider里有保存账本的BlockStoreProvider,状态数据库和历史数据库的Provider,对于新创建的通道Channel1,PeerLedgerProvider为其返回一个PeerLedger实例,用来操作Channel1的账本。从下面的主要方法中就可以看出其功能:

    • Create(genesisBlock) 使用创世块创建一个PeerLedger,创世块中包含了账本的相关配置信息
    • Open(ledgerID) 根据账本ID返回一个PeerLedger,ledgerID就是Channel的名称
    • Exists(ledgerID) 判断账本ID是否存在
    • List() 列出当前的所有账本ID

    PeerLedgerProvider中包含几个属性:

    • 保存provider信息的数据库,使用leveldb保存provider的内容,在ledgerProvider文件夹中。
    • blockStoreProvider 会生产BlockStore,定义如何保存区块,目前只有一种实现FSBlockSotre,其将区块数据保存到文件系统中。
    • VersionedDBProvider 提供VersionDB的处理类,用于保存账本key值的最终状态。
    • HistoryDBProvider 提供HistoryDB的处理类,用于保存key的历史记录。

    2.账本初始化

    使用peer start启动peer节点时,首先会初始化与账本相关的对象,主要代码在ledger_mgmt.go中,主要就是创建一个PeerLedgerProvider。
    kvLedger是PeerLedger的实现类,由其创建PeerLedgerProvider,主要逻辑为:

    1. 获取peer.fileSystemPath配置的路径,在内部的ledgersData/ledgerProvider文件夹初始化一个leveldb的数据库,保存到idStore中。idStore用于维护账本id相关信息。
    2. 在保存区块的过程中,为了快速检索区块,需要建立索引,因此,创建了一个indexConfig,指定了需要建立索引的字段数组
    3. 生成blockStoreProvider,指定存储区块的路径,每个区块文件的最大大小,索引配置信息
    4. 初始化VersionedDBProvider,根据配置返回leveldb和couchdb的provider
    5. 初始化HistoryDBProvider
    6. 构造PeerLedgerProvider,准备返回
    7. 最后一步,查看是否存在创建账本时发生崩溃的账本id,如果存在的话,需要恢复,在Create()创建账本时会设置这个flag,内容为账本id,成功创建后会删除这个flag。因此如果中途程序崩溃,会留下这个flag。恢复的逻辑是重新创建KVLedger,使用blockStore从文件中获取blockchaininfo,如果高度为0,说明还没有提交创世块,删除flag即可,如果高度为1,已经提交创世块,使用idStore维护账本id,其他情况说明账本已经创建了。

    3. idStore

    idStore定义在kv_ledger_provider.go中,内部包含一个用于操作leveldb的成员db,初始化账本的时候,会为provider创建一个leveldb数据库,主要功能:

    1. underConstructionFlag 操作underConstructionLedgerKey,这个key用来记录账本创建过程中是否发生了异常,发生异常时会有补偿机制重建账本。
    2. 在PeerLedgerProvider使用Create(genesisBlock *common.Block)创建PeerLedger时,会将账本的id和创世块内容组成键值对保存在数据库中。
    3. 获取所有的账本id,遍历数据库的key,如果是账本id的前缀,则说明有这个账本,存放在返回的list中。

    4.BlockStore

    BlockStore定义在common/ledger/blkstorage/blockstorage.go中,只有一个实现fsblkstorage,在文件系统中保存区块。这是账本机制的重要组成部分,PeerLedger的实现kvLedger中的大部分操作都是使用的BlockStore,VersionedDB和HistroyDB。

    下面是BlockStore接口的方法,可以看到,主要是添加取款,获取区块链信息,检索区块的功能。

    type BlockStore interface {
        AddBlock(block *common.Block) error
        GetBlockchainInfo() (*common.BlockchainInfo, error)
        RetrieveBlocks(startNum uint64) (ledger.ResultsIterator, error)
        ......
        Shutdown()
    }
    

    fs_blockstore.go中的fsBlockStore实现了BlockStore,在文件系统中保存区块。需要注意的是fsBlockStore中有一个blockfileMgr,里面负责具体的文件操作。

    4.1 blockfileMgr

    blockfileMgr定义在blockfile_mgr.go中,用来管理将Block写入文件,建立索引,获取Block。blockfileMgr中有如下几个重要的组件,用来帮助完成存储Block的操作。
    blockfileWriter
    blockfileWriter用于将数据写入文件,由于fsBlockStore将区块保存到了多个文件中,blockfileWriter记录了写入文件的文件夹和操作当前文件的os.File

    checkpointinfo
    checkpointInfo定义在blockfile_mgr.go中,用来记录当前最后一个文件的序号,已写入文件的字节数,最后一个Block的编号信息。checkpointinfo会被保存在数据库中,每次初始化时,可以从数据库中读出,更新Block时,需要更新checkpointinfo并更新数据库。

    BlockchainInfo
    BlockchainInfo记录了区块链的状态,如高度,最后一个Block的Hash等

    blockIndex

    blockIndex用来建立索引,indexBlock

    4.2 提交Block

    blockfileMgr的addBlock负责添加Block,主要逻辑为:

    1. 校验区块编号,必须与BlockChain的高度相同,例如:区块链现有3个区块,编号分别为0-1-2,高度为3,那么新添加的Block编号需要是3。
    2. 使用protobuf将Block序列化,Block的结构为
    common.Block
      - BlockHeader
          - Number  > 区块编号
          - PrevHash > 上一个区块的Hash
          - DataHash  > BlockData的Hash
      - BlockData 
          - txEnvelope  > 一个交易
            - Payload 
              - Header  > 头
                - ChannelHeader  > 类型 版本和ChannelID
                - SignatureHeader > 签名头
            - Signature  
              - Creator  > 创建者
              - Nonce   >  随机数
      - BlockMetadata
    

    在序列化的过程中,可以提取出Block的交易id列表,元数据等信息,保存在serializedBlockInfo中;还可以计算出区块占用的字节数。

    1. 计算Block的Hash值,逻辑为将Block的编号,上一个Block的Hash和当前区块的DataHash使用ASN.1 编码为byte[],然后再使用SHA-256生成当前区块的Hash值。
    2. 将Block的byte数组(记为A)写入文件,写入内容为B:A的长度+A的内容,那么B的大小就是要写入文件的总大小,由于每个文件有大小限制,如果剩余的文件容量不足以支持写入,就重新创建一个文件并写入。blockfileMgr中维护了checkpointinfo,内部记录了账本当前所处于的文件及offset,每次写入之后,都会修改checkpointinfo信息。
    3. 在写入数据期间,如果发生error,会将文件恢复到写入之前的状态,并抛出异常。
    4. 更新checkpointInfo的信息,并保存到数据库中。
    5. 写入成功后,使用blockIndex,创建索引,这样可以根据block编号,hash值,交易id快速定位到Block所在文件的位置
    6. 添加Block后,BlockChainInfo最后一个Block发生变化,更新这些信息,。

    相关文章

      网友评论

        本文标题:Fabric源码分析-账本机制01

        本文链接:https://www.haomeiwen.com/subject/mtfmkqtx.html