id:BSN_2021
公众号:BSN研习社
作者:红枣科技高晨光

背景:BSN公网Fabric联盟链的呈现升高了应用区块链的难度,在通过BSN城市节点网关发动交易时,只能获取最根本交易信息,想要展现更多区块链个性的数据就须要从账本数据中获取,而解析账本数据有肯定的难度。

指标:理解账本数据结构,更好的设计开发本人的我的项目

对象: 应用BSN联盟链Fabric的开发人员

前言

开始之前先看一个简略的合约代码

import (    "github.com/hyperledger/fabric-contract-api-go/contractapi"    "github.com/hyperledger/fabric-contract-api-go/metadata"    "math/big")type DemoChaincode struct {    contractapi.Contract}func (d *DemoChaincode) Set(ctx contractapi.TransactionContextInterface, key string, value int64) (string, error) {    bigValue := new(big.Int).SetInt64(value)    keyValue ,err :=ctx.GetStub().GetState(key)    var resultValue  string    if err != nil || len(keyValue) ==0 {        keyValue = bigValue.Bytes()        resultValue = bigValue.String()    }else {        bigKeyValue := new(big.Int).SetBytes(keyValue)        result := new(big.Int).Add(bigKeyValue,bigValue)        keyValue = result.Bytes()        resultValue  = result.String()    }    err = ctx.GetStub().PutState(key, keyValue)    if err != nil {        return "", err    } else {        ctx.GetStub().SetEvent("set_key",bigValue.Bytes())        return resultValue, nil    }}func (d *DemoChaincode) Query(ctx contractapi.TransactionContextInterface, key string) (string, error) {    valueBytes, err := ctx.GetStub().GetState(key)    if err != nil || len(valueBytes) ==0 {        return "0", nil    }    bigKeyValue := new(big.Int).SetBytes(valueBytes)    return bigKeyValue.String(), nil}

这是一个通过Go语言开发的Fabric智能合约,合约提供了两个合约办法SetQuery

Set办法的次要性能就是依据输出的key 读取账本数据并且累加value的值,并输入后果。

Query办法次要就是查问以后key的值。

那么咱们在调用这个合约的Set办法时,会在链上留下什么数据,都蕴含那些有用的信息呢?

接下来咱们通过BSN城市节点网关查问交易所在的块信息来查看一个账本的块数据都有那些信息。

通过BSN城市节点网关查问块数据

在此之前,咱们先通过接口调用一下Set办法,拿到合约的执行后果和交易Id。

    cli :=getTestNetFabricClient(t)    nonce,_ :=common.GetRandomNonce()    reqData :=node.TransReqDataBody{        Nonce: base64.StdEncoding.EncodeToString(nonce),        ChainCode: "cc_f73a60f601654467b71bdc28b8f16033",        FuncName: "Set",        Args: []string{"abc","76"},    }    res,err :=cli.ReqChainCode(reqData)    if err !=nil {        t.Fatal(err)    }    resBytes,_ :=json.Marshal(res)    fmt.Println(string(resBytes))
getTestNetFabricClient 办法次要是依据网关地址、用户信息等参数创立了一个FabricClient对象

响应后果为:

{    "header":{        "code":0,        "msg":"success"    },    "mac":"MEUCIQCLnU5gTu6NvM0zn4HH1lDSEef5i6HgNjKS2YRirDfYVgIgJaN+BQRUulS6jtqePAvb/Z3E9U0W5Go4aV7ffrkMbBc=",    "body":{        "blockInfo":{            "txId":"32a4ce2060e4138e6465f907579a9765d50bdb6e89763b064d69664bc4ebf999",            "blockHash":"",            "status":0        },        "ccRes":{            "ccCode":200,            "ccData":"276"        }    }}

在返回的后果中咱们能够看到本次交易的Id为32a4ce2060e4138e6465f907579a9765d50bdb6e89763b064d69664bc4ebf999,合约的返回后果为276,状态为200 示意合约执行胜利。
因为目前BSN网关的ReqChainCode接口不会期待交易落块后再返回,所以接口不会返回以后交易的块信息,也无奈确认交易的最终状态,所以须要咱们再次调用接口查问交易的信息。

调用接口查问块信息

