Fabric1.4原始碼解析:鏈碼例項化過程

觸不可及`發表於2019-07-08

之前說完了鏈碼的安裝過程,接下來說一下鏈碼的例項化過程好了,再然後是鏈碼的呼叫過程。其實這幾個過程內容已經很相似了,都是涉及到Proposal,不過整體流程還是要說一下的。
同樣,切入點仍然是fabric/peer/main.go檔案中的main()方法:

#這一句定義了關於通過Peer節點操作鏈碼的命令
mainCmd.AddCommand(chaincode.Cmd(nil))

然後是fabric/peer/chaincode/chaincode.go檔案中的Cmd()方法,這裡則是具體的操作鏈碼的命令,其中就有對鏈碼進行例項化的命令:

chaincodeCmd.AddCommand(instantiateCmd(cf))

最後呼叫到了fabric/peer/chaincode/instantiate.go檔案中的第57行的instantiate()方法。也就是說,當我們在Peer節點執行以下命令時,最終會到這個方法:

#以官方的例項化鏈碼的方法為例
peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR      ('Org1MSP.member','Org2MSP.member')"

接下來就看一下instantiate()方法:

#首先獲取要例項化的鏈碼的資訊
spec, err := getChaincodeSpec(cmd)
if err != nil {
    return nil, err
}

getChaincodeSpec()方法在peer/chaincode/common.go檔案中第69行:

    #將使用者例項化鏈碼所執行的命令作為引數傳入進去
func getChaincodeSpec(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
    #定義一個ChaincodeSpec結構體
    spec := &pb.ChaincodeSpec{}
====================ChaincodeSpec===========================
type ChaincodeSpec struct {
    #Type表示鏈碼的型別 有GOLANG,NODE,CAR,JAVA,UNDEFINED五種型別
    Type                 ChaincodeSpec_Type `protobuf:"varint,1,opt,name=type,proto3,enum=protos.ChaincodeSpec_Type" json:"type,omitempty"`
    #ChaincodeId也是一個結構體,定義了鏈碼的路徑資訊,鏈碼的名稱以及版本資訊
    ChaincodeId          *ChaincodeID       `protobuf:"bytes,2,opt,name=chaincode_id,json=chaincodeId,proto3" json:"chaincode_id,omitempty"`
    #ChaincodeInput結構體中定義鏈碼的功能以及函式引數資訊
    Input                *ChaincodeInput    `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"`
    Timeout              int32              `protobuf:"varint,4,opt,name=timeout,proto3" json:"timeout,omitempty"`
    XXX_NoUnkeyedLiteral struct{}           `json:"-"`
    XXX_unrecognized     []byte             `json:"-"`
    XXX_sizecache        int32              `json:"-"`
}
====================ChaincodeSpec===========================
    #對使用者輸入的命令進行檢查
    if err := checkChaincodeCmdParams(cmd); err != nil {
        // unset usage silence because it's a command line usage error
        cmd.SilenceUsage = false
        return spec, err
    }

    #定義ChaincodeInput結構體,就是上面說過的那個
    input := &pb.ChaincodeInput{}
    if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
        return spec, errors.Wrap(err, "chaincode argument error")
    }

    chaincodeLang = strings.ToUpper(chaincodeLang)
    #最後將建立的ChaincodeSpec結構體返回 
    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
}

看一下checkChaincodeCmdParams()方法做了哪些工作,在219行:

func checkChaincodeCmdParams(cmd *cobra.Command) error {
    #檢查使用者輸入的鏈碼名稱是否為空字串
    if chaincodeName == common.UndefinedParamValue {
        return errors.Errorf("must supply value for %s name parameter", chainFuncName)
    }
    #呼叫的方法是否為instantiate,install,upgrade,package其中的一個
    if cmd.Name() == instantiateCmdName || cmd.Name() == installCmdName ||
        cmd.Name() == upgradeCmdName || cmd.Name() == packageCmdName {
        if chaincodeVersion == common.UndefinedParamValue {
            return errors.Errorf("chaincode version is not provided for %s", cmd.Name())
        }

        if escc != common.UndefinedParamValue {
            logger.Infof("Using escc %s", escc)
        } else {
            logger.Info("Using default escc")
            escc = "escc"
        }

        if vscc != common.UndefinedParamValue {
            logger.Infof("Using vscc %s", vscc)
        } else {
            logger.Info("Using default vscc")
            vscc = "vscc"
        }

        if policy != common.UndefinedParamValue {
            #獲取定義的策略,就比如   OR ('Org1MSP.member','Org2MSP.member')這條資訊是否有誤
            p, err := cauthdsl.FromString(policy)
            if err != nil {
                return errors.Errorf("invalid policy %s", policy)
            }
            policyMarshalled = putils.MarshalOrPanic(p)
        }
        #如果定義了配置檔案,則從配置檔案中讀取配置資訊
        if collectionsConfigFile != common.UndefinedParamValue {
            var err error
            collectionConfigBytes, err = getCollectionConfigFromFile(collectionsConfigFile)
            if err != nil {
                return errors.WithMessage(err, fmt.Sprintf("invalid collection configuration in file %s", collectionsConfigFile))
            }
        }
    }
    #對使用者傳入的例項化引數比如:-c '{"Args":["init","a","100","b","200"]}'
    if chaincodeCtorJSON != "{}" {
        ...
    }

    return nil
}

回到instantiate()方法:

cds, err := getChaincodeDeploymentSpec(spec, false)
if err != nil {
    return nil, fmt.Errorf("error getting chaincode code %s: %s", chaincodeName, err)
}

獲取ChaincodeDeploymentSpec這個結構體:

