本文主要目的是用于整理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会特地提取出一个只包含有效交易的部分。
实验部分请见下一节。