Fabric如何从Ledger里面读取block的内容

Fabric从ledger读取block的例子

下面例子使用fabric的功能,读取ledger的block,包含两个方法:

  1. GetBlockByNumber:根据block number读取确切的一个block
  2. GetBlocksIterator:遍历的方式读取所有的block
package main

//
// There is web resource describing the detailed block structure:
//   https://blockchain-fabric.blogspot.com/2017/04/hyperledger-fabric-v10-block-structure.html
//

import (
     "os"
     "fmt"
     "strings"
     "encoding/base64"

     "github.com/hyperledger/fabric/peer/common"
     "github.com/hyperledger/fabric/core/ledger/kvledger"
  cb "github.com/hyperledger/fabric/protos/common"
     "github.com/spf13/viper"
)

const cmdRoot = "core"
const channel = "mychannel"

// TODO: print more block data fields
func printBlock(prefix string, block * cb.Block) {
    fmt.Printf("%s Block: Number=[%d], CurrentBlockHash=[%s], PreviousBlockHash=[%s]\n",
        prefix,
        block.GetHeader().Number,
        base64.StdEncoding.EncodeToString(block.GetHeader().DataHash),
        base64.StdEncoding.EncodeToString(block.GetHeader().PreviousHash))
}


func main() {
    viper.SetEnvPrefix(cmdRoot)
    viper.AutomaticEnv()
    replacer := strings.NewReplacer(".", "_")
    viper.SetEnvKeyReplacer(replacer)
    err := common.InitConfig("core")
    if err != nil { // Handle errors reading the config file
        fmt.Printf("Cannot init configure, error=[%v]", err)
        os.Exit(1)
    }

    provider, err := kvledger.NewProvider()   // core/ledger/kvledger/kv_ledger_provider.go
    if err != nil {
        fmt.Printf("Cannot new provider, error=[%s]", err)
        os.Exit(1)
    }
    defer provider.Close()

    // Print channel list
    channels, err := provider.List()        // core/ledger/kvledger/kv_ledger_provider.go
    if err != nil {
        fmt.Printf("Cannot get channel list, error=[%v]\n", err)
        os.Exit(1)
    }
    fmt.Printf("channels=[%v]\n", channels)
    
    // Open a channel
    ledger, err := provider.Open(channel)   // core/ledger/kvledger/kv_ledger_provider.go
    if err != nil {
        fmt.Printf("Cannot open channel ledger, error=[%v]\n", err)
        os.Exit(1)
    }
    defer ledger.Close()
    // Return ledger as kvLedger is defined in core/ledger/kvledger/kv_ledger.go, following API:
    //  func (l *kvLedger) GetBlockchainInfo() (*common.BlockchainInfo, error)
    //  func (l *kvLedger) GetTransactionByID(txID string) (*peer.ProcessedTransaction, error)
    //  func (l *kvLedger) GetBlockByNumber(blockNumber uint64) (*common.Block, error)
    //  func (l *kvLedger) GetBlockByHash(blockHash []byte) (*common.Block, error)
    //  func (l *kvLedger) GetBlockByTxID(txID string) (*common.Block, error)
    //  func (l *kvLedger) GetBlocksIterator(startBlockNumber uint64) (commonledger.ResultsIterator, error)
    //  func (l *kvLedger) GetTxValidationCodeByTxID(txID string) (peer.TxValidationCode, error)
    //  func (l *kvLedger) Close()

    // Get basic channel information
    chainInfo, err := ledger.GetBlockchainInfo() // (*common.BlockchainInfo, error)
    if err != nil {
        fmt.Printf("Cannot get block chain info, error=[%v]\n", err)
        os.Exit(1)
    }
    fmt.Printf("chainInfo: Height=[%d], CurrentBlockHash=[%s], PreviousBlockHash=[%s]\n",
                    chainInfo.GetHeight(),
                    base64.StdEncoding.EncodeToString(chainInfo.CurrentBlockHash),
                    base64.StdEncoding.EncodeToString(chainInfo.PreviousBlockHash))

    // Retrieve blocks based on block number
    for i := uint64(0); i < chainInfo.GetHeight(); i++ {
        block, err := ledger.GetBlockByNumber(i) // (blockNumber uint64) (*common.Block, error)
        if err != nil {
            fmt.Printf("Cannot get block for %d, error=[%v]\n", i, err)
            os.Exit(1)
        }
        printBlock("Get", block)
    }


    // Retrieve blocks based on iterator
    itr, err := ledger.GetBlocksIterator(0) // (ResultsIterator, error)
    if err != nil {
        fmt.Printf("Cannot get iterator, error=[%v]\n", err)
        os.Exit(1)
    }
    defer itr.Close()

    queryResult, err := itr.Next()    // commonledger.QueryResult
    for i := uint64(0); err == nil; i++ {
        block := queryResult.(*cb.Block)
        printBlock("Iterator", block)
        if i >= chainInfo.GetHeight() - 1 {
            break
        }
        queryResult, err = itr.Next()    // commonledger.QueryResult
    }   
}

