Hyperledger Fabric 1.2 --- Chaincode Operator 解讀和測試(一)

前言

  本文主要目的是用於整理Hyperledger  Fabric中關於chaincode 管理和操做的內容,做者以release-1.2爲範本進行講解html

  主要參考連接: https://hyperledger-fabric.readthedocs.io/en/release-1.2/chaincode4noah.htmljava

  本文總計有兩節:node

    第一節: chaincode Operator 介紹git

    第二節: 測試和驗證github

   新入門的建議看一看有所瞭解,已經熟練也能夠查缺補漏。golang

關鍵詞

  chaincode: 能夠安裝部署在fabric網絡中的鏈上可執行程序;web

  CDS(ChaincodeDeploymentSpec ): 安裝部署時向fabric節點提交的數據,其中包含 版本、名稱、源碼等等docker

  SignedCDS: 帶有簽名的CDS,其中比CDS多包含了 ownerlist 和 instantiation policy 數據數組

  Instantiation Policy: 實例化策略,指定實例化chaincode時須要知足的條件緩存

  Endorsement Policy:背書策略,指定交易背書結果須要知足的條件。

  lscc(Lifecycle system chaincode ):用於管理application chaincode的生命週期的系統級別chaincode

正文

chaincode 的生命週期

  一個chaincode從編寫到部署須要經歷如下幾個步驟:

    • package(optional)
    • install,
    • instantiate,
    • upgrade(optional)
  • stop(no implemented)

  整個流程和咱們安裝發佈一個web服務很類似,fabric chaincode如今支持 go、java、nodejs多種語言,入門的門檻要比ethereum、eos等公鏈低不少,並且利用fabirc提供的test庫測試起來很方便,做者一般都是用go來開發chaincode,用protobuf做爲數據傳輸協議。

 

chaincode的組成部分

  ChaincodeDeploymentSpec:包含版本和須要安裝部署的chaincode;

  Instantiation Policy(Optional): 和設置背書策略所用的語法是一致;

  Owner Signatures: 由「擁有」鏈碼的實體簽署的一組簽名。


  owner Signatures 用於如下目的:

    <1>標明鏈碼的全部權;

    <2> 容許驗證包的內容;

    <3>容許校驗包的完整性;

 

  在一個channel中根據chaincode的實例化策略來驗證chaincode實例化交易建立者的身份。光翻譯文檔太沒有意思了啊,不直觀,我們直接上SingedCDS的數據結構看看,一目瞭然:

           <1>最外層 SignedCDS的結構  

// SignedChaincodeDeploymentSpec carries the CDS along with endorsements
message SignedChaincodeDeploymentSpec {

	// This is the bytes of the ChaincodeDeploymentSpec
	bytes chaincode_deployment_spec = 1;

	// This is the instantiation policy which is identical in structure
	// to endorsement policy.  This policy is checked by the VSCC at commit
	// time on the instantiation (all peers will get the same policy as it
	// will be part of the LSCC instantation record and will be part of the
	// hash as well)
	bytes instantiation_policy = 2;

	// The endorsements of the above deployment spec, the owner's signature over
	// chaincode_deployment_spec and Endorsement.endorser.
	repeated Endorsement owner_endorsements = 3;
}

 

    結構裏面的三個成員和上面三個組成部分對應 .  

  <2> ChaincodeDeploymentSpec

// Specify the deployment of a chaincode.
// TODO: Define `codePackage`.
message ChaincodeDeploymentSpec {
    // Prevent removed tag re-use
    reserved 2;
    reserved "effective_date";

    enum ExecutionEnvironment {
        DOCKER = 0; 
        SYSTEM = 1;
    }

    ChaincodeSpec chaincode_spec = 1;
    bytes code_package = 3;
    ExecutionEnvironment exec_env=  4;

}

   ExecutionEnvironment 裏面包含兩種執行環境,分別對應了fabric中的兩種chaincode類型: system chaincode(直接運行在節點上) 和 application chaincode(運行在docker 容器環境中);

  code_package: chaincode 源代碼的壓縮包

  exce_env: 傳入給chaincode的環境變量

  若是對fabric有過深刻了解的讀者應該明白,實際上所謂的chaincode部署安裝後就是一個執行在docker 容器中的一段程序,這個程序像一個服務同樣一直運行而且監聽着從peer節點轉發過來的外部請求,咱們隨便用docker inspect 命令去查看如下chaincode容器就能明白:

  <3> ChaincodeSpec