type ChaincodeDeploymentSpec struct {
    #這個是之前獲取到的結構體
    ChaincodeSpec        *ChaincodeSpec                               `protobuf:"bytes,1,opt,name=chaincode_spec,json=chaincodeSpec,proto3" json:"chaincode_spec,omitempty"`
    #鏈碼資料 
    CodePackage          []byte                                       `protobuf:"bytes,3,opt,name=code_package,json=codePackage,proto3" json:"code_package,omitempty"`
    #鏈碼的執行環境,有兩種,Docker容器或者直接在系統中執行
    ExecEnv              ChaincodeDeploymentSpec_ExecutionEnvironment `protobuf:"varint,4,opt,name=exec_env,json=execEnv,proto3,enum=protos.ChaincodeDeploymentSpec_ExecutionEnvironment" json:"exec_env,omitempty"`
    XXX_NoUnkeyedLiteral struct{}                                     `json:"-"`
    XXX_unrecognized     []byte                                       `json:"-"`
    XXX_sizecache        int32                                        `json:"-"`
}

看一下如何獲取ChaincodeDeploymentSpec結構體:

#定義了ChaincodeDeploymentSpec中的CodePackage
var codePackageBytes []byte
#判斷是否為開發模式
if chaincode.IsDevMode() == false && crtPkg {
    var err error
    #如果不是則檢查鏈碼是否為空,以及路徑是否正確
    if err = checkSpec(spec); err != nil {
        return nil, err
    }
      #將鏈碼轉換為Byte資料
    codePackageBytes, err = container.GetChaincodePackageBytes(platformRegistry, spec)
    ...
}
#構造chaincodeDeploymentSpec並返回 
chaincodeDeploymentSpec := &pb.ChaincodeDeploymentSpec{ChaincodeSpec: spec, CodePackage: codePackageBytes}
return chaincodeDeploymentSpec, nil

回到instantiate()方法:

#獲取一全個簽名者,需要對建立例項化鏈碼的Proposal進行簽名
creator, err := cf.Signer.Serialize()
if err != nil {
        return nil, fmt.Errorf("error serializing identity for %s: %s", cf.Signer.GetIdentifier(), err)
}
#要建立用於例項化鏈碼的Proposal了
prop, _, err := utils.CreateDeployProposalFromCDS(channelID, cds, creator, policyMarshalled, []byte(escc), []byte(vscc), collectionConfigBytes)
if err != nil {
    return nil, fmt.Errorf("error creating proposal  %s: %s", chainFuncName, err)
}

看一下CreateDeployProposalFromCDS()方法,看名字瞭解到是根據chaincodeDeploymentSpec建立用於部署鏈碼的Proposal

func CreateDeployProposalFromCDS(
    #通道Id
    chainID string,
    cds *peer.ChaincodeDeploymentSpec,
    #簽名者
    creator []byte,
    #具體的策略
    policy []byte,
    #endorser system chaincode
    escc []byte,
    #Verification System ChainCode
    vscc []byte,
    collectionConfig []byte) (*peer.Proposal, string, error) {
    #下面的兩個方法呼叫的是同一個,只是傳入的引數不同,點進去
    if collectionConfig == nil {
        return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc)
    }
    return createProposalFromCDS(chainID, cds, creator, "deploy", policy, escc, vscc, collectionConfig)
}

該方法在538行,接下來的部分與客戶端安裝鏈碼所執行的流程基本是相同的,只有下面的一部分不同:

#對於例項化鏈碼來說,執行的是deploy與upgrade這兩部分,而安裝鏈碼則是install這部分,差異就在於ChaincodeInput結構體內的引數不同
case "deploy":
        fallthrough
case "upgrade":
        cds, ok := msg.(*peer.ChaincodeDeploymentSpec)
        if !ok || cds == nil {
            return nil, "", errors.New("invalid message for creating lifecycle chaincode proposal")
        }
        Args := [][]byte{[]byte(propType), []byte(chainID), b}
        Args = append(Args, args...)

        ccinp = &peer.ChaincodeInput{Args: Args}
    case "install":
        ccinp = &peer.ChaincodeInput{Args: [][]byte{[]byte(propType), b}}
    }
    // wrap the deployment in an invocation spec to lscc...
    lsccSpec := &peer.ChaincodeInvocationSpec{
        ChaincodeSpec: &peer.ChaincodeSpec{
            Type:        peer.ChaincodeSpec_GOLANG,
            ChaincodeId: &peer.ChaincodeID{Name: "lscc"},
            Input:       ccinp,
        },
    }

剩下的部分就不再重複看了,可以參考Fabric1.4原始碼解析:客戶端安裝鏈碼這篇文章。
總的來說,整個流程共有以下幾部分:

  1. 根據使用者執行例項化鏈碼的命令啟動全過程
  2. 獲取需要例項化鏈碼的基本資訊
  3. 建立ChaincodeDeploymentSpec結構體.
  4. 獲取用於對Proposal進行簽名的Creator
  5. 建立ProposalProposalHeader定義為ENDORSER_TRANSACTION,表示是一個需要背書的交易。
  6. 由之前獲取的Creator進行簽名操作。
  7. Peer節點呼叫ProcessProposal()方法進行處理,該方法的解析在這裡。這是一個很重要的方法。
  8. 接收到由Peer節點處理完成所返回的Response訊息後傳送到Orderer節點。
  9. Orderer節點接收到訊息後進行排序操作,如果是SOLO模式則由Orderer節點生成區塊,最後將區塊廣播至Peer節點,
  10. Peer節點接收到區塊訊息後驗證有效性,最後更新賬本資料。

最後附上參考連結:1.傳送門
2.傳送門

相關文章