使用方法:

  1. 拷贝core.yaml到当前目录,并
     修改fileSystemPath配置值到正确的ledger存储目录: 例如~/.../hyperledger/production
  2. go build && ./main

上述例子代码依赖于peer组织的ledger结构,因为他需要读取ledger的index信息,也就是说输入必须是完整的hyperledger/production目录,这样才能包含完整的ledger信息;其好处就是可以按照block number或者transaction id来搜搜block。

另外一种情况,是如果只有单个ledger文件,能不能读取block信息呢,当然这种情况下只能按顺序遍历读取所有的block而不能随机读取。

代码如下:

package main

import (
       "os"
       "fmt"
       "io"
       "io/ioutil"
       "bufio"
       "errors"
       "encoding/base64"

       "github.com/golang/protobuf/proto"
       "github.com/hyperledger/fabric/protos/common"
 lutil "github.com/hyperledger/fabric/common/ledger/util"
 putil "github.com/hyperledger/fabric/protos/utils"
)

var ErrUnexpectedEndOfBlockfile = errors.New("unexpected end of blockfile")

var (
    file        *os.File
    fileName    string
    fileSize    int64
    fileOffset  int64
    fileReader  *bufio.Reader
)

// Parse a block
func handleBlock(block * common.Block) {
    fmt.Printf("Block: Number=[%d], CurrentBlockHash=[%s], PreviousBlockHash=[%s]\n",
        block.GetHeader().Number,
        base64.StdEncoding.EncodeToString(block.GetHeader().DataHash),
        base64.StdEncoding.EncodeToString(block.GetHeader().PreviousHash))

    if putil.IsConfigBlock(block) {
        fmt.Printf("    txid=CONFIGBLOCK\n")
    } else {
        for _, txEnvBytes := range block.GetData().GetData() {
            if txid, err := extractTxID(txEnvBytes); err != nil {
                fmt.Printf("ERROR: Cannot extract txid, error=[%v]\n", err)
                return
            } else {
                fmt.Printf("    txid=%s\n", txid)
            }
        }
    }

    // write block to file
    b, err := proto.Marshal(block)
    if err != nil {
        fmt.Printf("ERROR: Cannot marshal block, error=[%v]\n", err)
        return
    }

    filename := fmt.Sprintf("block%d.block", block.GetHeader().Number)
    if err := ioutil.WriteFile(filename, b, 0644); err != nil {
        fmt.Printf("ERROR: Cannot write block to file:[%s], error=[%v]\n", filename, err)
    }

    // Then you could use utility to read block content, like:
    // $ configtxlator proto_decode --input block0.block --type common.Block
}

func nextBlockBytes() ([]byte, error) {
    var lenBytes []byte
    var err error

    // At the end of file
    if fileOffset == fileSize {
        return nil, nil
    }

    remainingBytes := fileSize - fileOffset
    peekBytes := 8
    if remainingBytes < int64(peekBytes) {
        peekBytes = int(remainingBytes)
    }
    if lenBytes, err = fileReader.Peek(peekBytes); err != nil {
        return nil, err
    }

    length, n := proto.DecodeVarint(lenBytes)
    if n == 0 {
        return nil, fmt.Errorf("Error in decoding varint bytes [%#v]", lenBytes)
    }

    bytesExpected := int64(n) + int64(length)
    if bytesExpected > remainingBytes {
        return nil, ErrUnexpectedEndOfBlockfile
    }

    // skip the bytes representing the block size
    if _, err = fileReader.Discard(n); err != nil {
        return nil, err
    }

    blockBytes := make([]byte, length)
    if _, err = io.ReadAtLeast(fileReader, blockBytes, int(length)); err != nil {
        return nil, err
    }

    fileOffset += int64(n) + int64(length)
    return blockBytes, nil
}

