本文主要目的是用於整理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從編寫到部署須要經歷如下幾個步驟:
package(optional)
install
, instantiate
, upgrade
(optional)整個流程和咱們安裝發佈一個web服務很類似,fabric chaincode如今支持 go、java、nodejs多種語言,入門的門檻要比ethereum、eos等公鏈低不少,並且利用fabirc提供的test庫測試起來很方便,做者一般都是用go來開發chaincode,用protobuf做爲數據傳輸協議。
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
:
第一種,一個
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
,若是沒有指定策略,則使用缺省的策略:僅容許
peer
的
admin
身份來實例化
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在你不填加該屬性時,自動爲你添加的權限!它寫的沒問題,可是容易讓人混淆,至於爲何耐心往下看就明白了。
在建立時簽署的鏈代碼包能夠移交給其餘全部者輪流進行檢查和簽名。
ChaincodeDeploymentSpec
能夠選擇由集體全部者簽名,以建立
SignedChaincodeDeploymentSpec
(或
SignedCDS
)。
SignedCDS
包含
3
個元素:
CDS
包含源代碼,鏈碼的名稱和版本
;
鏈碼的實例化策略,語法和背書策略相同
;
經過
Endorserment數組組成
的
chainod
的
owner 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
在
fabric
中
install chaincode
,必須由
peer
對應的
admin Identity
來安裝,這個是不須要設置的,也是沒法更改的, 並且要向每個須要安裝的peer分別發送指令。
install chaincode
能夠指定一個上面的安裝包,也能夠直接指定路徑。不過須要注意的是,直接指定
chaincode
源碼路徑安裝是沒有自定義
instantiate policy
的,它將被賦予一個
default policy
(
在
Package
簽名說的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
在實例化的時候
,會根據所實例化的
chaincode
的
policy
來校驗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會特地提取出一個只包含有效交易的部分。
實驗部分請見下一節。