在Hyperledger Fabric 2.0中引入的一个新特性,就是可以使用外部的链码启动器,这种外部启动链码的方式非常适合使用kubenetes或dowcker swarm来统一管理节点容器和链码容器。在这片文章中,我们将学习如何使用外部链码启动器在K8s集群中部署链码。
1、Fabric外部链码实验的前提条件
Hyperledger Fabric区块链开发教程:
Fabric Node.js开发详解 |
Fabric Java开发详解 |
Fabric Golang开发详解
下面是我们实验的一些前提条件
- 一个kubenetes集群,你可以使用minikube或一个单节点的kubeadmin。在 本文中我们使用kubeadmin
- hyperledger fabric 2.0.1 docker镜像
- hyperledger fabric 2.0.1 预编译程序。我们需要其中的工具来创建 密码学资料和通道交易配置文件。可以从这里下载。
- 从这里下载本文的代码
2、安装预编译程序
使用以下命令安装预编译程序:
wget https://github.com/hyperledger/fabric/releases/download/v2.0.1/hyperledger-fabric-linux-amd64-2.0.1.tar.gz
tar -xzf hyperledger-fabric-linux-amd64-2.0.1.tar.gz# Move to the bin path
mv bin/* /bin# Check that you have successfully installed the tools by executing
configtxgen --version# Should print the following output:
# configtxgen:
# Version: 2.0.1
# Commit SHA: 1cfa5da98
# Go version: go1.13.4
# OS/Arch: linux/amd64
3、启动Hyperledger Fabric网络
一旦我们启动了kubernetes集群,就可以启动Fabric网络了。但是我们先要生成基本的密码学资料以及网络创世区块。在configtx.yaml中有一些修改以适应fabric 2.0中新的lifecycle chaincode命令,这些修改对于使用外部链码启动器是必须的。
Fabric区块链网络中包含3个RAFT排序服务节点,2个机构org1和org2各含1个peer节点以及一个CA。这些信息已经编码在configtx.yaml和crypto-config.yaml文件中,不需要修改。
有了配置文件,现在我们可以生成fabric区块链网络的密码学资料和创世区块。我们使用fabricOps.sh脚本:
$ ./fabricOps.sh start
上面的命令将生成所有的密码学资料,例如节点和CA的证书等,以及通道的创世区块。
现在需要为Hyperledger Fabric工作负载创建一个新的k8s命名空间,命令如下:
$ kubectl create ns hyperledger
然后创建一个文件夹来保存Hyperledger Fabric工作负载的持久化数据:
$ mkdir /home/storage
好了,现在可以开始部署Fabric排序节点了:
$ kubectl create -f orderer-service/
使用下面命令检查Fabric网络是否启动正常:
$ kubectl get pods -n hyperledger
### Should print a similar output
NAME READY STATUS RESTARTS AGE
orderer0-58666b6bd7-pflf7 1/1 Running 0 5m47s
orderer1-c4fd65c7d-c27ll 1/1 Running 0 5m47s
orderer2-557cb7865-wlcmh 1/1 Running 0 5m47s
接下来创建org1的工作负载,这主要包含部署机构的CA和peer节点:
$ kubectl create -f org1/
同样用下面命令检查Fabric网络节点是否启动:
$ kubectl get pods -n hyperledger
### Should print a similar output
NAME READY STATUS RESTARTS AGE
ca-org1-84945b8c7b-9px4s 1/1 Running 0 19m
cli-org1-bc9f895f6-zmmdc 1/1 Running 0 2m56s
orderer0-58666b6bd7-pflf7 1/1 Running 0 79m
orderer1-c4fd65c7d-c27ll 1/1 Running 0 79m
orderer2-557cb7865-wlcmh 1/1 Running 0 79m
peer0-org1-798b974467-vv4zz 1/1 Running 0 19m
重复上面的步骤来加载org2的工作负载:
$ kubectl create -f org2/
检查Fabric网络情况:
$ kubectl get pods -n hyperledger
### Should print a similar output
NAME READY STATUS RESTARTS AGE
ca-org1-84945b8c7b-9px4s 1/1 Running 0 71m
ca-org2-7454f69c48-q8lft 1/1 Running 0 2m20s
cli-org1-bc9f895f6-zmmdc 1/1 Running 0 55m
cli-org2-7779cc8788-8q4ns 1/1 Running 0 2m20s
orderer0-58666b6bd7-pflf7 1/1 Running 0 131m
orderer1-c4fd65c7d-c27ll 1/1 Running 0 131m
orderer2-557cb7865-wlcmh 1/1 Running 0 131m
peer0-org1-798b974467-vv4zz 1/1 Running 0 71m
peer0-org2-5849c55fcd-mbn5h 1/1 Running 0 2m19s
3、设置Fabric网络的通道
一旦部署完Fabric网络的工作负载,我们可以开始创建通道并将peer节点加入通道。进入org1的cli:
$ kubectl exec -it cli_org1_pod_name sh -n hyperledger
在这个pod内执行如下命令:
$ peer channel create -o orderer0:7050 -c mychannel -f ./scripts/channel-artifacts/channel.tx --tls true --cafile $ORDERER_CA
### Should print a similar output
2020-03-06 11:54:57.582 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-03-06 11:54:58.903 UTC [cli.common] readBlock -> INFO 002 Received block: 0
现在mychannel通道已经创建好了,接下来将org1的peer节点加入通道:
$ peer channel join -b mychannel.block
### Should print a similar output
2020-03-06 12:01:41.608 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-03-06 12:01:41.688 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
同样的操作将org2的peer节点加入通道。不过由于org1已经创建了通道,因此我们需要从排序服务提取通道创世块。首先进入pod:
$ kubectl exec -it cli_org2_pod_name sh -n hyperledger
然后在pod内执行如下操作:
$ peer channel fetch 0 mychannel.block -c mychannel -o orderer0:7050 --tls --cafile $ORDERER_CA
### Should print a similar output
2020-03-06 12:18:14.880 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-03-06 12:18:14.895 UTC [cli.common] readBlock -> INFO 002 Received block: 0
然后加入通道:
$ peer channel join -b mychannel.block
### Should print a similar output
2020-03-06 12:20:41.475 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2020-03-06 12:20:41.561 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
用下面的命令检查当前节点是否已经加入了通道:
$ peer channel list
### Should print a similar output
2020-03-06 12:22:41.102 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
4、在Fabric节点安装外部链码
现在开始我们进入真正有趣的环节。我们将部署marbles链码作为外部链码。你可以在Fabric-samples
中找到原始的链码,但是为了将其作为外部链码部署,我们需要在imports和init函数中进行一些修改,
以将其声明为外部链码服务器。
新版本的Hyperledger Fabric同样包含了新的链码处理流程来支持外部方式安装和启动链码。为了使用外部链码特性,我们也需要使用这一新的流程。
第一件要做的事情就是将peer节点配置为可以处理外部链码。外部链码构建器就是基于buildpack
实现,因此我们创建3个脚本:detect、build和release。这三个脚本必须在peer容器内定义。你可以
在buildpack/bin目录下找到这些脚本。
链码构建器定义在org1目录下的builders-config.yaml中,其中的自定义构建器配置:
# List of directories to treat as external builders and launchers for
# chaincode. The external builder detection processing will iterate over the
# builders in the order specified below.
externalBuilders:
- name: external-builder
path: /builders/external
environmentWhitelist:
- GOPROXY
kubernetes使用环境变量来配置peer节点,这些环境变量将覆盖core.yaml中的默认值。
Fabric 2.0采用了与之前版本不同的方式来打包和安装链码。由于我们要使用外部链码特性,因此链码代码不需要在peer的pod内进行编译和安装,而是在另一个pod中进行。在peer进程中唯一需要的就是用来连接外部链码进程的信息。
为此,我们需要打包链码的一些准备。需要一个connection.json文件,其中包含连接外部链码服务器的信息,例如地址、TLS证书、连接超时配置等。
{
"address": "chaincode-marbles-org1.hyperledger:7052",
"dial_timeout": "10s",
"tls_required": false,
"client_auth_required": false,
"client_key": "-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----",
"client_cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----",
"root_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----"
}
这个文件需要打包进一个tar文件,code.tar.gz,使用如下命令进行压缩打包:
$ cd chaincode/packaging
$ tar cfz code.tar.gz connection.json
一旦有了code.tar.gz文件,我们需要将其与另一个文件metadata.json一起重新打包。在metadata.json中包含了链码类型、路径、标签等信息:
{"path":"","type":"external","label":"marbles"}
使用下面的命令打包:
$ tar cfz marbles-org1.tgz code.tar.gz metadata.json
一旦有了上面的打包文件,我们就可以使用新的lifecycle chaincode install命令来安装链码了:
$ peer lifecycle chaincode install marbles-org1.tgz
### Should print a similar output
2020-03-07 14:33:18.120 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nGdmarbles:e001937433673b11673d660d142c722fc372905db87f88d2448eee42c9c63064\022\006degree" >
2020-03-07 14:33:18.126 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: marbles:e001937433673b11673d660d142c722fc372905db87f88d2448eee42c9c63064
记下来上面的链码包标识符,我们接下来会用到,不过你也可以随时用下面的命令查询链码包的标识符:
$ peer lifecycle chaincode queryinstalled
### Should print a similar output
Installed chaincodes on peer:
Package ID: marbles:030eec59c7d74fbb4e9fd57bbd50bb904a715ffb9de8fea85b6a6d4b8ca9ea12, Label: marbles
现在我们为org2重复上面的步骤,但是由于我们希望用另一个pod为org2的peer节点提供链码服务,
因此我们需要修改connection.json中的地址配置:
"address": "chaincode-marbles-org2.hyperledger:7052",
然后重复之前的步骤,在org2的cli pod中执行如下命令:
$ rm -f code.tar.gz
$ tar cfz code.tar.gz connection.json
$ tar cfz marbles-org2.tgz code.tar.gz metadata.json
$ peer lifecycle chaincode install marbles-org2.tgz
### Should print a similar output
2020-03-07 15:10:15.093 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nGmarbles:c422c797444e4ee25a92a8eaf97765288a8d68f9c29cedf1e0cd82e4aa2c8a5b\022\006degree" >
2020-03-07 15:10:15.093 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: marbles:c422c797444e4ee25a92a8eaf97765288a8d68f9c29cedf1e0cd82e4aa2c8a5b
同样记录链码包的标识符,它应该与之前org1的不同。
当我们安装链码时在peer内部发生了什么?如果定义了外部构建器或启动器,那么就不会执行内置的链码构建过程。由于我们已经定义了外部构建器,那么将按下面的顺序执行外部构建脚本:
detect
脚本检测要安装的链码是否在metadata.json中配置为external类型。如果脚本失败,那么peer将认为这个外部构建器不需要构建链码并尝试其他外部构建器,直到最终没有可用的外部构建器时,使用内置的docker构建流程。
#!/bin/sh
# The bin/detect script is responsible for determining whether or not a buildpack
# should be used to build a chaincode package and launch it.
#
# The peer invokes detect with two arguments:
# bin/detect CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR
#
# When detect is invoked, CHAINCODE_SOURCE_DIR contains the chaincode source and
# CHAINCODE_METADATA_DIR contains the metadata.json file from the chaincode package installed to the peer.
# The CHAINCODE_SOURCE_DIR and CHAINCODE_METADATA_DIR should be treated as read only inputs.
# If the buildpack should be applied to the chaincode source package, detect must return an exit code of 0;
# any other exit code will indicate that the buildpack should not be applied.
CHAINCODE_METADATA_DIR="$2"
set -euo pipefail
# use jq to extract the chaincode type from metadata.json and exit with
# success if the chaincode type is golang
if [ "$(cat "$CHAINCODE_METADATA_DIR/metadata.json" | sed -e 's/[{}]/''/g' | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'type'\042/){print $(i+1)}}}' | tr -d '"')" = "external" ]; then
exit 0
fi
exit 1
如果detect
脚本成功,那么就调用build
脚本。如果在peer内部构建链码,那么这个脚本应当生成二进制程序。但是由于我们希望作为外部服务,那么只需要简单的拷贝connection.json文件到build的输出目录就可以了。
#!/bin/sh
# The bin/build script is responsible for building, compiling, or transforming the contents
# of a chaincode package into artifacts that can be used by release and run.
#
# The peer invokes build with three arguments:
# bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR
#
# When build is invoked, CHAINCODE_SOURCE_DIR contains the chaincode source and
# CHAINCODE_METADATA_DIR contains the metadata.json file from the chaincode package installed to the peer.
# BUILD_OUTPUT_DIR is the directory where build must place artifacts needed by release and run.
# The build script should treat the input directories CHAINCODE_SOURCE_DIR and
# CHAINCODE_METADATA_DIR as read only, but the BUILD_OUTPUT_DIR is writeable.
CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"
set -euo pipefail
#external chaincodes expect connection.json file in the chaincode package
if [ ! -f "$CHAINCODE_SOURCE_DIR/connection.json" ]; then
>&2 echo "$CHAINCODE_SOURCE_DIR/connection.json not found"
exit 1
fi
#simply copy the endpoint information to specified output location
cp $CHAINCODE_SOURCE_DIR/connection.json $BUILD_OUTPUT_DIR/connection.json
if [ -d "$CHAINCODE_SOURCE_DIR/metadata" ]; then
cp -a $CHAINCODE_SOURCE_DIR/metadata $BUILD_OUTPUT_DIR/metadata
fi
exit 0
最后,一旦build
脚本执行完毕,就会调用release
脚本。这个脚本负责提供connection.json文件给peer节点,它只需要将该文件放到release输出目录就可以了。因此,peer现在知道如何调用链码了。
#!/bin/sh
# The bin/release script is responsible for providing chaincode metadata to the peer.
# bin/release is optional. If it is not provided, this step is skipped.
#
# The peer invokes release with two arguments:
# bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR
#
# When release is invoked, BUILD_OUTPUT_DIR contains the artifacts
# populated by the build program and should be treated as read only input.
# RELEASE_OUTPUT_DIR is the directory where release must place artifacts to be consumed by the peer.
set -euo pipefail
BUILD_OUTPUT_DIR="$1"
RELEASE_OUTPUT_DIR="$2"
# copy indexes from metadata/* to the output directory
# if [ -d "$BUILD_OUTPUT_DIR/metadata" ] ; then
# cp -a "$BUILD_OUTPUT_DIR/metadata/"* "$RELEASE_OUTPUT_DIR/"
# fi
#external chaincodes expect artifacts to be placed under "$RELEASE_OUTPUT_DIR"/chaincode/server
if [ -f $BUILD_OUTPUT_DIR/connection.json ]; then
mkdir -p "$RELEASE_OUTPUT_DIR"/chaincode/server
cp $BUILD_OUTPUT_DIR/connection.json "$RELEASE_OUTPUT_DIR"/chaincode/server
#if tls_required is true, copy TLS files (using above example, the fully qualified path for these fils would be "$RELEASE_OUTPUT_DIR"/chaincode/server/tls)
exit 0
fi
exit 1
5、Fabric外部链码的构建与部署
一旦我们在peer节点上安装了链码,就可以构建并在我们的kubernetes集群上部署这些链码了。让我们看一下必要的修改。
首先需要import一些必要的模块:
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"os"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
)
需要指出的一点是,在构建代码之前需要定义go.mod文件。最新版本的shim模块中包含了对外部链码特性的支持 —— 目前只有go 链码开发包支持外部链码。
module github.com/marbles
go 1.12
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200128192331-2d899240a7ed
github.com/hyperledger/fabric-protos-go v0.0.0-20200124220212-e9cfc186ba7b
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect
golang.org/x/text v0.3.2 // indirect
google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 // indirect
)
其他的变化包括修改链码服务器的监听地址、端口等。因为我们希望使用kubernetes的yaml描述文件来修改这些配置,因此我们使用os模块来获取这些值:
func main() {
server := &shim.ChaincodeServer{
CCID: os.Getenv("CHAINCODE_CCID"),
Address: os.Getenv("CHAINCODE_ADDRESS"),
CC: new(SimpleChaincode),
TLSProps: shim.TLSProperties{
Disabled: true,
},
}
// Start the chaincode external server
err := server.Start()
if err != nil {
fmt.Printf("Error starting Marbles02 chaincode: %s", err)
}
}
现在可以用下面的Dockerfile来构建链码镜像:
# This image is a microservice in golang for the Degree chaincode
FROM golang:1.13.8-alpine AS build
COPY ./ /go/src/github.com/marbles
WORKDIR /go/src/github.com/marbles
# Build application
RUN go build -o chaincode -v .
# Production ready image
# Pass the binary to the prod image
FROM alpine:3.11 as prod
COPY --from=build /go/src/github.com/marbles/chaincode /app/chaincode
USER 1000
WORKDIR /app
CMD ./chaincode
链码是使用一个golang的alpine镜像构建的。执行如下命令创建链码镜像:
$ docker build -t chaincode/marbles:1.0 .
一切顺利的话,就可以部署了。修改链码部署文件org1-chaincode-deployment.yaml和org2-chaincode-deployment.yaml中的CHAINCODE_CCID变量为你需要安装的链码。
#---------------- Chaincode Deployment ---------------------
apiVersion: apps/v1 # for versions before 1.8.0 use apps/v1beta1
kind: Deployment
metadata:
name: chaincode-marbles-org1
namespace: hyperledger
labels:
app: chaincode-marbles-org1
spec:
selector:
matchLabels:
app: chaincode-marbles-org1
strategy:
type: Recreate
template:
metadata:
labels:
app: chaincode-marbles-org1
spec:
containers:
- image: chaincode/marbles:1.0
name: chaincode-marbles-org1
imagePullPolicy: IfNotPresent
env:
- name: CHAINCODE_CCID
value: "marbles:d8140fbc1a0903bd88611a96c5b0077a2fdeef00a95c05bfe52e207f5f9ab79d"
- name: CHAINCODE_ADDRESS
value: "0.0.0.0:7052"
ports:
- containerPort: 7052
然后进行部署:
$ kubectl create -f chaincode/k8s
现在fabric网络看起来应该是这样:
$ kubectl get pods -n hyperledgerNAME READY STATUS RESTARTS AGE
ca-org1-84945b8c7b-tx59g 1/1 Running 0 19h
ca-org2-7454f69c48-nfzsq 1/1 Running 0 19h
chaincode-marbles-org1-6fc8858855-wdz7z 1/1 Running 0 20m
chaincode-marbles-org2-77bf56fdfb-6cdfm 1/1 Running 0 14m
cli-org1-589944999c-cvgbx 1/1 Running 0 19h
cli-org2-656cf8dd7c-kcxd7 1/1 Running 0 19h
orderer0-5844bd9bcc-6td8c 1/1 Running 0 46h
orderer1-75d8df99cd-6vbjl 1/1 Running 0 46h
orderer2-795cf7c4c-6lsdd 1/1 Running 0 46h
peer0-org1-5bc579d766-kq2qd 1/1 Running 0 19h
peer0-org2-77f58c87fd-sczp8 1/1 Running 0 19h
现在我们需要为每个机构审批链码。这是链码生命周期过程的一个新特性,每个机构都需要同意新链码的定义。我们将为org1审批marble链码定义。在org1的cli pod内执行如下命令,记得修改CHAINCODE_CCID:
$ peer lifecycle chaincode approveformyorg --channelID mychannel --name marbles --version 1.0 --init-required --package-id marbles:e001937433673b11673d660d142c722fc372905db87f88d2448eee42c9c63064 --sequence 1 -o orderer0:7050 --tls --cafile $ORDERER_CA --signature-policy "AND ('org1MSP.peer','org2MSP.peer')"
### Should print a similar output
2020-03-08 10:02:46.192 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [4d81ea5fd494e9717a0c860812d2b06bc62e4fc6c4b85fa6c3a916eee2c78e85] committed with status (VALID)
你可以用如下命令检查整个网络中的审批状态:
$ peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name marbles --version 1.0 --init-require
d --sequence 1 -o -orderer0:7050 --tls --cafile $ORDERER_CA --signature-policy "AND ('org1MSP.peer','org2MSP.peer')"
### Should print a similar output
Chaincode definition for chaincode 'marbles', version '1.0', sequence '1' on channel 'mychannel' approval status by org:
org1MSP: true
org2MSP: false
现在让我们为org2批准链码定义。在org2的cli pod中执行如下命令,记得修改CHAINCODE_CCID:
$ peer lifecycle chaincode approveformyorg --channelID mychannel --name marbles --version 1.0 --init-required --package-id marbles:25a9f6fe26161d29af928228ca1db0c41892e26e46335c84952336ee26d1fd93 --sequence 1 -o orderer0:7050 --tls --cafile $ORDERER_CA --signature-policy "AND ('org1MSP.peer','org2MSP.peer')"
### Should print a similar output
2020-03-08 10:26:43.992 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [74a89f3c93c10f14c626bd4d6cb654b37889908c9e6f7b983d2cad79f1e82267] committed with status (VALID)
再次检查链码提交状态:
$ peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name marbles --version 1.0 --init-required --sequence 1 -o orderer0:7050 --tls --cafile $ORDERER_CA --signature-policy "AND ('org1MSP.peer','org2MSP.peer')"
### Should print a similar output
Chaincode definition for chaincode 'marbles', version '1.0', sequence '1' on channel 'mychannel' approval status by org:
org1MSP: true
org2MSP: true
现在我们得到所有机构的批准,让我们将链码定义在通道上提交。可以在任何peer上执行如下操作:
$ peer lifecycle chaincode commit -o orderer0:7050 --channelID mychannel --name marbles --version 1.0 --sequence 1 --init-required --tls true --cafile $ORDERER_CA --peerAddresses peer0-org1:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1/peers/peer0-org1/tls/ca.crt --peerAddresses peer0-org2:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2/peers/peer0-org2/tls/ca.crt --signature-policy "AND ('org1MSP.peer','org2MSP.peer')"
### Should print a similar output
2020-03-08 14:13:49.516 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [568cb81f821698025bbc61f4c6cd3b4baf1aea632e1e1a8cfdf3ec3902d1c6bd] committed with status (VALID) at peer0-org1:7051
2020-03-08 14:13:49.533 UTC [chaincodeCmd] ClientWait -> INFO 002 txid [568cb81f821698025bbc61f4c6cd3b4baf1aea632e1e1a8cfdf3ec3902d1c6bd] committed with status (VALID) at peer0-org2:7051
好了,现在链码已经就绪,可以查询和调用了!
6、测试Fabric外部链码
我们可以从cli pod中测试链码的查询和交易调用。首先创建一些宝石:
$ peer chaincode invoke -o orderer0:7050 --tls true --cafile $ORDERER_CA -C mychannel -n marbles --peerAddresses
peer0-org1:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1/peers/peer0-org1/tls/ca.crt --peerAddresses peer
0-org2:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2/peers/peer0-org2/tls/ca.crt -c '{"Args":["initMarble
","marble1","blue","35","tom"]}' --waitForEvent
### Should print a similar output
2020-03-08 14:23:03.569 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [83aeeaac47cf6302bc139addc4aa38116a40eaff788846d87cc815d2e1318f44] committed with status (VALID) at peer0-org2:7051
2020-03-08 14:23:03.575 UTC [chaincodeCmd] ClientWait -> INFO 002 txid [83aeeaac47cf6302bc139addc4aa38116a40eaff788846d87cc815d2e1318f44] committed with status (VALID) at peer0-org1:7051
2020-03-08 14:23:03.576 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 003 Chaincode invoke successful. result: status:200
创建另一个宝石:
$ peer chaincode invoke -o orderer0:7050 --tls true --cafile $ORDERER_CA -C mychannel -n marbles --peerAddresses peer0-org1:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1/peers/peer0-org1/tls/ca.crt --peerAddresses peer0-org2:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2/peers/peer0-org2/tls/ca.crt -c '{"Args":["initMarble","marble2","red","50","tom"]}' --waitForEvent
### Should print a similar output
2020-03-08 14:23:40.404 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [8391f9f8ea84887a56f99e4dc4501eaa6696cd7bd6c524e4868bd6cfd5b85e78] committed with status (VALID) at peer0-org2:7051
2020-03-08 14:23:40.434 UTC [chaincodeCmd] ClientWait -> INFO 002 txid [8391f9f8ea84887a56f99e4dc4501eaa6696cd7bd6c524e4868bd6cfd5b85e78] committed with status (VALID) at peer0-org1:7051
2020-03-08 14:23:40.434 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 003 Chaincode invoke successful. result: status:200
查询宝石信息:
$ peer chaincode query -C mychannel -n marbles -c '{"Args":["readMarble","marble1"]}'
{"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}
也可以执行如下命令查询链码日志:
$ kubectl logs chaincode_pod_name -n hyperledger
### Should print a similar output
invoke is running initMarble
- start init marble
- end init marble
invoke is running initMarble
- start init marble
- end init marble
invoke is running readMarble