fabric如何解析一个common.block的内容
在前面的文章 “fabric如何从ledger里面读取block的内容” 中我们介绍了如何从ledger文件里面读取一个block,然后把block的内容写到一个文件中,再使用工具configtxlator把block内容翻译成json,这样用户就可以读取block的内容了。
这篇文章中我们直接用golang借助于fabric本身的代码,利用代码来解析和读取block的内容;也就是说当我们得到一个common.block对象时,我们直接利用fabric的数据结构和函数访问block的内容,而不需要借助configtxlator。
这部分代码是一个框架,不能单独编译,必须借助于前面提到的文章"fabric如何从ledger里面读取block的内容”的部分代码。
主函数
接受一个common.block对象,然后根据是Config block还是Endorser Transaction block分别解析。
一个简单的从block文件读取block对象的例子,block文件可以从peer channel fetch <BLOCKID> block.pb -o ${ORDERERADDR} -c ${CHANNEL}
得到:
import (
"github.com/golang/protobuf/proto"
cb "github.com/hyperledger/fabric/protos/common"
)
...
blockBytes, err := ioutil.ReadFile("block.pb")
if err != nil {
log.Println("ioutil.ReadFile error:", err)
return
}
block := &cb.Block{}
if err = proto.Unmarshal(blockBytes, block); err != nil {
log.Println("proto.Unmarshal block error:", err)
return
}
return block; // this is a common.block
block包含属性:
- block number
- block hash value
- channel
- txid
- creator
package main
import (
"fmt"
"encoding/base64"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/utils"
"github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/common/configtx"
"github.com/hyperledger/fabric/core/ledger/util"
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/rwsetutil"
)
func parseBlock(block *common.Block) error {
var err error
// Handle header
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))
// Handle transaction
var tranNo int64 = -1
txsFilter := util.TxValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER])
if len(txsFilter) == 0 {
txsFilter = util.NewTxValidationFlags(len(block.Data.Data))
block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter
}
for _, envBytes := range block.Data.Data {
tranNo++
if txsFilter.IsInvalid(int(tranNo)) {
fmt.Printf(" Transaction: No=[%d], Status=[INVALID]\n", tranNo)
continue
} else {
fmt.Printf(" Transaction: No=[%d], Status=[VALID]\n", tranNo)
}
var env *common.Envelope
if env, err = utils.GetEnvelopeFromBlock(envBytes); err != nil {
return err
}
var payload *common.Payload
if payload, err = utils.GetPayload(env); err != nil {
return err
}
var chdr *common.ChannelHeader
if chdr, err = utils.UnmarshalChannelHeader(payload.Header.ChannelHeader); err != nil {
return err
}
fmt.Printf(" txid=[%s], channel=[%s]\n", chdr.TxId, chdr.ChannelId)
var shdr *common.SignatureHeader
if shdr, err = utils.GetSignatureHeader(payload.Header.SignatureHeader); err != nil {
return err
}
var mspid, subject string
if mspid, subject, err = decodeSerializedIdentity(shdr.Creator); err != nil {
return err
}
fmt.Printf(" creator=[%s:%s]\n", mspid, subject)
if common.HeaderType(chdr.Type) == common.HeaderType_CONFIG {
fmt.Printf(" type=[CONFIG]\n")
if err = parseConfig(payload); err != nil {
return err
}
} else if common.HeaderType(chdr.Type) == common.HeaderType_ENDORSER_TRANSACTION {
fmt.Printf(" type=[ENDORSER_TRANSACTION]\n")
if err = parseEndorserTransaction(payload); err != nil {
return err
}
} else {
fmt.Printf(" txid=[%s], channel=[%s], type=[UNKNOWN]\n", chdr.TxId, chdr.ChannelId)
}
}
return nil
}
解析endorser transaction block
这个解析并不完整,只是我们项目中需要的字段进行了解析,其他的丢弃了。
主要包括:
- endorsers
- RWSet (chaincode, key)
func parseEndorserTransaction(payload *common.Payload) error {
var err error
var tx *peer.Transaction
if tx, err = utils.GetTransaction(payload.Data); err != nil {
return err
}
fmt.Printf(" actions\n")
for _, action := range tx.Actions {
var capayload *peer.ChaincodeActionPayload
var ca *peer.ChaincodeAction
if capayload, ca, err = utils.GetPayloads(action); err != nil {
return err
}
fmt.Printf(" endorsers\n")
for _, endorser := range capayload.Action.Endorsements {
var mspid, subject string
if mspid, subject, err = decodeSerializedIdentity(endorser.Endorser); err != nil {
return err
}
fmt.Printf(" endorser[%s:%s]\n", mspid, subject)
}
fmt.Printf(" RWSet\n")
txRWSet := &rwsetutil.TxRwSet{}
if err = txRWSet.FromProtoBytes(ca.Results); err != nil {
return err
}
for _, nsRWSet := range txRWSet.NsRwSets {
ns := nsRWSet.NameSpace
if ns != "lscc" { // skip system chaincode
fmt.Printf(" ns=[%v]\n", ns)
fmt.Printf(" RDSet\n")
for _, kvRead := range nsRWSet.KvRwSet.Reads {
fmt.Printf(" key=[%v]\n", kvRead.Key)
}
fmt.Printf(" WRSet\n")
for _, kvWrite := range nsRWSet.KvRwSet.Writes {
if kvWrite.IsDelete {
fmt.Printf(" key=[%v] op=[delete]\n", kvWrite.Key)
} else {
fmt.Printf(" key=[%v] op=[write]\n", kvWrite.Key)
}
}
}
}
}
return nil
}
解析 config block
和前面一样,也没有解析整个config block,只解析了我们项目中需要的字段,其实只有一个orderer address,当然其他的也很容易处理了。
func parseConfig(payload *common.Payload) error {
var err error
var configEnvelope *common.ConfigEnvelope
if configEnvelope, err = configtx.UnmarshalConfigEnvelope(payload.Data); err != nil {
return err
}
var configGroup * common.ConfigGroup = configEnvelope.Config.ChannelGroup
fmt.Printf(" Groups\n")
for k, _ := range configGroup.Groups {
fmt.Printf(" %s\n", k)
}
fmt.Printf(" Values\n")
for k, v := range configGroup.Values {
fmt.Printf(" %s\n", k)
if k == "OrdererAddresses" {
addresses := &common.OrdererAddresses{}
if err = proto.Unmarshal(v.Value, addresses); err != nil {
return err
}
for _, address := range addresses.Addresses {
fmt.Printf(" [%s]\n", address)
}
}
}
return nil
}