前言
本文共分三部份介绍,第一部分先了解相关术语词,第二部分进行过程的分析,第三部份进行方法详解。
正文
一、相关术语词
ChaincodeSpec:Chaincode说明,描述说明chaincode的结构体,简称为CS。
ChaincodeDeploymentSpec:Chaincode部署说明,描述说明一个chaincode该如何部署,简称为CDS。
ccpackfile:一种由chaincode命令的package或signpackage子命令生成的chaincode二进制部署包。前者是由CDS对象生成,后者是由Envelope对象(包含签名过的CDS)生成。
ChaincodeInvocationSpec:chaincode调用说明,简称为CIS,主要存储了一份chaincode说明
Proposal/SignedProposal:Proposal封装了chaincode的数据
CCContext:chaincode context,即链码内容
chaincodeCtorJSON:较复杂的Flag之一,指定chaincode要运行的函数和函数的参数,原型为JSON格式的字符串,如{ "Function":"func", "Args":["param"] }
。可以直接调用json.Unmarshal
将原型字符串存入一个ChaincodeInput
对象中,作为ChaincodeSpec的Input。
ccHdrExt:全称ChaincodeHeaderExtension,是Header类型为Chaincode时要使用的Header的扩展消息。 此扩展用于指定要调用的chaincode以及ledger应显示的内容。
二、执行过程分析
peer注册的chaincode命令
mainCmd.AddCommand(chaincode.Cmd(nil))
//github.com/hyperledger/fabric/peer/chaincode/chaincode.go
//peer chaincode
func Cmd(cf *ChaincodeCmdFactory) *cobra.Command {
addFlags(chaincodeCmd)
chaincodeCmd.AddCommand(installCmd(cf))
chaincodeCmd.AddCommand(instantiateCmd(cf))
chaincodeCmd.AddCommand(invokeCmd(cf))
chaincodeCmd.AddCommand(packageCmd(cf, nil))
chaincodeCmd.AddCommand(queryCmd(cf))
chaincodeCmd.AddCommand(signpackageCmd(cf))
chaincodeCmd.AddCommand(upgradeCmd(cf))
return chaincodeCmd
}
var chaincodeCmd = &cobra.Command{
Use: chainFuncName,
Short: fmt.Sprint(shortDes),
Long: fmt.Sprint(longDes),
}
由上可知,peer chaincode命令中新增了六项子命令,以下先介绍peer chinacode install。
在chiancode.go中因含有init方法,所以同时为peer chaincode新增了以下命令标记
func init() {
resetFlags()
}
// Explicitly define a method to facilitate tests
func resetFlags() {
flags = &pflag.FlagSet{}
flags.StringVarP(&chaincodeLang, "lang", "l", "golang",
fmt.Sprintf("Language the %s is written in", chainFuncName))
flags.StringVarP(&chaincodeCtorJSON, "ctor", "c", "{}",
fmt.Sprintf("Constructor message for the %s in JSON format", chainFuncName))
flags.StringVarP(&chaincodePath, "path", "p", common.UndefinedParamValue,
fmt.Sprintf("Path to %s", chainFuncName))
flags.StringVarP(&chaincodeName, "name", "n", common.UndefinedParamValue,
fmt.Sprint("Name of the chaincode"))
flags.StringVarP(&chaincodeVersion, "version", "v", common.UndefinedParamValue,
fmt.Sprint("Version of the chaincode specified in install/instantiate/upgrade commands"))
flags.StringVarP(&chaincodeUsr, "username", "u", common.UndefinedParamValue,
fmt.Sprint("Username for chaincode operations when security is enabled"))
flags.StringVarP(&customIDGenAlg, "tid", "t", common.UndefinedParamValue,
fmt.Sprint("Name of a custom ID generation algorithm (hashing and decoding) e.g. sha256base64"))
flags.StringVarP(&chainID, "channelID", "C", util.GetTestChainID(),
fmt.Sprint("The channel on which this command should be executed"))
flags.StringVarP(&policy, "policy", "P", common.UndefinedParamValue,
fmt.Sprint("The endorsement policy associated to this chaincode"))
flags.StringVarP(&escc, "escc", "E", common.UndefinedParamValue,
fmt.Sprint("The name of the endorsement system chaincode to be used for this chaincode"))
flags.StringVarP(&vscc, "vscc", "V", common.UndefinedParamValue,
fmt.Sprint("The name of the verification system chaincode to be used for this chaincode"))
}
var (
chaincodeLang string
chaincodeCtorJSON string
chaincodePath string
chaincodeName string
chaincodeUsr string // Not used
chaincodeQueryRaw bool
chaincodeQueryHex bool
customIDGenAlg string
chainID string
chaincodeVersion string
policy string
escc string
vscc string
policyMarhsalled []byte
orderingEndpoint string
tls bool
caFile string
)
示例:
peer chaincode install -n mycc -v v0 -p github.com/chaincode_example02
-n表示chaincode的名字,对应定义的变量chaincodeName
-l表示chaincode的语言,对应定义的变量chaincodeLang(java,go,nodejs)
peer chaincode install命令
peer chaincode install支持以下两种方式:
1、指定代码方式,peer chaincode install -n <链码名称> -v <链码版本> -p <链码路径>
2、基于链码打包文件方式,peer chaincode install <链码打包文件>
//github.com/hyperledger/fabric/peer/chaincode/install.go
//添加install命令
chaincodeCmd.AddCommand(installCmd(cf))
//install实现
func installCmd(cf *ChaincodeCmdFactory) *cobra.Command {
chaincodeInstallCmd = &cobra.Command{
Use: "install",
Short: fmt.Sprint(installDesc),
Long: fmt.Sprint(installDesc),
ValidArgs: []string{"1"},
RunE: func(cmd *cobra.Command, args []string) error {
//命令运行入口
//保存第一个参数到ccpackfile,并传入chaincodeInstall()
var ccpackfile string
if len(args) > 0 {
ccpackfile = args[0]
}
return chaincodeInstall(cmd, ccpackfile, cf)
},
}
flagList := []string{
"lang",
"ctor",
"path",
"name",
"version",
}
attachFlags(chaincodeInstallCmd, flagList)
return chaincodeInstallCmd
}
chaincodeInstall()执行过程
//github.com/hyperledger/fabric/peer/chaincode/install.go
func chaincodeInstall(cmd *cobra.Command, ccpackfile string, cf *ChaincodeCmdFactory) error {
var err error
if cf == nil {
//初始化Endorser客户端,构造ChaincodeCmdFactory
cf, err = InitCmdFactory(true, false)
if err != nil {
return err
}
}
var ccpackmsg proto.Message
//链码执行方式判断
if ccpackfile == "" {
//指定代码方式
if chaincodePath == common.UndefinedParamValue || chaincodeVersion == common.UndefinedParamValue || chaincodeName == common.UndefinedParamValue {
return fmt.Errorf("Must supply value for %s name, path and version parameters.", chainFuncName)
}
//构造ChaincodeDeploymentSpec
ccpackmsg, err = genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
if err != nil {
return err
}
} else {
//基于链码打包方式
//read in a package generated by the "package" sub-command (and perhaps signed
//by multiple owners with the "signpackage" sub-command)
//获取ccpackfile包内容并构造ChaincodeDeploymentSpec
var cds *pb.ChaincodeDeploymentSpec
ccpackmsg, cds, err = getPackageFromFile(ccpackfile)
if err != nil {
return err
}
//get the chaincode details from cds
cName := cds.ChaincodeSpec.ChaincodeId.Name
cVersion := cds.ChaincodeSpec.ChaincodeId.Version
//if user provided chaincodeName, use it for validation
if chaincodeName != "" && chaincodeName != cName {
return fmt.Errorf("chaincode name %s does not match name %s in package", chaincodeName, cName)
}
//if user provided chaincodeVersion, use it for validation
if chaincodeVersion != "" && chaincodeVersion != cVersion {
return fmt.Errorf("chaincode version %s does not match version %s in packages", chaincodeVersion, cVersion)
}
}
err = install(ccpackmsg, cf)
return err
}
install()执行过程
//github.com/hyperledger/fabric/peer/chaincode/install.go
//安装到peer.address,即core.yaml配置文件定义中地址,默认为0.0.0.0:7051
func install(msg proto.Message, cf *ChaincodeCmdFactory) error {
//获取lscc签名身份,并序列化
creator, err := cf.Signer.Serialize()
if err != nil {
return fmt.Errorf("Error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
//由ChaincodeDeploymentSpec生成提案Proposal
prop, _, err := utils.CreateInstallProposalFromCDS(msg, creator)
if err != nil {
return fmt.Errorf("Error creating proposal %s: %s", chainFuncName, err)
}
//对提案进行签名
var signedProp *pb.SignedProposal
signedProp, err = utils.GetSignedProposal(prop, cf.Signer)
if err != nil {
return fmt.Errorf("Error creating signed proposal %s: %s", chainFuncName, err)
}
//提交提案并等待响应
proposalResponse, err := cf.EndorserClient.ProcessProposal(context.Background(), signedProp)
if err != nil {
return fmt.Errorf("Error endorsing %s: %s", chainFuncName, err)
}
if proposalResponse != nil {
logger.Debugf("Installed remotely %v", proposalResponse)
}
return nil
}
三、方法详解
InitCmdFactory(true,false)
初始化客户端并构造ChaincodeCmdFactory
//github.com/hyperledger/fabric/peer/chaincode/common.go
func InitCmdFactory(isEndorserRequired, isOrdererRequired bool) (*ChaincodeCmdFactory, error) {
var err error
var endorserClient pb.EndorserClient
if isEndorserRequired {
endorserClient, err = common.GetEndorserClientFnc()
if err != nil {
return nil, fmt.Errorf("Error getting endorser client %s: %s", chainFuncName, err)
}
}
signer, err := common.GetDefaultSignerFnc()
if err != nil {
return nil, fmt.Errorf("Error getting default signer: %s", err)
}
var broadcastClient common.BroadcastClient
//isOrdererRequired默认为false,暂时未用到,暂时跳过
if isOrdererRequired {
if len(orderingEndpoint) == 0 {
orderingEndpoints, err := common.GetOrdererEndpointOfChainFnc(chainID, signer, endorserClient)
if err != nil {
return nil, fmt.Errorf("Error getting (%s) orderer endpoint: %s", chainID, err)
}
if len(orderingEndpoints) == 0 {
return nil, fmt.Errorf("Error no orderer endpoint got for %s", chainID)
}
logger.Infof("Get chain(%s) orderer endpoint: %s", chainID, orderingEndpoints[0])
orderingEndpoint = orderingEndpoints[0]
}
broadcastClient, err = common.GetBroadcastClientFnc(orderingEndpoint, tls, caFile)
if err != nil {
return nil, fmt.Errorf("Error getting broadcast client: %s", err)
}
}
//构造并返回ChaincodeCmdFactory
return &ChaincodeCmdFactory{
EndorserClient: endorserClient,
Signer: signer,
BroadcastClient: broadcastClient,
}, nil
}
//github.com/hyperledger/fabric/peer/common/common.go
//初始化进行了赋值
GetEndorserClientFnc = GetEndorserClient
//与peer服务建立连接
func GetEndorserClient() (pb.EndorserClient, error) {
clientConn, err := peer.NewPeerClientConnection()
if err != nil {
err = errors.ErrorWithCallstack("PER", "404", "Error trying to connect to local peer").WrapError(err)
return nil, err
}
endorserClient := pb.NewEndorserClient(clientConn)
return endorserClient, nil
}
//github.com/hyperledger/fabric/peer/common/common.go
//初始化进行了赋值
GetDefaultSignerFnc = GetDefaultSigner
//获取启动peer服务时建立的默认的msp身份签名
func GetDefaultSigner() (msp.SigningIdentity, error) {
signer, err := mspmgmt.GetLocalMSP().GetDefaultSigningIdentity()
if err != nil {
return nil, fmt.Errorf("Error obtaining the default signing identity, err %s", err)
}
return signer, err
}
genChaincodeDeploymentSpec(cmd, chaincodeName, chaincodeVersion)
生成chaincode部署说明对象
//github.com/hyperledger/fabric/peer/chaincode/install.go
func genChaincodeDeploymentSpec(cmd *cobra.Command, chaincodeName, chaincodeVersion string) (*pb.ChaincodeDeploymentSpec, error) {
//通过chaincode提供者对象判断是否存在
if existed, _ := ccprovider.ChaincodePackageExists(chaincodeName, chaincodeVersion); existed {
return nil, fmt.Errorf("chaincode %s:%s already exists", chaincodeName, chaincodeVersion)
}
spec, err := getChaincodeSpec(cmd)
if err != nil {
return nil, err
}
cds, err := getChaincodeDeploymentSpec(spec, true)
if err != nil {
return nil, fmt.Errorf("Error getting chaincode code %s: %s", chainFuncName, err)
}
//返回构造好的chaincodeDeploymentSpec
return cds, nil
}
//github.com/hyperledger/fabric/peer/chaincode/common.go
//构造chaincode说明
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
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)
}
chaincodeLang = strings.ToUpper(chaincodeLang)
if pb.ChaincodeSpec_Type_value[chaincodeLang] == int32(pb.ChaincodeSpec_JAVA) {
return nil, fmt.Errorf("Java chaincode is work-in-progress and disabled")
}
spec = &pb.ChaincodeSpec{
Type: pb.ChaincodeSpec_Type(pb.ChaincodeSpec_Type_value[chaincodeLang]),
ChaincodeId: &pb.ChaincodeID{Path: chaincodePath, Name: chaincodeName, Version: chaincodeVersion},
Input: input,
}
return spec, nil
}
//github.com/hyperledger/fabric/peer/chaincode/common.go
//根据传入的chaincodespec构造chaincodeDeploymentSpec
func getChaincodeDeploymentSpec(spec *pb.ChaincodeSpec, crtPkg bool) (*pb.ChaincodeDeploymentSpec, error) {
var codePackageBytes []byte
if chaincode.IsDevMode() == false && crtPkg {
var err error
if err = checkSpec(spec); err != nil {
return nil, err
}
codePackageBytes, err = container.GetChaincodePackageBytes(spec)
if err != nil {
err = fmt.Errorf("Error getting chaincode package bytes: %s", err)
return nil, err
}
}
chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
return chaincodeDeploymentSpec, nil
}
getPackageFromFile(ccpackfile)
//获取chaincode包内容,并提取ChaincodeDeploymentSpec
func getPackageFromFile(ccpackfile string) (proto.Message, *pb.ChaincodeDeploymentSpec, error) {
b, err := ioutil.ReadFile(ccpackfile)
if err != nil {
return nil, nil, err
}
//the bytes should be a valid package (CDS or SigedCDS)
//GetCCPackage将buffer中的数据存入对象CDSpackage或SignedCDSpackage
ccpack, err := ccprovider.GetCCPackage(b)
if err != nil {
return nil, nil, err
}
//either CDS or Envelope
//将buffer转化为CDS或envelope(信封)提供部署时用
o, err := ccpack.GetPackageObject(), nil
if err != nil {
return nil, nil, err
}
//try CDS first
cds, ok := o.(*pb.ChaincodeDeploymentSpec)
if !ok || cds == nil {
//try Envelope next
env, ok := o.(*pcommon.Envelope)
if !ok || env == nil {
return nil, nil, fmt.Errorf("error extracting valid chaincode package")
}
//this will check for a valid package Envelope
_, sCDS, err := ccpackage.ExtractSignedCCDepSpec(env)
if err != nil {
return nil, nil, fmt.Errorf("error extracting valid signed chaincode package(%s)", err)
}
//...and get the CDS at last
cds, err = utils.GetChaincodeDeploymentSpec(sCDS.ChaincodeDeploymentSpec)
if err != nil {
return nil, nil, fmt.Errorf("error extracting chaincode deployment spec(%s)", err)
}
}
return o, cds, nil
}
CreateInstallProposalFromCDS(msg, creator)
从ChaincodeDeploymentSpec创建chaincode安装提案
func createProposalFromCDS(chainID string, msg proto.Message, creator []byte, policy []byte, escc []byte, vscc []byte, propType string) (*peer.Proposal, string, error) {
//in the new mode, cds will be nil, "deploy" and "upgrade" are instantiates.
var ccinp *peer.ChaincodeInput
var b []byte
var err error
if msg != nil {
b, err = proto.Marshal(msg)
if err != nil {
return nil, "", err
}
}
switch propType {
case "deploy":
fallthrough
case "upgrade":
cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
if !ok || cds == nil {
return nil, "", fmt.Errorf("invalid message for creating lifecycle chaincode proposal from")
}
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), []byte(chainID), b, policy, escc, vscc}}
case "install":
ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
}
//构造lscc说明
lsccSpec := &peer.ChaincodeInvocationSpec{
ChaincodeSpec: &peer.ChaincodeSpec{
Type: peer.ChaincodeSpec_GOLANG,
ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
Input: ccinp}}
//...and get the proposal for it
return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lsccSpec, creator)
}
func CreateProposalFromCIS(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) {
return CreateChaincodeProposal(typ, chainID, cis, creator)
}
func CreateChaincodeProposal(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) {
return CreateChaincodeProposalWithTransient(typ, chainID, cis, creator, nil)
}
//创建交易ID(txid)
func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
// generate a random nonce
nonce, err := crypto.GetRandomNonce()
if err != nil {
return nil, "", err
}
// compute txid
txid, err := ComputeProposalTxID(nonce, creator)
if err != nil {
return nil, "", err
}
return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap)
}
//对数据进行序列化处理,返回提案信息
func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
if err != nil {
return nil, "", err
}
cisBytes, err := proto.Marshal(cis)
if err != nil {
return nil, "", err
}
ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
if err != nil {
return nil, "", err
}
// TODO: epoch is now set to zero. This must be changed once we
// get a more appropriate mechanism to handle it in.
var epoch uint64 = 0
timestamp := util.CreateUtcTimestamp()
//将信息构建到ChannelHeader
hdr := &common.Header{ChannelHeader: MarshalOrPanic(&common.ChannelHeader{
Type: int32(typ),
TxId: txid,
Timestamp: timestamp,
ChannelId: chainID,
Extension: ccHdrExtBytes,
Epoch: epoch}),
SignatureHeader: MarshalOrPanic(&common.SignatureHeader{Nonce: nonce, Creator: creator})}
hdrBytes, err := proto.Marshal(hdr)
if err != nil {
return nil, "", err
}
return &peer.Proposal{Header: hdrBytes, Payload: ccPropPayloadBytes}, txid, nil
}
utils.GetSignedProposal(prop, cf.Signer)
对提案进行签名
func GetSignedProposal(prop *peer.Proposal, signer msp.SigningIdentity) (*peer.SignedProposal, error) {
// check for nil argument
if prop == nil || signer == nil {
return nil, fmt.Errorf("Nil arguments")
}
propBytes, err := GetBytesProposal(prop)
if err != nil {
return nil, err
}
signature, err := signer.Sign(propBytes)
if err != nil {
return nil, err
}
return &peer.SignedProposal{ProposalBytes: propBytes, Signature: signature}, nil
}
func GetBytesProposal(prop *peer.Proposal) ([]byte, error) {
propBytes, err := proto.Marshal(prop)
return propBytes, err
}
cf.EndorserClient.ProcessProposal(context.Background(), signedProp)
提交提案并模拟执行,对结果进行背书等详细过程,访问https://blog.youkuaiyun.com/cs380637384/article/details/81906712