BSN城市节点网关提供了两个接口查问块信息 getBlockInfo以及getBlockData,这两个接口的参数雷同,返回又有哪些不同呢?
getBlockInfo 接口返回了解析之后的块信息以及交易的简略信息,只蕴含了交易Id、状态、提交者、交易工夫等。
getBlockData 接口则返回了实现的块数据,块数据是以base64编码后的块数据,示例如下:

    txId :="32a4ce2060e4138e6465f907579a9765d50bdb6e89763b064d69664bc4ebf999"    cli :=getTestNetFabricClient(t)    _,block,err :=cli.GetBlockData(node.BlockReqDataBody{TxId:txId })    if err !=nil {        t.Fatal(err)    }

响应后果为:

{    "header":{        "code":0,        "msg":"success"    },    "mac":"MEQCIDIg/lhMy2yK1oaK/7naISwmL9gEYUtVHsgYykUYr73jAiALQcEsIBfmeFZvdgq4gEBNY/thLO/ZJUb/tbPl9ql9WA==",    "body":{        "blockHash":"66e7d01b102a0bbd2ebe55fff608d46512c3d243dde0d1305fec44a31a800932",        "blockNumber":384187,        "preBlockHash":"0ce4a7200bb67aea157e92f8dfea5c40bd1a6390d28cc70bad91e9af79098df4",        "blockData":"Ckg ... muf1s="    }}

在网关返回的参数中blockData即残缺的块数据。

转换块数据

网关响应的块数据即github.com\hyperledger\fabric-protos-go\common\common.pb.goBlock通过proto.Marshal序列化后的数据进行base64编码后的。
解析代码如下

func ConvertToBlock(blockData string) (*common.Block, error) {    blockBytes, err := base64.StdEncoding.DecodeString(blockData)    if err != nil {        return nil, errors.WithMessage(err, "convert block data has error")    }    block := &common.Block{}    err = proto.Unmarshal(blockBytes, block)    if err != nil {        return nil, errors.WithMessage(err, "convert block bytes has error")    }    return block, nil}

同时在github.com/hyperledger/fabric-config/protolator包中也提供了转换为json格局的办法:

func ConvertBlockToJson(block *common.Block) (string, error) {    var sb strings.Builder    err := protolator.DeepMarshalJSON(&sb, block)    if err != nil {        return "", err    }    return sb.String(), err}

Fabric 块内数据蕴含哪些内容

咱们先来看common.Block 对象:

// This is finalized block structure to be shared among the orderer and peer// Note that the BlockHeader chains to the previous BlockHeader, and the BlockData hash is embedded// in the BlockHeader.  This makes it natural and obvious that the Data is included in the hash, but// the Metadata is not.type Block struct {    Header               *BlockHeader   `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`    Data                 *BlockData     `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`    Metadata             *BlockMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`    XXX_NoUnkeyedLiteral struct{}       `json:"-"`    XXX_unrecognized     []byte         `json:"-"`    XXX_sizecache        int32          `json:"-"`}

上面咱们来具体的解释每一部分的内容

Header

// BlockHeader is the element of the block which forms the block chain// The block header is hashed using the configured chain hashing algorithm// over the ASN.1 encoding of the BlockHeadertype BlockHeader struct {    Number               uint64   `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`    PreviousHash         []byte   `protobuf:"bytes,2,opt,name=previous_hash,json=previousHash,proto3" json:"previous_hash,omitempty"`    DataHash             []byte   `protobuf:"bytes,3,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"`    XXX_NoUnkeyedLiteral struct{} `json:"-"`    XXX_unrecognized     []byte   `json:"-"`    XXX_sizecache        int32    `json:"-"`}

BlockHeader中蕴含块号、上一个块哈希、以后块的数据哈希。因为不蕴含以后块的块哈希,所以咱们如果想获取以后块的哈希,须要本人手动计算。
即计算asn1编码后的块号、上一个块哈希、数据哈希的哈希即可。

func GetBlockHASH(info *common.Block) []byte {    asn1Header := asn1Header{        PreviousHash: info.Header.PreviousHash,        DataHash:     info.Header.DataHash,        Number:       int64(info.Header.Number),    }    result, _ := asn1.Marshal(asn1Header)    return Hash(result)    }

Data

type BlockData struct {    Data                 [][]byte `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"`    XXX_NoUnkeyedLiteral struct{} `json:"-"`    XXX_unrecognized     []byte   `json:"-"`    XXX_sizecache        int32    `json:"-"`}

