Fabric1.4源碼解析:鏈碼實例化過程


之前說完了鏈碼的安裝過程,接下來說一下鏈碼的實例化過程好了,再然后是鏈碼的調用過程。其實這幾個過程內容已經很相似了,都是涉及到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.傳送門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM