从peer命令行参数到Transaction对象

以Invoke Transaction的构造为例. 分析基于v6.0版本的代码.

构造一个Transaction对象的流程

Transaction对象定义在fabric/protos/fabric.pb.go中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Transaction struct {
Type Transaction_Type `protobuf:"varint,1,opt,name=type,enum=protos.Transaction_Type" json:"type,omitempty"`
// store ChaincodeID as bytes so its encrypted value can be stored
ChaincodeID []byte `protobuf:"bytes,2,opt,name=chaincodeID,proto3" json:"chaincodeID,omitempty"`
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
Metadata []byte `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"`
Txid string `protobuf:"bytes,5,opt,name=txid" json:"txid,omitempty"`
Timestamp *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=timestamp" json:"timestamp,omitempty"`
ConfidentialityLevel ConfidentialityLevel `protobuf:"varint,7,opt,name=confidentialityLevel,enum=protos.ConfidentialityLevel" json:"confidentialityLevel,omitempty"`
ConfidentialityProtocolVersion string `protobuf:"bytes,8,opt,name=confidentialityProtocolVersion" json:"confidentialityProtocolVersion,omitempty"`
Nonce []byte `protobuf:"bytes,9,opt,name=nonce,proto3" json:"nonce,omitempty"`
ToValidators []byte `protobuf:"bytes,10,opt,name=toValidators,proto3" json:"toValidators,omitempty"`
Cert []byte `protobuf:"bytes,11,opt,name=cert,proto3" json:"cert,omitempty"`
Signature []byte `protobuf:"bytes,12,opt,name=signature,proto3" json:"signature,omitempty"`
}

构造一个Transcation对象的关键在于如何填充Payload字段. 对于Type为Invoke的Transaction来说, Payload字段是一个Marshal过的ChaincodeInvationSpec对象, 该对象在fabric/protos/chaincode.pb.go中定义, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
type ChaincodeInvocationSpec struct {
ChaincodeSpec *ChaincodeSpec `protobuf:"bytes,1,opt,name=chaincodeSpec" json:"chaincodeSpec,omitempty"`
// This field can contain a user-specified ID generation algorithm
// If supplied, this will be used to generate a ID
// If not supplied (left empty), sha256base64 will be used
// The algorithm consists of two parts:
// 1, a hash function
// 2, a decoding used to decode user (string) input to bytes
// Currently, SHA256 with BASE64 is supported (e.g. idGenerationAlg='sha256base64')
IdGenerationAlg string `protobuf:"bytes,2,opt,name=idGenerationAlg" json:"idGenerationAlg,omitempty"`
}

里面的ChaincodeSpec在同一个文件下面:

1
2
3
4
5
6
7
8
9
10
type ChaincodeSpec struct {
Type ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"`
ChaincodeID *ChaincodeID `protobuf:"bytes,2,opt,name=chaincodeID" json:"chaincodeID,omitempty"`
CtorMsg *ChaincodeInput `protobuf:"bytes,3,opt,name=ctorMsg" json:"ctorMsg,omitempty"`
Timeout int32 `protobuf:"varint,4,opt,name=timeout" json:"timeout,omitempty"`
SecureContext string `protobuf:"bytes,5,opt,name=secureContext" json:"secureContext,omitempty"`
ConfidentialityLevel ConfidentialityLevel `protobuf:"varint,6,opt,name=confidentialityLevel,enum=protos.ConfidentialityLevel" json:"confidentialityLevel,omitempty"`
Metadata []byte `protobuf:"bytes,7,opt,name=metadata,proto3" json:"metadata,omitempty"`
Attributes []string `protobuf:"bytes,8,rep,name=attributes" json:"attributes,omitempty"`
}

其中最核心的字段为CTorMsgChaincodeId. ChiancodeId为chaincode的路径,CTorMsg是一个指向ChaincodeInput对象的指针, 而ChiancodeInput就是调用Chaincode时指定的函数名以及参数.

因此构造一个Invoke类型的Transaction的关键步骤为:

1
CTorMsg, ChaincodeID==>ChaincodeSpec==>ChaincodeInvocationSpec==>Transaction

peer客户端是如何把命令行参数构造成相应Transaction对象的

peer使用了cobra框架, 对应chaincode子命令的相应定义在fabric/peer/chaincode/chaincode.go. 通过分析发现与invoke操作挂钩的是fabric/peer/chaincode/invoke.go里面的chaincodeInvoke, 而这个函数又转而直接调用了fabric/peer/chaincode/common.go里的chaincodeInvokeOrQuery. 关键的部分从这里开始.

函数先是调用了getChaincodeSpecification来构造一个ChaincodeSpec.

1
spec, err := getChaincodeSpecification(cmd)

跟到getChaincodeSpecification里, 可以看到在这里ChaincodeSpec是如何被填充的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spec := &pb.ChaincodeSpec{}
if err := checkChaincodeCmdParams(cmd); err != nil {
return spec, err
}
// Build the spec
input := &pb.ChaincodeInput{}
if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
return spec, fmt.Errorf("Chaincode argument error: %s", err)
}
var attributes []string
if err := json.Unmarshal([]byte(chaincodeAttributesJSON), &attributes); err != nil {
return spec, fmt.Errorf("Chaincode argument error: %s", err)
}
chaincodeLang = strings.ToUpper(chaincodeLang)
spec = &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]),
ChaincodeID: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName},
CtorMsg: input,
Attributes: attributes,
}

其中的chaincodeCtorJSON等变量就是从命令行传入的JSON格式的参数, 这个函数把这些参数Unmarshal之后填入了目标chaincodeSpec中.

得到chaincodeSpec之后,回到chaincodeInvokeOrQuery,可以看到这个chaincodeSpec被用以勾造了一个ChaincodeInvocationSpec:

1
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}

按照上一节说的构造顺序, 接下来只要把这个ChaincodeInvocationSpec给marshal了然后扔进一个Transaction里就大功告成了. 而这个动作是在fabric/core/devops.gocreateExecTx函数中完成的(这里实际上省略了很长一段调用链的分析, 大概流程为: grpc调用invoke方法->invoke_handler->devopsServer.invoke->createExecTx).

到了createExecTx思路实际上就很清晰了, 里面有关键性的一行:

1
tx, err = pb.NewChaincodeExecute(spec, uuid, t)

其中uuid是用util.GenerateIdWithAlg由spec中的CtorMsg算出来的, t就是Transaction类型.也就是说, pb.NewChaincodeExecute完成了最终的Transaction组装.它位于fabridc/protos/transaction.go, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func NewChaincodeExecute(chaincodeInvocationSpec *ChaincodeInvocationSpec, uuid string, typ Transaction_Type) (*Transaction, error) {
transaction := new(Transaction)
transaction.Type = typ
transaction.Txid = uuid
transaction.Timestamp = util.CreateUtcTimestamp()
cID := chaincodeInvocationSpec.ChaincodeSpec.GetChaincodeID()
if cID != nil {
data, err := proto.Marshal(cID)
if err != nil {
return nil, fmt.Errorf("Could not marshal chaincode : %s", err)
}
transaction.ChaincodeID = data
}
data, err := proto.Marshal(chaincodeInvocationSpec)
if err != nil {
return nil, fmt.Errorf("Could not marshal payload for chaincode invocation: %s", err)
}
transaction.Payload = data
return transaction, nil
}

至此, 整个流程完成.