BlockData中蕴含的即为以后块内的每一条交易,是common.Envelope对象的序列化后果的合集。即每一个交易。

// Envelope wraps a Payload with a signature so that the message may be authenticatedtype Envelope struct {    // A marshaled Payload    Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`    // A signature by the creator specified in the Payload header    Signature            []byte   `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`    XXX_NoUnkeyedLiteral struct{} `json:"-"`    XXX_unrecognized     []byte   `json:"-"`    XXX_sizecache        int32    `json:"-"`}

common.Envelope对象即客户端收集到足够的背书后果后向orderer提交的交易内容,蕴含具体交易以及客户端签名,将Payload序列化为common.Payload对象后我么能够失去向orderer提交的channel信息,提交者信息。他们在common.Payload.Header中。

type Header struct {    ChannelHeader        []byte   `protobuf:"bytes,1,opt,name=channel_header,json=channelHeader,proto3" json:"channel_header,omitempty"`    SignatureHeader      []byte   `protobuf:"bytes,2,opt,name=signature_header,json=signatureHeader,proto3" json:"signature_header,omitempty"`    XXX_NoUnkeyedLiteral struct{} `json:"-"`    XXX_unrecognized     []byte   `json:"-"`    XXX_sizecache        int32    `json:"-"`}

他们别离是common.ChannelHeader以及common.SignatureHeader对象,这些信息中蕴含了channelId,交易Id,交易工夫,提交者,MSPId,等信息。
而对common.Payload.Data解析后咱们能够失去peer.Transaction对象。这里就是咱们向各个节点发动的交易提案以及节点的背书后果。每一个peer.TransactionAction对象中蕴含两局部数据,

// TransactionAction binds a proposal to its action.  The type field in the// header dictates the type of action to be applied to the ledger.type TransactionAction struct {    // The header of the proposal action, which is the proposal header    Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`    // The payload of the action as defined by the type in the header For    // chaincode, it's the bytes of ChaincodeActionPayload    Payload              []byte   `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`    XXX_NoUnkeyedLiteral struct{} `json:"-"`    XXX_unrecognized     []byte   `json:"-"`    XXX_sizecache        int32    `json:"-"`}

Header是交易提案提交者身份信息,个别和common.Payload.Header中的SignatureHeader统一。
Payloadpeer.ChaincodeActionPayload对象,蕴含交易提案的详细信息以及节点的模仿执行后果和背书信息。

// ChaincodeActionPayload is the message to be used for the TransactionAction's// payload when the Header's type is set to CHAINCODE.  It carries the// chaincodeProposalPayload and an endorsed action to apply to the ledger.type ChaincodeActionPayload struct {    // This field contains the bytes of the ChaincodeProposalPayload message from    // the original invocation (essentially the arguments) after the application    // of the visibility function. The main visibility modes are "full" (the    // entire ChaincodeProposalPayload message is included here), "hash" (only    // the hash of the ChaincodeProposalPayload message is included) or    // "nothing".  This field will be used to check the consistency of    // ProposalResponsePayload.proposalHash.  For the CHAINCODE type,    // ProposalResponsePayload.proposalHash is supposed to be H(ProposalHeader ||    // f(ChaincodeProposalPayload)) where f is the visibility function.    ChaincodeProposalPayload []byte `protobuf:"bytes,1,opt,name=chaincode_proposal_payload,json=chaincodeProposalPayload,proto3" json:"chaincode_proposal_payload,omitempty"`    // The list of actions to apply to the ledger    Action               *ChaincodeEndorsedAction `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`    XXX_NoUnkeyedLiteral struct{}                 `json:"-"`    XXX_unrecognized     []byte                   `json:"-"`    XXX_sizecache        int32                    `json:"-"`}

ChaincodeProposalPayload存储了peer.ChaincodeProposalPayload对象的序列化数据,其中蕴含了向节点发动的交易提案内容,它蕴含了咱们调用的合约信息、合约方以及输出参数等信息。
ChaincodeEndorsedAction对象中蕴含了两局部数据:

