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智能合约,合约提供了两个合约办法Set
、Query
。
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.go
中Block
通过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
统一。Payload
是peer.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
为合约的返回信息,蕴含Status
、Message
、Payload
。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多平台公布