Fabric如何從Ledger里面讀取block的內(nèi)容

Fabric從ledger讀取block的例子

下面例子使用fabric的功能竿秆,讀取ledger的block擎析,包含兩個方法:

  1. GetBlockByNumber:根據(jù)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結(jié)構(gòu)弯予,因為他需要讀取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的內(nèi)容,但是我們可以通過工具configtxlator把生成的block文件轉(zhuǎn)換成可讀json格式狰域。

注意媳拴,這個方式只能按順序讀取block從文件的頭讀到尾,不能隨機讀取block兆览;因為沒有l(wèi)edger的index庫了屈溉。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抬探,隨后出現(xiàn)的幾起案子子巾,更是在濱河造成了極大的恐慌,老刑警劉巖小压,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砰左,死亡現(xiàn)場離奇詭異,居然都是意外死亡场航,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門廉羔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溉痢,“玉大人僻造,你說我怎么就攤上這事『⒈” “怎么了髓削?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長镀娶。 經(jīng)常有香客問我立膛,道長,這世上最難降的妖魔是什么梯码? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任宝泵,我火速辦了婚禮,結(jié)果婚禮上轩娶,老公的妹妹穿的比我還像新娘儿奶。我一直安慰自己,他們只是感情好鳄抒,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布闯捎。 她就那樣靜靜地躺著,像睡著了一般许溅。 火紅的嫁衣襯著肌膚如雪瓤鼻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天贤重,我揣著相機與錄音茬祷,去河邊找鬼。 笑死游桩,一個胖子當著我的面吹牛牲迫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播借卧,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盹憎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铐刘?” 一聲冷哼從身側(cè)響起陪每,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎镰吵,沒想到半個月后檩禾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡疤祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年盼产,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺馆。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡戏售,死狀恐怖侨核,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灌灾,我是刑警寧澤搓译,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站锋喜,受9級特大地震影響些己,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘿般,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一段标、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧博个,春花似錦怀樟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至共耍,卻和暖如春虑灰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痹兜。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工穆咐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人字旭。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓对湃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遗淳。 傳聞我的和親對象是個殘疾皇子拍柒,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容