// ChaincodeEndorsedAction carries information about the endorsement of a// specific proposaltype ChaincodeEndorsedAction struct {    // This is the bytes of the ProposalResponsePayload message signed by the    // endorsers.  Recall that for the CHAINCODE type, the    // ProposalResponsePayload's extenstion field carries a ChaincodeAction    ProposalResponsePayload []byte `protobuf:"bytes,1,opt,name=proposal_response_payload,json=proposalResponsePayload,proto3" json:"proposal_response_payload,omitempty"`    // The endorsement of the proposal, basically the endorser's signature over    // proposalResponsePayload    Endorsements         []*Endorsement `protobuf:"bytes,2,rep,name=endorsements,proto3" json:"endorsements,omitempty"`    XXX_NoUnkeyedLiteral struct{}       `json:"-"`    XXX_unrecognized     []byte         `json:"-"`    XXX_sizecache        int32          `json:"-"`}

Endorsements是节点的背书信息,蕴含节点证书以及节点签名。
ProposalResponsePayload 存储了peer.ProposalResponsePayload对象的序列化数据,它蕴含了ProposalHash以及Extension,
其中Extension局部的数据在合约调用中为peer.ChaincodeAction对象的序列化数据,蕴含了执行的合约名称、合约中对账本的读写汇合,合约的返回后果,以及合约事件等。

// ChaincodeAction contains the actions the events generated by the execution// of the chaincode.type ChaincodeAction struct {    // This field contains the read set and the write set produced by the    // chaincode executing this invocation.    Results []byte `protobuf:"bytes,1,opt,name=results,proto3" json:"results,omitempty"`    // This field contains the events generated by the chaincode executing this    // invocation.    Events []byte `protobuf:"bytes,2,opt,name=events,proto3" json:"events,omitempty"`    // This field contains the result of executing this invocation.    Response *Response `protobuf:"bytes,3,opt,name=response,proto3" json:"response,omitempty"`    // This field contains the ChaincodeID of executing this invocation. Endorser    // will set it with the ChaincodeID called by endorser while simulating proposal.    // Committer will validate the version matching with latest chaincode version.    // Adding ChaincodeID to keep version opens up the possibility of multiple    // ChaincodeAction per transaction.    ChaincodeId          *ChaincodeID `protobuf:"bytes,4,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`    XXX_NoUnkeyedLiteral struct{}     `json:"-"`    XXX_unrecognized     []byte       `json:"-"`    XXX_sizecache        int32        `json:"-"`}

Results为合约执行过程中对不同合约的读写信息,由rwset.TxReadWriteSet对象序列化。
Events为合约执行中的事件信息,有peer.ChaincodeEvent对象序列化。
Response为合约的返回信息,蕴含StatusMessagePayload
ChaincodeId为执行的合约信息,蕴含合约名,版本等。

Metadata

Metadata 目前蕴含5个Byte数组,他们别离对应,Orderer签名信息,最初一个配置块号,交易过滤器,Orderer信息,提交哈希。其中 配置块号和Orderer信息在目前的版本中未应用。
其中交易过滤器中的每一个Bytes示意对应交易的状态信息,转换为peer.TxValidationCode对象即可。

结语

通过对块数据的学习咱们能够查到交易在提案、背书、提交各个阶段的信息,帮忙咱们更好的了解交易的流程以及每一个参加交易的合约和账本的数据批改状况。对在排查异样交易的过程中提供帮忙。
最初用一个块的json格局数据来帮忙大家更好的了解Fabric账本的块数据