func deserializeBlock(serializedBlockBytes []byte) (*common.Block, error) {
    block := &common.Block{}
    var err error
    b := lutil.NewBuffer(serializedBlockBytes)
    if block.Header, err = extractHeader(b); err != nil {
        return nil, err
    }
    if block.Data, err = extractData(b); err != nil {
        return nil, err
    }
    if block.Metadata, err = extractMetadata(b); err != nil {
        return nil, err
    }
    return block, nil
}

func extractHeader(buf *lutil.Buffer) (*common.BlockHeader, error) {
    header := &common.BlockHeader{}
    var err error
    if header.Number, err = buf.DecodeVarint(); err != nil {
        return nil, err
    }
    if header.DataHash, err = buf.DecodeRawBytes(false); err != nil {
        return nil, err
    }
    if header.PreviousHash, err = buf.DecodeRawBytes(false); err != nil {
        return nil, err
    }
    if len(header.PreviousHash) == 0 {
        header.PreviousHash = nil
    }
    return header, nil
}

func extractData(buf *lutil.Buffer) (*common.BlockData, error) {
    data := &common.BlockData{}
    var numItems uint64
    var err error

    if numItems, err = buf.DecodeVarint(); err != nil {
        return nil, err
    }
    for i := uint64(0); i < numItems; i++ {
        var txEnvBytes []byte
        if txEnvBytes, err = buf.DecodeRawBytes(false); err != nil {
            return nil, err
        }
        data.Data = append(data.Data, txEnvBytes)
    }
    return data, nil
}

func extractMetadata(buf *lutil.Buffer) (*common.BlockMetadata, error) {
    metadata := &common.BlockMetadata{}
    var numItems uint64
    var metadataEntry []byte
    var err error
    if numItems, err = buf.DecodeVarint(); err != nil {
        return nil, err
    }
    for i := uint64(0); i < numItems; i++ {
        if metadataEntry, err = buf.DecodeRawBytes(false); err != nil {
            return nil, err
        }
        metadata.Metadata = append(metadata.Metadata, metadataEntry)
    }
    return metadata, nil
}

func extractTxID(txEnvelopBytes []byte) (string, error) {
    txEnvelope, err := putil.GetEnvelopeFromBlock(txEnvelopBytes)
    if err != nil {
        return "", err
    }
    txPayload, err := putil.GetPayload(txEnvelope)
    if err != nil {
        return "", nil
    }
    chdr, err := putil.UnmarshalChannelHeader(txPayload.Header.ChannelHeader)
    if err != nil {
        return "", err
    }
    return chdr.TxId, nil
}


func main() {
    fileName = "hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000"

    var err error
    if file, err = os.OpenFile(fileName, os.O_RDONLY, 0600); err != nil {
        fmt.Printf("ERROR: Cannot Open file: [%s], error=[%v]\n", fileName, err)
        return
    }
    defer file.Close()


    if fileInfo, err := file.Stat(); err != nil {
        fmt.Printf("ERROR: Cannot Stat file: [%s], error=[%v]\n", fileName, err)
        return
    } else {
        fileOffset = 0
        fileSize   = fileInfo.Size()
        fileReader = bufio.NewReader(file)
    }

    // Loop each block
    for {
        if blockBytes, err := nextBlockBytes(); err != nil {
            fmt.Printf("ERROR: Cannot read block file: [%s], error=[%v]\n", fileName, err)
            break
        } else if blockBytes == nil {
            // End of file
            break
        } else {
            if block, err := deserializeBlock(blockBytes); err != nil {
                fmt.Printf("ERROR: Cannot deserialize block from file: [%s], error=[%v]\n", fileName, err)
                break
            } else {
                handleBlock(block)
            }
        }
    }
}

这个例子非常简单,读取单个ledger文件hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000的所有block,并把每一个block拆分成独立的文件,block{BLOCKNUMBER}.block;这个代码片段偷了点懒,没有去解析block的内容,但是我们可以通过工具configtxlator把生成的block文件转换成可读json格式。

注意,这个方式只能按顺序读取block从文件的头读到尾,不能随机读取block;因为没有ledger的index库了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容