本文通过对比Fabric 1.4介绍Fabric 2.0 链码操作.
Hyperledger Fabric 2.0最近已经发布,其中最引人关注的一点是 链码操作。官方文档虽然对此提供了详细的说明,但本文将通过与 Hyperledger Fabric前一个版本中链码操作的对比,来帮助你更好的 理解新版本中链码操作的不同之处以及幕后的一些技术细节。
我们首先快速浏览1.4和2.0版中的整个过程。 然后,在每个版本上的演示逐步介绍链码操作。
请注意,这是新发布的2.0代码中的第一篇快速报告。 随着时间的流逝,在完成更详细的探索和测试之后,本文的某些部分将进行更新。 尽管如此,希望本文能对该主题有一个初步的了解,并有助于您的进一步探索。
链码操作是指在 Fabric 网络信道上部署经过开发和测试的链码,以便外部世界可以根据内部的编码逻辑来调用和查询链码功能。 开发并测试了链码后,首先需要 将Fabric链码安装到指定的peer节点。在这个阶段链码还不能使用,直到 链码被提交(Fabric 2.0中的术语)到通道中或在通道上实例化(Fabric 1.4中的 术语),这样链码就可以被授权用户访问了。
下面是两个版本的Hyperledger Fabric中链码操作流程的对比图:
在Hyperledger Fabric 1.4中,链码操作过程包含以下步骤:打包、安装、实例化。 如果链码属于多个参与方,那么就需要打包这一环节。如果不存在多方属主的问题, 那么直接安装链码就可以(其中隐含了打包环节)。
在Fabric链码安装时需要指定 要安装链码的目标节点。我们仅将链码安装到那些目标节点,这些节点将在以后用于调用和查询链码功能。
在这个阶段,安装好的Fabric链码还不能使用,因为它还没有在通道上实例化。 当Fabric链码包被安装在指定的节点上之后,我们就可以执行链码实例化操作, 从而让链码在通道上可用。技术上来说,链码实例化实际上就是调用LSCC系统链码(invokes the lifecycle system chaincode)的方法来初始化通道上的一个链码。
调用此链码时,链码实例化需要通道信息和背书策略。 此外,如果chaincode在Init()中包含初始代码,则此处也会提供参数。
Fabric链码实例化之后就可以使用了,可以接受通道上的调用或查询请求。
以此为基础,我们现在可以看一下新版本2.0。
宽泛地来讲,在Fabric 2.0中链码操作基本遵循同样的流程,但是在命令和某些 后台处理中做了一些调整。整体的流程可以分为四个步骤:打包、安装、机构审批、 链码提交。大致可以认为前两个环节对应于Fabric 1.4中的链码安装,后面两个环节对应于Fabric 1.4中的链码实例化,但是实例化(instantiation)这个词不再用了。
链码打包这一步是创建一个打包文件(tar格式),其中包含Fabric链码以及一些元数据。 虽然不同的机构可以分别进行打包处理,更常见是由一个机构打包然后分发给其他 机构以便确保所有的机构使用相同的链码。
安装步骤是将打包的Fabric链码文件安装在指定的peer节点上。和之前的版本一样, 只有需要接受链码调用的节点才需要安装链码。在这个节点,Fabric链码还不可用, 因为还没有提交到通道中。链码安装的结果是得到一个包标识符,其格式为<label>.<hash>
。
机构审批是在Hyperledger Fabric 2.0中增加的步骤。在之前的版本中我们可以让 一个机构实例化链码。在Fabric 2.0中,需要机构显式地审批链码。需要多少机构 审批则是由生命周期背书策略来决定,默认情况下设置为需要大多数机构(超过半数)。 如果Fabric网络中包含两个机构,那么就需要这两个机构同时批准。在审批过程中需要排序节点的参与,因为每次审批都会生成一个新的区块,这意味着所有的peer 节点都了解审批的状态。
当审批环节完成后,我们就需要指定要在哪个通道上部署链码。这需要提交一些信息, 例如背书策略、是否需要执行Init代码等等。在这里也有些与Fabric 1.4不同的地方: 在Fabric 1.4中,当链码实例化时会自动调用链码的Init方法,然而在Fabric 2.0中, 需要在提交链码后显式地调用Init方法。
在批准机构达到指定数量后,链码就可以提交了。我们现在就进入了最后一个步骤: 链码提交。
链码提交可以由任何机构发起。该流程首先需要批准机构的背书(如果已经审批了代码),然后交易提交到 排序服务并生成新的区块,最后所有的对等节点在账本中提交该区块。
链码生命周期现已完成,链码已准备就绪,可以使用,即可以调用和查询链码功能。
关于Init()的注释:Init() 包含要在调用任何其他函数之前执行的代码,并且只能在开始时执行一次。 在版本1.4中,实例化链码时执行Init()。 在2.0版中,我们需要在提交链码后显式调用Init()。 我们将在批准和链码提交期间指定需要Init()。 这样一来,在调用Init()之前,我们无法调用任何其他函数。
下面会有说明。
出于完整性考虑,下面给出关于First Network和SACC链码的一些信息,这些内容 都来自 fabric-samples仓库。
First Network是一个双机构设置,每个机构中包含两个peer节点。通道mychannel 创建后加入所有的4个peer节点。在byfn.sh中完整的实现了First Network的部署, 并包含一些可选的参数。在下面的演示中,我们不使用默认的链码(在Fabric 1.4 中式chaincode_example02,在Fabric 2.0中是abstore),而是使用SACC链码。
SACC式Simple Asset ChainCode的缩写,表示简单资产链码。它在账本中模拟一个 键/值存储。当初次部署后,需要一个初始的键/值对。SACC链码定义了两个方法: Set()和Get(),分别用来设置或读取某个键的值。
好了,现在我们可以开始演示Fabric 1.4和Fabric 2.0中链码操作的不同了。
我们首先以无链码方式(使用-n选项)启动First Network,然后我们再加载 SACC链码以便聚焦链码的生命周期。
下面是演示步骤:
STEP 1:首先启动First Network:
cd fabric-samples/first-network
./byfn.sh up -n
上面的命令会启动First Network、创建通道mychannel并将所有的peer节点加入该通道。 注意在Fabric 1.4.4中,First Network使用solo排序服务实现,只有一个排序节点。 另外,我们看到有4个对等节点在运行,以及一个CLI用于链码操作。
现在我们可以开始链码部署操作。
STEP 2:在指定peer节点上安装链码
这里我们跳过打包环节,直接在目标节点peer.org1和peer0.org2上安装链码, 因为在这个演示中我们只需要这两个节点进行链码调用和查询。
# peer0.org1
docker exec cli peer chaincode install -n mycc -v 1 -p github.com/chaincode/sacc
# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode install -n mycc -v 1 -p github.com/chaincode/sacc
STEP 3:在通道mychannel上实例化链码并查询
注意在sacc链码中有Init()代码。当我们实例化链码时,我们需要 提供Init()所需的参数, 在这里,我们将key 指定为名称,将值指定为kc作为参数。
docker exec cli peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -v 1 -c '{"Args":["name","kc"]}' -P "AND ('Org1MSP.peer','Org2MSP.peer')"
如果现在看看peer节点的日志,我们可以看到出了新区块#3((这是第3块,因为我们将通道生成块定为#0,并且两个锚点 peer节点 更新交易在区块#1和区块#2中进行)。
在链码实例化之后,我们可以查询:
docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
STEP 4:调用set()设置新值并从另一个节点查询
出于演示目的,我们在peer0.org1上调用set()
,然后在peer0.org2上 调用get()
,以此说明链码是否工作正常。
# peer0.org1
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -C mychannel -n mycc -c '{"Args":["set","name","Peter"]}'
# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
类似的,在Fairc 2.0.0中,我们也先以无链码方式启动First Network, 然后再启动SACC链码以便聚焦链码的生命周期。
下面是演示步骤:
STEP 1:以无链码方式启动First Network:
cd fabric-samples/first-network
./byfn.sh up -n
现在First Network已经启动,通道mychannel已经创建并且所有peer节点 已经加入mychannel通道。注意在Fabric 2.0.0中,First Network使用Raft 作为排序服务,所有5个排序节点都在运行。在这个演示中,我们只使用 orderer.example.com,不过使用其他排序节点也是一样的。
STEP 2:打包SACC链码
首先我们处理依赖问题(如果之前没有处理的话):
cd fabric-sample/chaincode/sacc
GO111MODULE=on go mod vendor
cd fabric-sample/first-network
然后打包链码:
docker exec cli peer lifecycle chaincode package sacc.tar.gz --path github.com/hyperledger/fabric-samples/chaincode/sacc/ --label sacc_1
在CLI容器内可以看到生成了新的文件sacc.tar.gz:
STEP 3:在指定peer节点上安装SACC链码包
现在我们在peer0.org1和peer0.org2上安装SACC 链码包,因为在这个演示中我们只需要使用这两个节点进行链码调用和查询。
# peer0.org1
docker exec cli peer lifecycle chaincode install sacc.tar.gz
# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode install sacc.tar.gz
我们会收到链码包的标识符,在下面的链码审批阶段会用到。我们 收到的链码标识符为:sacc_1:bf57…6399
。
我们可以使用peer lifecycle chaincode queryinstalled
命令随时检查节点上的链码安装情况,如果我们需要找出链码包的标识ID,这个命令会很有用。
# peer0.org1
docker exec cli peer lifecycle chaincode queryinstalled
# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode queryinstalled
STEP 4:审批链码
根据默认策略,需要超过半数的机构审批链码后才能向通道提交链码, 具体可参考configtx.yaml中的Application/Policies/LifecycleEndorsement
部分。 目前的设置中包含两个机构,因此需要两个机构同时批准链码。
首先是Org1批准链码:
docker exec cli peer lifecycle chaincode approveformyorg --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --name mycc --version 1 --init-required --sequence 1 --waitForEvent --package-id ${PACKAGE_ID}
如果我们现在看下peer节点的日志,可以看到出了新块#3(这是第3块,因为我们将通道生成块定为#0,并且两个锚点 peer节点更新交易在区块#1和区块#2中进行)。
类似的,我们让Org2批准链码:
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode approveformyorg --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --name mycc --version 1 --init-required --sequence 1 --waitForEvent --package-id ${PACKAGE_ID}
不出意外,可以看到出了新块block#4:
注意,我们在批准命令中指定了init-required选项。 以便向SACC链码的 Init方法传入所需的参数,在将链码提交到通道后,我们将调用Init()的。
可以随时使用如下命令查看链码的提交状态(在任何peer节点上都收到这些新块的更新):
docker exec cli peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1 --sequence 1 --output json
两个机构都已经批准了链码,现在可以提交了。
STEP 5:向通道mychannel提交链码
链码提交可以在一个peer节点上完成:
docker exec cli peer lifecycle chaincode commit -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --channelID mychannel --name mycc --version 1 --sequence 1 --init-required
可以看到出了新块#5:
注意我们在commit命令中已经包含了 init-required 参数。
同样,我们可以使用 peer lifecycle chaincode querycommitted
命令来查看链码的提交状态:
docker exec cli peer lifecycle chaincode querycommitted --channelID mychannel --name mycc
在链码提交到通道之后,链码的生命周期就完成了,链码已经可以访问。 现在我们回到链码的调用和查询,这和之前的版本是一致的。
STEP 6:调用链码的Init方法(用相应的参数)
根据SACC链代码设计,代码是Init()。 在调用其他链码函数之前,我们将首先使用所需的参数调用Init()。 在这里,我们将键指定为name,将值指定为kc。 注意,在命令中,我们有一个isInit选项,表明它正在调用Init()。
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -C mychannel -n mycc --isInit -c '{"Args":["name","kc"]}'
现在可以查询 链码:
docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
STEP 7:调用链码的set()方法并从另一个peer节点查询
和之前一样,我们在peer0.org1上调用链码的set()
方法,在peer0.org2上 进行查询:
# peer0.org1
docker exec cli peer chaincode invoke \
-o orderer.example.com:7050 \
--tls \
--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
--peerAddresses peer0.org1.example.com:7051 \
--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
--peerAddresses peer0.org2.example.com:9051 \
--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
-C mychannel -n mycc -c '{"Args":["set","name","Peter"]}'
# peer0.org2
docker exec \
-e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp \
-e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 \
-e CORE_PEER_LOCALMSPID="Org2MSP" \
-e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
一切正常。
这是通过演示第一次尝试了解2.0版中的链码操作与先前版本的区别。 如前所述,仍然有很多东西需要深入研究。 希望这可以作为我们在2.0版代码中启航的起点。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!