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 BlockHeader
type 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 authenticated
type 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 proposal
type 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 多平台公布