// Carries the chaincode specification. This is the actual metadata required for
// defining a chaincode.
message ChaincodeSpec {

    enum Type {
        UNDEFINED = 0;
        GOLANG = 1;
        NODE = 2;
        CAR = 3;
        JAVA = 4;
    }

    Type type = 1;
    ChaincodeID chaincode_id = 2;
    ChaincodeInput input = 3;
    int32 timeout = 4;
}

  這個結構就包含了一些其餘相關信息好比:chaincode的id,它是該chaincode在一個channel中的惟一識別符; Type:使用哪一種語言編寫的; timeout:執行chaincode請求的超時時間(這是爲了不chaincode內部死鎖,致使peer節點沒法返回執行結果給client); input ,傳入給chaincode的參數,這時invoke時候執行用的.

  做者就不在這裏多講整個chaincode的運行機制了,畢竟今天的重點不是這個,有空單獨開一個文章給你們說說。

  <4>Endorsement

// An endorsement is a signature of an endorser over a proposal response. By 
// producing an endorsement message, an endorser implicitly "approves" that 
// proposal response and the actions contained therein. When enough 
// endorsements have been collected, a transaction can be generated out of a 
// set of proposal responses. Note that this message only contains an identity 
// and a signature but no signed payload. This is intentional because 
// endorsements are supposed to be collected in a transaction, and they are all // expected to endorse a single proposal response/action (many endorsements 
// over a single proposal response) 

message Endorsement {
 // Identity of the endorser (e.g. its certificate)
 bytes endorser = 1; 
// Signature of the payload included in ProposalResponse concatenated with
 // the endorser's certificate; ie, sign(ProposalResponse.payload + endorser) 
bytes signature = 2; 
}

   上面是一個主要用在proposal_response 標明應答者身份和保證數據完整性的數據結構,endorser實際上就是公鑰證書,能夠標明身份;signature是簽名,這樣你能夠直接用endorser裏的公鑰去驗證簽名是否正確,看上面註釋寫的很清楚,這個簽名是對(data + endorser)一塊兒籤的名,不光能校驗數據的完整性,還能校驗證書的完整性,一箭雙鵰。在給chaincodespec簽名的時候也是同樣,不僅僅是對cds簽名:

// sign the concatenation of cds, instpolicy and the serialized endorser identity with this endorser's key
 signature, err := owner.Sign(append(cdsbytes, append(instpolicybytes, endorser...)...))
// each owner starts off the endorsements with one element. All such endorsed
// packages will be collected in a final package by CreateSignedCCDepSpecForInstall
// when endorsements will have all the entries

endorsements = make([]*peer.Endorsement, 1)
endorsements[0] = &peer.Endorsement{Signature: signature, Endorser: endorser}

 

建立一個Chaincode Package

  有兩種方式去打包一個chaincode

    第一種,一個chaincode對應多個owner

    第二種,一個chaincode只有一個owner,更簡單的工做流程適用於部署僅具備發出安裝事務的節點標識的簽名的SignedCDS

  能夠用peer chaincode package 命令進行打包操做,你們能夠本身動手體驗一下,關於package的參數介紹以下:

Package the specified chaincode into a deployment spec.

Usage:
  peer chaincode package [flags]