{    "data": {        "data": [            {                "payload": {                    "data": {                        "actions": [                            {                                "header": {                                    "creator": {                                        "id_bytes": "LS0 ... LQo=",                                        "mspid": "ECDSARTestNodeMSP"                                    },                                    "nonce": "rG24c6sj28YGtCo8PBeQMTJsgPusft6m"                                },                                "payload": {                                    "action": {                                        "endorsements": [                                            {                                                "endorser": "ChF ... LQo=",                                                "signature": "MEQCIDr+a5HiELJq1M2vZWc2NqNxDRnCEck7EtErgbvfe+mOAiAx9XKRmCcM2xyEyYoz5l6wMuYE4zDIR5GVvLnz0MAmXg=="                                            }                                        ],                                        "proposal_response_payload": {                                            "extension": {                                                "chaincode_id": {                                                    "name": "cc_f73a60f601654467b71bdc28b8f16033",                                                    "path": "",                                                    "version": "1.0.0.1"                                                },                                                "events": {                                                    "chaincode_id": "cc_f73a60f601654467b71bdc28b8f16033",                                                    "event_name": "set_key",                                                    "payload": "TA==",                                                    "tx_id": "32a4ce2060e4138e6465f907579a9765d50bdb6e89763b064d69664bc4ebf999"                                                },                                                "response": {                                                    "message": "",                                                    "payload": "Mjc2",                                                    "status": 200                                                },                                                "results": {                                                    "data_model": "KV",                                                    "ns_rwset": [                                                        {                                                            "collection_hashed_rwset": [],                                                            "namespace": "_lifecycle",                                                            "rwset": {                                                                "metadata_writes": [],                                                                "range_queries_info": [],                                                                "reads": [                                                                    {                                                                        "key": "namespaces/fields/cc_f73a60f601654467b71bdc28b8f16033/Sequence",                                                                        "version": {                                                                            "block_num": "384158",                                                                            "tx_num": "0"                                                                        }                                                                    }                                                                ],                                                                "writes": []                                                            }                                                        },                                                        {                                                            "collection_hashed_rwset": [],                                                            "namespace": "cc_f73a60f601654467b71bdc28b8f16033",                                                            "rwset": {                                                                "metadata_writes": [],                                                                "range_queries_info": [],                                                                "reads": [                                                                    {                                                                        "key": "abc",                                                                        "version": {                                                                            "block_num": "384179",                                                                            "tx_num": "0"                                                                        }                                                                    }                                                                ],                                                                "writes": [                                                                    {                                                                        "is_delete": false,                                                                        "key": "abc",                                                                        "value": "ARQ="                                                                    }                                                                ]                                                            }                                                        }                                                    ]                                                }                                            },                                            "proposal_hash": "3jOb59oJFGtq2NM4loU4cwmHSqp//YV7EwA+qNKV4fo="                                        }                                    },                                    "chaincode_proposal_payload": {                                        "TransientMap": {},                                        "input": {                                            "chaincode_spec": {                                                "chaincode_id": {                                                    "name": "cc_f73a60f601654467b71bdc28b8f16033",                                                    "path": "",                                                    "version": ""                                                },                                                "input": {                                                    "args": [                                                        "U2V0",                                                        "YWJj",                                                        "NzY="                                                    ],                                                    "decorations": {},                                                    "is_init": false                                                },                                                "timeout": 0,                                                "type": "GOLANG"                                            }                                        }                                    }                                }                            }                        ]                    },                    "header": {                        "channel_header": {                            "channel_id": "channel202010310000001",                            "epoch": "0",                            "extension": {                                "chaincode_id": {                                    "name": "cc_f73a60f601654467b71bdc28b8f16033",                                    "path": "",                                    "version": ""                                }                            },                            "timestamp": "2022-06-09T03:29:47.851381445Z",                            "tls_cert_hash": null,                            "tx_id": "32a4ce2060e4138e6465f907579a9765d50bdb6e89763b064d69664bc4ebf999",                            "type": 3,                            "version": 0                        },                        "signature_header": {                            "creator": {                                "id_bytes": "LS0 ... LQo=",                                "mspid": "ECDSARTestNodeMSP"                            },                            "nonce": "rG24c6sj28YGtCo8PBeQMTJsgPusft6m"                        }                    }                },                "signature": "MEQCIEIoQw4ZlOB6qc42oQ9L85I4Chs3lKPYgXEbDUEiQUPBAiAf/OQj21xhinlmI6ef7Ufv04KoeIuLwrFlS9lAfltXpw=="            },                    ]    },    "header": {        "data_hash": "u/d0Jx1D5tEv4WZIpqkjw17J/89klX27L+ukmhNdTQU=",        "number": "384187",        "previous_hash": "DOSnIAu2euoVfpL43+pcQL0aY5DSjMcLrZHpr3kJjfQ="    },    "metadata": {        "metadata": [            "CgI ... cDB",            "",            "AAA=",            "",            "CiCROQhM45JkjcmvLOVSVLqEoS1artoPCQdcipWPGa5/Ww=="        ]    }}
因为数据过大该块内只保留了一个交易,以及去掉了证书局部
以上的数据都能够在BSN测试网服务中查问操作

本文由mdnice多平台公布