链上代码 (chaincode),简称链码 ,一般是指用户编写的应用代码。
链码被部署在Fabric网络节点上,运行在隔离沙盒(目前为Docker容器)中,并通过gRPC协议与相应的Peer节点进行交互,以操作分布式账本中的数据。
启动Fabric网络后,可以通过命令行或SDK进行链码操作,验证网络运行是否正常。
注意 用户链码有别于系统链码(System Chaincode)。系统链码指的是Fabric Peer中负责系统配置、背书、验证等平台功能的逻辑,运行在Peer进程内,将在后续章节予以介绍。
9.5.1 链码操作命令
用户可以通过命令行方式操作链码,支持的链码子命令包括install、instantiate、invoke、 query、upgrade、package、signpackage等,未来还会支持start、stop命令。大部分命令(除了package、 signpackage外)的处理过程都是类似的,创建签名提案消息,发给Peer进行背书,获取ProposalResponse消息。
特别是,instantiate、upgrade、invoke等子命令还需要根据ProposalResponse消 息创建SignedTX,发送给Orderer进行排序和广播全网执行。package、signpackage子命令作为本地操作,无需与Peer或 Orderer打交道。
这些操作管理了链码的整个生命周期,如图9-3所示。
图9-3 链码生命周期
后面将以Fabric项目中自带的Go语言example02链码(路径在examples/chaincode/go/chaincode_example02)为例进行相关命令讲解。
9.5.2 命令参数
链码操作支持的命令参数及对应的功能如表9-5所示。
表9-5 链码操作支持的命令参数
注意,不同子命令支持不同的参数,总结如表9-6所示。
表9-6 子命令所支持的不同参数
其中,必需、支持和不支持三种情况的含义为:
·必需:该参数必须被指定,包括通过命令行、环境变量、配置等;
·支持:该参数可以被使用。某些时候如果不指定,可能采取默认值或自动获取;
·不支持:该参数不应该使用。
9.5.3 安装链码
install命令将链码的源码和环境等内容封装为一个链码安装打包文件(Chaincode Install Package,CIP),并传输到背书节点。背书节点解析后一般会保存在$CORE_PEER_FILESYSTEMPATH/chaincodes /目录下。安装链码只需要与Peer打交道。
打包文件以name.version命名,主要包括如下内容:
·ChaincodeDeploymentSpec:链码的源码和一些关联环境,如名称和版本;
·链码实例化策略,默认是任意通道上的MSP管理员身份均可;
·拥有这个链码的实体的证书和签名;
·安装时,本地MSP管理员的签名。
ChaincodeDeploymentSpec(CDS)结构包括了最核心的ChaincodeSpec(CS)数据结构,同时也被其他链码命令(如实例化命令和升级命令)使用,如图9-4所示。
图9-4 ChaincodeDeploymentSpec结构
例如,采用如下命令会部署test_cc.1.0的打包部署文件到背书节点:
$ peer chaincode install \
-n test_cc \
-v 1.0 \
-p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02
链码安装实现的整体流程如图9-5所示。
图9-5 链码安装过程
主要步骤包括:
1)首先是构造签名提案消息(SignedProposal)。
a)调用InitCmdFactory(isEndorserRequired,isOrdererRequired bool)(*ChaincodeCmdFactory,error)方法,初始化EndoserClient、Signer等结构。这一步初始化操作对 于所有链码子命令来说都是类似的,会初始化不同的结构。
b)然后根据命令行参数进行解析,判断是根据传入的打包文件直接读取ChaincodeDeploymentSpec(CDS)结构,还是根据传入参数从本地链码文件来重新构造。
c)以本地重新构造情况为例,首先根据命令行中传入的路径、名称等信息,构造生成ChaincodeSpec(CS)结构。
d)利用ChaincodeSpec结构,结合链码包数据生成一个ChaincodeDeploymentSpec结构 (chainID为空),调用本地的install(msg proto.Message,cf*ChaincodeCmdFactory)error方法。
e)install方法基于传入的ChaincodeDeploymentSpec结构,构造一个对生命周期管理系统链 码(LSCC)调用的ChaincodeSpec结构,其中,Type为ChaincodeSpec_GOLANG,ChaincodeId.Name为 “lscc”,Input为“install”+ChaincodeDeploymentSpec。进一步地,构造了一个LSCC的 ChaincodeInvocationSpec(CIS)结构,对ChaincodeSpec结构进行封装。
f)基于LSCC的ChaincodeInvocationSpec结构,添加头部结构,生成一个提案(Proposal)结构。其中,通道头部中类型为ENDORSER_TRANSACTION,TxID为对随机数+签名实体,进行Hash。
g)对Proposal进行签名,转化为一个签名后的提案消息SignedProposal。
2)通过EndorserClient经由gRPC通道发送给Peer的ProcessProposal(ctx context.Context,in*SignedProposal,opts...grpc.CallOption) (*ProposalResponse,error)接口。
3)Peer模拟运行生命周期链码的调用交易进行处理,检查格式、签名和权限等,通过则保存到本地文件系统。
图9-6给出了链码安装过程中所涉及的数据结构,这些数据结构对于大部分链码操作命令都是类似的,其中最重要的是ChannelHeader结构和ChaincodeSpec结构中参数的差异。
图9-6 链码安装过程所涉及的数据结构
9.5.4 实例化链码
instantiate命令通过构造生命周期管理系统链码(Lifecycle System Chaincode,LSCC)的交易,将安装过的链码在指定通道上进行实例化调用,在节点上创建容器启动,并执行初始化操作。实例化链码需要同时跟 Peer和Orderer打交道。
执行instantiate命令的用户身份必须满足实例化的策略,并且在所指定的通道上拥有写(Write)权限。在 instantiate命令中可以通过“-P”参数指定链码的背书策略(Endorsement Policy),不满足背书策略的链码调用将在Commit阶段被作废。
例如,如下命令会启动test_cc.1.0链码,会将参数'{"Args":["init","a","100","b","200"]}'传入链码中的Init()方法执行。命令会生成一笔交易,因此需指定排序节点地址:
$ CHANNEL_NAME="businesschannel"
$ peer chaincode instantiate \
-o orderer0:7050 \
-C businesschannel \
-n test_cc \
-v 1.0 \
-C ${CHANNEL_NAME} \
-c '{"Args":["init","a","100","b","200"]}' \
-P "OR ('Org1MSP.member','Org2MSP.member')"
链码实例化实现的整体流程如图9-7所示。
图9-7 链码实例化过程
主要步骤包括:
1)首先,类似链码安装命令,需要创建一个SignedProposal消息。注意instantiate和 upgrade支持policy、escc、vscc等参数。LSCC的ChaincodeSpec结构中,Input中包括类型(“deploy”)、 通道ID、ChaincodeDeploymentSpec结构、背书策略、escc和vscc等。
2)调用EndorserClient,发送gRPC消息,将签名后的Proposal发给指定的Peer节点 (Endorser),调用ProcessProposal(ctx context.Context,in*SignedProposal,opts...grpc.CallOption) (*ProposalResponse,error)方法,进行背书处理。节点会模拟运行LSCC的调用交易,启动链码容器。实例化成功后会返回 ProposalResponse消息(其中包括背书签名)。
3)根据Peer返回的ProposalResponse消息,创建一个SignedTX(Envelop结构的交易,带有签名)。
4)使用BroadcastClient将交易消息通过gRPC通道发给Orderer,Orderer会进行全网排序,并广播给Peer进行确认提交。
其中,SignedProposal结构如图9-8所示。
图9-8 Signed Proposal结构
交易Envelope结构如图9-9所示。
图9-9 交易Envelope结构
Peer返回的ProposalResponse消息定义如下:
type ProposalResponse struct {
// 消息协议版本
Version int32 `protobuf:"varint,1,opt,name=version" json:"version,
omitempty"`
// 消息创建时的时间戳
Timestamp *google_protobuf1.Timestamp `protobuf:"bytes,2,opt,name=tim
estamp" json:"timestamp,omitempty"`
// 返回消息,包括状态、消息、元数据载荷等
Response *Response `protobuf:"bytes,4,opt,name=response" json:
"response,omitempty"`
// 数据载荷,包括提案的 Hash 值,和扩展的行动等
Payload []byte `protobuf:"bytes,5,opt,name=payload,proto3" json:
"payload,omitempty"`
// 背书信息列表,包括背书者的证书,以及其对“载荷+背书者证书”的签名
Endorsement *Endorsement `protobuf:"bytes,6,opt,name=endorsement" json:
"endorsement,omitempty"`
}
9.5.5 调用链码
通过invoke命令可以调用运行中的链码的方法。“-c”参数指定的函数名和参数会被传入到链码的Invoke()方法进行处理。调用链码操作需要同时跟Peer和Orderer打交道。
例如,对部署成功的链码执行调用操作,由a向b转账10元。在peer0容器中执行如下操作,注意验证最终结果状态正常response:
$ peer chaincode invoke \
-o orderer0:7050 \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["invoke","a","b","10"]}'
这一命令会调用最新版本的test_cc链码,将参数'{"Args":["invoke","a","b","10"]}'传入链码中的Invoke()方法执行。命令会生成一笔交易,需指定排序者地址。
需要注意,invoke命令不支持指定链码版本,只能调用最新版本的链码。
实现上,基本过程如图9-10所示。
图9-10 链码调用过程
主要步骤包括:
1)首先,也是要创建一个SignedProposal消息。根据传入的各种参数,生成ChaincodeSpec结构 (其中,Input为传入的调用参数)。然后,根据ChaincodeSpec、chainID、签名实体等,生成 ChaincodeInvocationSpec结构。进而封装生成Proposal结构(通道头部中类型为 ENDORSER_TRANSACTION),并进行签名。
2)调用EndorserClient,发送gRPC消息,将签名后的Proposal发给指定的Peer节点 (Endorser),调用ProcessProposal(ctx context.Context,in*SignedProposal,opts...grpc.CallOption) (*ProposalResponse,error)方法,进行背书处理。节点会模拟运行链码调用交易,成功后会返回ProposalResponse消 息(带有背书签名)。
3)根据Peer返回的ProposalResponse消息,创建一个SignedTX(Envelop结构的交易,带有签名)。
4)使用BroadcastClient将交易消息通过gRPC通道发给Orderer进行全网排序并广播给Peer进行确认提交。
注意,invoke是异步操作,invoke成功只能保证交易已经进入Orderer进行排序,但无法保证最终写到账本中(例如交易未通过Committer验证而被拒绝)。需要通过eventHub或查询方式来进行确认交易是否最终写入到账本上。
链码调用过程中所涉及的数据结构如图9-11所示。
图9-11 链码调用过程中所涉及的数据结构
注意 目前命令行下的instantiate命令还不支持指定实例化策略,Peer会采用默认的实例化策略(组织管理员身份)。
9.5.6 查询链码
查询链码可以通过query命令进行。query命令的执行过程与invoke命令类似,实际上同样是将-c指定的命令 参数发送给链码中的Invoke()方法执行。与invoke操作的区别在于,query操作只能查询Peer上账本状态,不生成交易,也不需要与 Orderer打交道。
例如,执行如下命令会调用最新版本的test_cc链码,将参数'{"Args":["query","a"]}'传入链码中的Invoke()方法执行,并返回查询结果:
$ peer chaincode query \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["query","a"]}'
在实例化链码容器后,可以在peer0容器中执行如下命令,注意输出无错误信息,最后的结果为初始值Query Result:100:
$ peer chaincode query \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["query","a"]}'
Query Result: 100
[main] main -> INFO 001 Exiting.....
类似地,查询b的余额,注意最终返回结果为初始值Query Result:200:
$ peer chaincode query \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["query","b"]}'
Query Result: 200
[main] main -> INFO 001 Exiting.....
在执行完a向b转账10的交易后,再次查询a和b的余额,发现发生了变化。
a的新余额为90:
$ peer chaincode query \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["query","a"]}'
Query Result: 90
[main] main -> INFO 001 Exiting.....
b的新余额为210:
$ peer chaincode query \
-n test_cc \
-C ${CHANNEL_NAME} \
-c '{"Args":["query","b"]}'
Query Result: 210
[main] main -> INFO 001 Exiting.....
query的实现过程实际上就是invoke命令跟Peer打交道的部分,因此某些时候可以用invoke命令来替代query命令。主要过程如下。
1)根据传入的各种参数,最终构造签名提案,通过endorserClient发送给指定的Peer;
2)成功的话,获取到ProposalResponse,打印出proposalResp.Response.Payload内容。
需要注意invoke和query的区别,query不需要创建SignedTx发送到Orderer,而且会返回查询结果。
9.5.7 升级链码
当需要修复链码漏洞或进行功能拓展时,可以对链码进行升级,部署新版本的链码。Fabric支持在保留现有状态的前提下对链码进行升级。
假设某通道上正在运行中的链码为test_cc,版本为1.0,可以通过如下步骤进行升级操作。
首先,安装新版本的链码,打包到Peer节点:
$ peer chaincode install \
-n test_cc \
-v 1.1 \
-p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02_
new
运行以下upgrade命令升级指定通道上的链码,需要指定相同的链码名称test_cc:
$ peer chaincode upgrade \
-n test_cc \
-C ${CHANNEL_NAME} \
-v 1.1 \
-c '{"Args":["re-init","c","60"]}' -o orderer0:7050
这一命令会在通道test_cc上实例化新版本链码test_cc.1.1并启动一个新容器。运行在其他通道上的旧版本链码将不受影响。升级操作跟实例化操作十分类似,唯一区别在于不改变实例化的策略。这就保证了只有拥有实例化权限的用户才能进行升级操作。
升级过程会将给定的参数(如例子中的'{"Args":["re-init","c","60"]}')传入新链码的 Init()方法中执行。只要Init()方法中对应的逻辑不改写状态,则升级前后链码的所有状态值可以保持不变。因此,如果链码将来要考虑在保留状态情 况下升级,需要在编写Init()方法时妥善处理升级时的逻辑。
升级操作实现的主要过程如下,十分类似实例化命令:
1)首先,需要创建一个封装了LSCC调用交易的SignedProposal消息。注意instantiate和 upgrade支持policy、escc、vscc等参数。LSCC的ChaincodeSpec结构中,Input中包括类型 (“upgrade”)、通道ID、ChaincodeDeploymentSpec结构、背书策略、escc和vscc等。
2)调用EndorserClient,发送gRPC消息,将签名后的Proposal发给指定的Peer节点 (Endorser),调用ProcessProposal(ctx context.Context,in*SignedProposal,opts...grpc.CallOption) (*ProposalResponse,error)方法,进行背书处理。节点会模拟运行LSCC的调用交易,启动链码容器。实例化成功后会返回 ProposalResponse消息(其中包括背书签名)。
3)根据Peer返回的ProposalResponse消息,创建一个SignedTX(Envelop结构的交易,带有签名)。
4)使用BroadcastClient将交易消息通过gRPC通道发给Orderer,Orderer会进行全网排序,并广播给Peer进行确认提交。
9.5.8 打包链码和签名
通过将链码相关的数据进行封装,可以实现对其进行打包和签名操作。
打包命令支持三个特定参数:
·-s,--cc-package:表示创建完整打包格式,而不是仅打包ChaincodeDeploymentSpec结构;
·-S,--sign:对打包的文件使用本地的MSP(core.yaml中的localMspid指定)进行签名;
·-i--instantiate-policy string:指定实例化策略,可选参数。
例如,通过如下命令创建一个本地的打包文件ccpack.out:
$ peer chaincode package \
-n test_cc -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_
example02 \
-v 1.0 \
-s \
-S \
-i "AND('Org1.admin')" \
ccpack.out
打包后的文件,也可以直接用于install操作,如:
$ peer chaincode install ccpack.out
签名命令则对一个打包文件进行签名操作(添加当前MSP签名到签名列表中)。
$ peer chaincode signpackage ccpack.out signedccpack.out
其中,打包文件结构主要包括以下三部分信息:
·ChaincodeDeploymentSpec结构;
·实例化策略信息;
·拥有者的签名列表。
实现的整体流程如下:
1)首先会调用 InitCmdFactory(isEndorserRequired,isOrdererRequired bool)(*ChaincodeCmdFactory,error)方法初始化Signer等结构。对于打包命令来说纯属本地操作,不需要 Endorser和Orderer的连接。
2)调用getChaincodeSpec()方法,解析命令行参数,根据所指定的数据生成ChaincodeSpec结构。
3)根据ChaincodeSpec结构,结合链码相关数据构造ChaincodeDeploymentSpec结构,并传入getChaincodeInstallPackage方法。
4)getChaincodeInstallPackage方法基于传入的 ChaincodeDeploymentSpec结构,添加实例化策略和签名信息等,生成一个 SignedChaincodeDeploymentSpec,并进一步作为Data生成一个Envelope结构,其中ChannelHeader指定 为CHAINCODE_PACKAGE。
5)将Envelope结构序列化,写到指定的本地文件。
其中,Envelope结构如图9-12所示。
图9-12 打包过程中的Envelope结构
来源:我是码农,转载请保留出处和链接!
本文链接:http://www.54manong.com/?id=927