Flags:
  -s, --cc-package                  create CC deployment spec for owner endorsements instead of raw CC deployment spec
  -c, --ctor string                 Constructor message for the chaincode in JSON format (default "{}")
  -i, --instantiate-policy string   instantiation policy for the chaincode
  -l, --lang string                 Language the chaincode is written in (default "golang")
  -n, --name string                 Name of the chaincode
  -p, --path string                 Path to chaincode
  -S, --sign                        if creating CC deployment spec package for owner endorsements, also sign it with local MSP
  -v, --version string              Version of the chaincode specified in install/instantiate/upgrade commands 

  這時關於package命令的help介紹:

  讀者們須要主要關注 -s -S -i 三個主要命令:

    -s : 指定了-s 就會建立一個能夠被簽名的sign CDS 來代替raw CDS,能夠在其中附加實例化策略和owner signatures

    -S : optional若是指定了該參數則會調用本地localMspId身份去對CDS簽名;若是不指定則不會簽名,同時其餘的owner也不能對它進行簽名。

    -i:指定一個實例化chaincode的策略,它會指明那些身份能夠實例化chaincode,若是沒有指定策略,則使用缺省的策略:僅容許peeradmin身份來實例化chaincode.

 

 

  若是咱們不使用-s 命令的話,那麼-S 和 -i 即使是指定了也沒法生效,peer-cli 會生成一個raw CDS ,就是上面那個結構二中的ChaincodeDeploymentSpec,它根本不包含其餘兩種設置。

  示例命令以下:  

#這是用 -i 指定了一個實例化策略,建立一個名爲 ccpack.out的包
peer chaincode package -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/example02/cmd -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out

   讀者們能夠執行 `cat  ccpack.out` 去看一下,除了上面一片亂碼之外,下面還有一個字符串和一個證書以及簽名,這說明peer-cli 默默的就幫咱們用本地的LocalMSP(須要用戶本身指定)進行了簽名。而後把-S的符號去掉重複上面的動做你就會發現,簽名沒有了。

  下面貼一段原文:

The optional -i option allows one to specify an instantiation policy for the chaincode. The instantiation policy has the same format as an endorsement policy 
and specifies which identities can instantiate the chaincode. In the example above, only the admin of OrgA is allowed to instantiate the chaincode.
If no policy is provided, the default policy is used, which only allows the admin identity of the peer’s MSP to instantiate chaincode.

  截止做者發文期間,這個文檔的描述仍是這樣的,之因此單獨貼出來是由於這個部分是一個坑,很大很大的坑。它裏面所說的默認策略不是指當你不填加`insatiation policy`時,fabric定義的默認權限,而是指peer-cli在你不填加該屬性時,自動爲你添加的權限!它寫的沒問題,可是容易讓人混淆,至於爲何耐心往下看就明白了。

 

Package 簽名

  在建立時簽署的鏈代碼包能夠移交給其餘全部者輪流進行檢查和簽名。ChaincodeDeploymentSpec能夠選擇由集體全部者簽名,以建立SignedChaincodeDeploymentSpec(或SignedCDS)。 SignedCDS包含3個元素:

    CDS包含源代碼,鏈碼的名稱和版本;

    鏈碼的實例化策略,語法和背書策略相同;

    經過Endorserment數組組成chainodowner list;

  在有多個owner的狀況要輪流順序簽名:

 

#org1  
peer chaincode signpackage ccpack.out signedccpack.out
#org2 peer chaincode signpackage signedccpack.out signedccpack2.out

 

   仍是像上面所說的操做同樣,用 `cat  signedccpack2.out` 命令查看一下,你就會看到底部多出來的簽名和證書。

  下面貼一段原文:

Note that this endorsement policy is determined out-of-band to provide proper MSP principals when the chaincode is instantiated on some channels.
If the instantiation policy is not specified, the default policy is any MSP administrator of the channel.

   這裏面也提到了default policy 和上面提到的不同,做者當時也是經歷了不少曲折才發現這個區別,最終爲了肯定到底default policy是什麼,從源碼中找出瞭如下注釋:

the default instantiation policy allows any of the channel MSP admins to be able to instantiate

   即:這句話很容易讓人產生混淆,實際上來說就是容許任意一個包含在channel內的組織的admin身份去實例化chaincode.

Install chaincode

  在fabricinstall chaincode 必須由peer對應的 admin Identity 來安裝,這個是不須要設置的,也是沒法更改的, 並且要向每個須要安裝的peer分別發送指令install chaincode 能夠指定一個上面的安裝包,也能夠直接指定路徑。不過須要注意的是,直接指定chaincode源碼路徑安裝是沒有自定義instantiate policy的,它將被賦予一個default policyPackage 簽名說的default policy)。

 

  接觸過fabric環境搭建的讀者因該很清楚,Install 命令是能夠經過 -p 參數直接指定 chaincode 路徑來安裝的,也就是說如今有兩種安裝方式:第一種,直接安裝; 第二種,打包安裝。

  示例命令:

#直接指定源碼路徑安裝
peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/example02/cmd 
	
#安裝chaicode package
peer chaincode install ccpack.out

   爲何fabric要搞的這麼複雜?install chaincode必需要由peer 的admin 來安裝,還要分別安裝不支持廣播:一方面是出於數據的隱私保護來考慮,這一點在fabirc中sidedb的特性上有體現,即使是同一個channel中的組織也未必全部的服務都是公共通用的;另外一方面,即使是同一個組織的peer也不都須要安裝 同一個chaincode,太浪費了。

  另外還要提一點是 install 這個請求是不須要打包成交易發送給 orderer的,這是做者經過sdk得出的結論,client向peer發送完請求後就結束了,沒有打包交易去廣播。這又是爲何?很簡單,若是打包成交易廣播除去,每個組織都能獲取到chaincode了,還有什麼隱私可言?

  這裏做者多說一句,Hyperledger fabric 確實功能豐富,配置靈活,但就是太靈活了,給使用形成不小的障礙。

 

Instantiation chaincode

   在實例化的時候,會根據所實例化的chaincodepolicy來校驗signed proposal creator的簽名是否是與chaincode instantiate policy所吻合。如install chaincode中所闡述,若是是一個default實例化策略的chaincode,部署時任意一個channel msp Admin.在實例化的時候會指定該chaincode的背書策略,該背書策略指明瞭交易生效須要的背書條件。

  示例命令:

#用 -P 指定了背書策略
peer chaincode instantiate -C mychannel -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR('Org1MSP.peer','Org2MSP.peer')"

   這裏須要給各位讀者一個提醒:在使用 peer-cli去實例化智能合約的時候,若是是要在多個組織和peer上部署,須要在同一個區塊時間內提交到orderer當中,不然後提交的會不成功,顯示錯誤: xxx chaincode 已經存在。這個你們能夠去試一試,一個channel內最起碼要兩個peer節點 A 和B, 無需指定實例化策略,直接用install -p 的方式安裝,全都安裝同一個chaincode,等PeerA 節點實例化交易被打包進區塊後,再去提交向PeerB節點發送實例化交易。

  爲何會這樣?這是由於一個chaincode實例化交易被廣播到本地後,及時是那些還沒來得及實例化或是不安裝的節點也會把已經實例化的結果寫入到帳本,因此再向PeerB發送實例化請求的時候發現本地的帳本中記錄該chaincode已經被實例化了。

  可是做者上面說了,install是不須要廣播的,爲何instantiation 須要廣播?這時由於instantiation的時候須要調用chaincode中的init 函數,初始化一些參數(由 -c 傳入)。第一,假如instantiation 請求不被打包成交易廣播出去寫入本地帳本,那麼初始化的參數是沒法生效的(fabric 的兩段校驗機制);第二,fabric也好bitcoin也好實際上都和狀態機有殊途同歸之妙,都追求數據的最終一致性,不過bitcoin是異步一致(commit tx 後並不當即生效),而fabric和狀態機(raft等)追求的是同步一致(在commit tx的同時,保證執行的tx是生效的),這個時候對不一樣節點之間的數據狀態一致性是有很高要求的,若是沒有上面的限制,有可能會致使不一樣節點之間數據的狀態不一致。

  peer-cli用上面的命令發送實例化請求,只會發送給實例化請求給一個peer 及  CORE_PEER_ADDRESS所指定的peer,在實際生成中確定不能這樣作。可參考fabric-sdk-go中的示例,由一個client 同時向多個peer節點發送實例化請求,而後在將peer返回的實例化結果打包成tx,廣播出去,這樣就不會形成衝突。

  另外,再說一點,如上文所說chaincode實例化成功後會建立一個chaincode 容器,那麼這個容器是何時建立的?是接收到實例化請求後,仍是在實例化交易寫入帳本?答案是接收到實例化請求以後,也就是說可能tx提交不成功chaincode容器也可能會被建立,這點須要注意。

 

Upgrade Chaincode

  在升級以前,必須在所需的endorsor上安裝新版本的鏈代碼。並且它與實例化同樣,只會在指定的channel內生效,其餘channel不影響。因爲連接代碼的多個版本可能同時處於活動狀態,所以升級過程不會自動刪除舊版本,暫時須要用戶手動管理。所謂手動管理就是手動清理那些舊版本的容器和緩存在節點本地的chaincode包。

  下面貼一段原文:

 

There’s one subtle difference with the instantiate transaction: the upgrade transaction is checked against the current chaincode instantiation policy, not the new policy (if specified). 
This is to ensure that only existing members specified in the current instantiation policy may upgrade the chaincode.

 

   執行更新操做的時候 Upgrade 須要去按照當前的chaincode的實例化策略去檢測,而不是新chaincode指定的實例化策略。 這句話是錯的,由於根據做者的測試結果顯示,並不是如此,而看成者看了源碼後更加確信這句話是錯的:

// executeUpgrade implements the "upgrade" Invoke transaction.
 [executeUpgrade](https://github.com/hyperledger/fabric/blob/9e9ebe651225104823d228a09e94432592252ca3/core/scc/lscc/lscc.go#L540)(...) {
  //cdLedger.InstantiationPolicy 當前chaincode的實例化策略
  err = lscc.support.CheckInstantiationPolicy(signedProp, chainName, cdLedger.InstantiationPolicy)
if err != nil {
return nil, err
}
.... .... //retain chaincode specific data and fill channel specific ones cdfs.Escc = string(escc) cdfs.Vscc = string(vscc) cdfs.Policy = policy **// retrieve and evaluate new instantiation policy**
  //cdfs.InstantiationPolicy 新chaincode 的實例化策略 cdfs.InstantiationPolicy, err = lscc.support.GetInstantiationPolicy(chainName, ccpackfs) if err != nil { return nil, err }
  
// CheckInstantiationPolicy checks whether the supplied signed proposal
// complies with the supplied instantiation policy
err = lscc.support.CheckInstantiationPolicy(signedProp, chainName, cdfs.InstantiationPolicy) if err != nil { return nil, err } …… …… return cdfs, nil }

   這是lscc中管理更新的源碼,咱們能夠看到它清清楚楚的在註釋中寫着「retrieve and evaluate new instantiation policy」, 也就是說它既要檢測已經存在的實例化策略,還要檢查新chaincode 指定的策略, 若是signedproposal 有一個策略不符合就不會成功。

  示例命令:

#安裝新版本的chaincode,安裝規則不變
peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/example02/cmd
#更新智能合約
peer chaincode upgrade -o orderer.example.com:7050 -n mycc -v 1.2 -c '{"Args":["init","a","100","b","200"]}' -P "AND('Org1MSP.peer','Org2MSP.peer')" -C mychannel

   和instantiation Tx同樣, upgrade Tx也是要廣播的。

 

Stop start

  這個它暫時尚未實現,須要本身手動刪除peer上的cds 和 在peer建立的docker容器。

Invoke Transaction

  發起一筆交易到fabric網絡,執行invoke操做的時候須要Endorser節點的背書,在Endorser執行背書請求的時候會檢測client提交的 signed Proposal 是否符合channel的策略(是否符合讀寫策略); 同時在commit Tx到帳本時候,committer 也會去檢測Tx中的背書結果是否符合背書策略,來決定是否修改state,而這個背書策略是在實例化和更新的時候指定的。

  示例命令

peer chaincode invoke -o orderer.example.com:7050 -C mychanel -n mycc -c '{"Args":["invoke","a","b","10"]}' 

  orderer是不會對Tx執行的結果進行檢測的,它只會對提交的Tx是否符合channel 的write policy進行檢測,只有peer在commit的時候會對Tx的進行檢查(是否符合背書策略,正確與否),來決定Tx的結果是否生效。在fabric中即使你提交的交易中包含的背書不符合策略或者`雙花交易`,它仍然會被寫入到帳本中,而fabric會特地提取出一個只包含有效交易的部分。

 

實驗部分請見下一節

相關文章
相關標籤/搜索