Hyperledgerのチュートリアルでチェインコードをデプロイしてみた
Hyperledger Tutorial をやってみた
Hyperledger のチュートリアルをやってみたので、その時の内容をまとめてみたいと思います。
環境構築
まずは環境構築ですが、以下に手順がまとめられています。
hyperledger-fabric.readthedocs.io
手順の中である以下のコマンドで Hyperledger Fabric のプラットフォーム固有のバイナリと設定ファイルおよび、チュートリアルで動かすコードのダウンロードができます。自分は Windows の環境だったので Hyper-V に CentOS7 の仮想マシンを立ち上げてそこにインストールしました。
curl -sSL https://bit.ly/2ysbOFE | bash -s
実行後、コマンド実行時のディレクトリの bin 直下にバイナリがダウンロードされるのでパスを通しておきます。
[root@localhost hyperledger]# ls bin/ configtxgen configtxlator cryptogen discover fabric-ca-client fabric-ca-server idemixgen orderer osnadmin peer
ダウンロードされたコマンドは以下にリファレンスがあります。
Commands Reference — hyperledger-fabricdocs master ドキュメント
それからチュートリアルで動かす以下のサンプルもダウンロードされています。
GitHub - hyperledger/fabric-samples
またサンプルを動かすには Docker が必要なので、Docker および Docker-Compose も入れておきます。
チュートリアル
環境が構築されたら以下のページのチュートリアルを実施していきます。今回はチェインコードのデプロイの動きを追ってみます。
hyperledger-fabric.readthedocs.io
Cahincode のデプロイ
Cahincode のデプロイの説明は以下になります。
Using the Fabric test network — hyperledger-fabricdocs master documentation
その前に Cahincode については、以下に説明があります。
Smart Contracts and Chaincode — hyperledger-fabricdocs master ドキュメント
- 台帳: ビジネスデータの集合の現在および過去の正しい状態をもっている
- スマートコンストラクト: 台帳に新しく追加される正しいデータを生成する実行可能なロジックを定義する
- チェインコード: Fabric の低レベルのシステムプログラミングの用途にも使用することができ、管理者が関連するスマートコントラクトをデプロイするためにひとまとめにするのに使われる
台帳の操作ができるのがスマートコンストラクトで、チェインコードは低レベルの操作ができて台帳の操作もできるのでスマートコンストラクトと呼べるのかと思います。
それではチュートリアル動かしますが、まず一通りコマンドを実行して動くのを確認してから中身の処理を追っていきます。実行するコマンドは docker を立ち上げて、チャネルを作成し、それからチェインコードをデプロイし、クエリを投げるになります。
[root@localhost hyperledger]# # PATHを通す [root@localhost hyperledger]# PATH=/home/arimura/work/hyperledger/bin/:$PATH [root@localhost hyperledger]# cd fabric-samples/test-network/ [root@localhost test-network]# # Docker立ち上げ [root@localhost test-network]# ./network.sh up Starting nodes with CLI timeout of '5' tries and CLI delay of '3' seconds and using database 'leveldb' LOCAL_VERSION=2.3.1 DOCKER_IMAGE_VERSION=2.3.1 ~省略~ [root@localhost test-network]# # チャネル作成 [root@localhost test-network]# ./network.sh createChannel Creating channel 'mychannel'. If network is not up, starting nodes with CLI timeout of '5' tries and CLI delay of '3' seconds and using database 'leveldb Generating channel genesis block 'mychannel.block' ~省略~ [root@localhost test-network]# # チェインコードデプロイ [root@localhost test-network]# ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go deploying chaincode on channel 'mychannel' executing with the following ~省略~ [root@localhost test-network]# # チュートリアルのコードの設定ファイルのパスを通す export FABRIC_CFG_PATH=$PWD/../config/ [root@localhost test-network]# export FABRIC_CFG_PATH=$PWD/../config/ [root@localhost test-network]# # クエリ実行のためOrg1の組織として実行できるよう環境変数を設定する [root@localhost test-network]# export CORE_PEER_TLS_ENABLED=true [root@localhost test-network]# export CORE_PEER_LOCALMSPID="Org1MSP" [root@localhost test-network]# export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt [root@localhost test-network]# export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp [root@localhost test-network]# export CORE_PEER_ADDRESS=localhost:7051 [root@localhost test-network]# # 台帳初期化のチェインコード実行 [root@localhost test-network]# peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"InitLedger","Args":[]}' 2021-02-28 11:19:00.101 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 [root@localhost test-network]# # クエリを投げて台帳への登録内容を確認する [root@localhost test-network]# peer chaincode query -C mychannel -n basic -c '{"Args":["GetAllAssets"]}' [{"ID":"asset1","color":"blue","size":5,"owner":"Tomoko","appraisedValue":300},{"ID":"asset2","color":"red","size":5,"owner":"Brad","appraisedValue":400},{"ID":"asset3","color":"green","size":10,"owner":"Jin Soo","appraisedValue":500},{"ID":"asset4","color":"yellow","size":10,"owner":"Max","appraisedValue":600},{"ID":"asset5","color":"black","size":15,"owner":"Adriana","appraisedValue":700},{"ID":"asset6","color":"white","size":15,"owner":"Michel","appraisedValue":800}] [root@localhost test-network]# # 動作が確認できたのでDockerを停止する [root@localhost test-network]# ./network.sh down Stopping network Stopping cli ... done ~停止~
サンプルが動くのが確認できたので中身を見ていきます。
Docker 立ち上げ
実行シェルを見てみると以下の関数内でdocker-compose up
しているのが確認できます。
function networkUp() { checkPrereqs # generate artifacts if they don't exist if [ ! -d "organizations/peerOrganizations" ]; then createOrgs fi COMPOSE_FILES="-f ${COMPOSE_FILE_BASE}" if [ "${DATABASE}" == "couchdb" ]; then COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}" fi docker-compose ${COMPOSE_FILES} up -d 2>&1 docker ps -a if [ $? -ne 0 ]; then fatalln "Unable to start network" fi }
fabric-samples/test-network/docker
には yml が 3 つあるのですが、docker コンテナのプロセスを確認してみると./network.sh up
で使われるのはdocker-compose-test-net.yaml
のみのようです。
[root@localhost test-network]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f9e8c15ab3f3 hyperledger/fabric-tools:latest "/bin/bash" About a minute ago Up About a minute cli fbc76883047f hyperledger/fabric-peer:latest "peer node start" About a minute ago Up About a minute 7051/tcp, 0.0.0.0:9051->9051/tcp peer0.org2.example.com 78a715239860 hyperledger/fabric-orderer:latest "orderer" About a minute ago Up About a minute 0.0.0.0:7050->7050/tcp, 0.0.0.0:7053->7053/tcp orderer.example.com 9dd1c32d9dfe hyperledger/fabric-peer:latest "peer node start" About a minute ago Up About a minute 0.0.0.0:7051->7051/tcp peer0.org1.example.com
docker-compose-test-net.yaml
を見てみますとhyperledger/fabric-orderer:latest
のコンテナを一つ、hyperledger/fabric-peer:latest
のコンテナを 2 つ立ち上げていることが確認できます。
fabric-orderer
はオーダーノードとして機能します。以下に説明がありますが各クライアントからのトランザクションを受け取って実行順序を決める役割などクライアントのインターフェースの役割があるのかと思います。
hyperledger-fabric.readthedocs.io
fabric-peer
はピアとして機能し、以下に説明があります。
hyperledger-fabric.readthedocs.io
ピアの説明を見てみますとブロックチェーンネットワーク内の各ピア毎で台帳とスマートコンストラクトのコピーを保持していることが確認できます。ピアは組織の単位になり、yml の設定ではpeer0.org1.example.com:
とpeer0.org2.example.com:
の 2 つの組織が存在していることが確認できます。各ピア毎で保持した台帳、スマートコンストラクトを共有参照するためにはチェネルに参加させる必要があります。チャネルに参加したピアを識別するには MSP が必要になり、yml から../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp
にファイルが置いてあるようで、見てみると pem があるのが確認できます。この MSP のパスは peer コマンドを実行するとき組織の判定にも必要になるので以下のように環境変数を設定しています。
[root@localhost test-network]# export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
環境変数について一覧がまとまっているところは確認できなかったのですが、CORE_PEER_MSPCONFIGPATH については以下のページで確認できました。
Updating the capability level of a channel — hyperledger-fabricdocs master documentation
# Copyright IBM Corp. All Rights Reserved. # # SPDX-License-Identifier: Apache-2.0 # version: '2.4' volumes: orderer.example.com: peer0.org1.example.com: peer0.org2.example.com: networks: test: name: fabric_test services: orderer.example.com: container_name: orderer.example.com image: hyperledger/fabric-orderer:latest labels: service: hyperledger-fabric environment: - FABRIC_LOGGING_SPEC=INFO ~省略~ - ORDERER_ADMIN_LISTENADDRESS=0.0.0.0:7053 working_dir: /opt/gopath/src/github.com/hyperledger/fabric command: orderer volumes: - ../system-genesis-block/genesis.block:/var/hyperledger/orderer/orderer.genesis.block - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls - orderer.example.com:/var/hyperledger/production/orderer ports: - 7050:7050 - 7053:7053 networks: - test peer0.org1.example.com: container_name: peer0.org1.example.com image: hyperledger/fabric-peer:latest labels: service: hyperledger-fabric environment: #Generic peer variables - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock ~省略~ volumes: - /var/run/docker.sock:/host/var/run/docker.sock - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls - peer0.org1.example.com:/var/hyperledger/production working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: peer node start ports: - 7051:7051 networks: - test peer0.org2.example.com: container_name: peer0.org2.example.com image: hyperledger/fabric-peer:latest labels: service: hyperledger-fabric environment: #Generic peer variables - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock ~省略~ volumes: - /var/run/docker.sock:/host/var/run/docker.sock - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls - peer0.org2.example.com:/var/hyperledger/production working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: peer node start ports: - 9051:9051 networks: - test cli: container_name: cli image: hyperledger/fabric-tools:latest labels: service: hyperledger-fabric tty: true stdin_open: true environment: ~省略~ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash volumes: - /var/run/:/host/var/run/ - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations - ../scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/ depends_on: - peer0.org1.example.com - peer0.org2.example.com networks: - test
チャネル作成
次にチャネルの作成ですが、shell 内では以下の関数を実行しています。
export VERBOSE=false # another container before giving up MAX_RETRY=5 # default for delay between commands CLI_DELAY=3 # channel name defaults to "mychannel" CHANNEL_NAME="mychannel" # call the script to create the channel, join the peers of org1 and org2, # and then update the anchor peers for each organization function createChannel() { # Bring up the network if it is not already up. if [ ! -d "organizations/peerOrganizations" ]; then infoln "Bringing up network" networkUp fi # now run the script that creates a channel. This script uses configtxgen once # to create the channel creation transaction and the anchor peer updates. scripts/createChannel.sh $CHANNEL_NAME $CLI_DELAY $MAX_RETRY $VERBOSE }
scripts/createChannel.sh
ですが、中身を見てみると shell 内で定義されたcreateChannelGenesisBlock
,createChannel
,joinChannel
,setAnchorPeer
を順に実行していることが確認できます。ジェネシスブロックを作ってチャネルを作ってピアを参加させて設定の反映といった流れかと思います。
createChannelGenesisBlock
ですが configtxgen コマンドを実行してジェネシスブロックを作成していることが確認できます。
configtxgen — hyperledger-fabricdocs master documentation
createChannelGenesisBlock() { which configtxgen if [ "$?" -ne 0 ]; then fatalln "configtxgen tool not found." fi set -x configtxgen -profile TwoOrgsApplicationGenesis -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME res=$? { set +x; } 2>/dev/null verifyResult $res "Failed to generate channel configuration transaction..." }
createChannel
ではosnadmin channel
コマンドでチャネルを作成してオーダーノードを参加させています。
osnadmin channel — hyperledger-fabricdocs master documentation
createChannel() { setGlobals 1 # Poll in case the raft leader is not set yet local rc=1 local COUNTER=1 while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do sleep $DELAY set -x osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7053 --ca-file "$ORDERER_CA" --client-cert "$ORDERER_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER_ADMIN_TLS_PRIVATE_KEY" >&log.txt res=$? { set +x; } 2>/dev/null let rc=$res COUNTER=$(expr $COUNTER + 1) done cat log.txt verifyResult $res "Channel creation failed" }
次にjoinChannel
ではpeer channel
コマンドでピアをチャネルに参加させています。
peer channel — hyperledger-fabricdocs master documentation
サンプルコード内で定義したsetGlobals
関数にて参加対象のピアの環境変数を設定したうえでpeer channel
コマンドを実行しています。
# joinChannel ORG joinChannel() { FABRIC_CFG_PATH=$PWD/../config/ ORG=$1 setGlobals $ORG local rc=1 local COUNTER=1 ## Sometimes Join takes time, hence retry while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do sleep $DELAY set -x peer channel join -b $BLOCKFILE >&log.txt res=$? { set +x; } 2>/dev/null let rc=$res COUNTER=$(expr $COUNTER + 1) done cat log.txt verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' " }
最後に setAnchorPeer ですが、docker コンテナ内でシェルを実行しています。
setAnchorPeer() { ORG=$1 docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME }
まず shell を実行する cli コンテナですが、以下のfabric-tools
がイメージとなります。あまり情報が出てこなかったのですがオーダーノードではなく各ピアと対話的コマンドを実行するのに必要となるようです。
cli: container_name: cli image: hyperledger/fabric-tools:latest labels: service: hyperledger-fabric tty: true stdin_open: true environment: - GOPATH=/opt/gopath - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - FABRIC_LOGGING_SPEC=INFO #- FABRIC_LOGGING_SPEC=DEBUG working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash volumes: - /var/run/:/host/var/run/ - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations - ../scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/ depends_on: - peer0.org1.example.com - peer0.org2.example.com networks: - test
それからコンテナ内で実行するのは以下のcreateAnchorPeerUpdate
とupdateAnchorPeer
になります。createConfigUpdate
もサンプルコード内で定義された関数でconfigtxlator proto_encode
コマンドにて受け取った json を ProtocolBuffer の形式に変換しています。
configtxlator — hyperledger-fabricdocs master documentation
それからupdateAnchorPeer
関数内ではpeer channel update
コマンドでチャネルに更新内容を反映しています。
peer channel — hyperledger-fabricdocs master documentation
# NOTE: this must be run in a CLI container since it requires jq and configtxlator createAnchorPeerUpdate() { infoln "Fetching channel config for channel $CHANNEL_NAME" fetchChannelConfig $ORG $CHANNEL_NAME ${CORE_PEER_LOCALMSPID}config.json infoln "Generating anchor peer update transaction for Org${ORG} on channel $CHANNEL_NAME" if [ $ORG -eq 1 ]; then HOST="peer0.org1.example.com" PORT=7051 elif [ $ORG -eq 2 ]; then HOST="peer0.org2.example.com" PORT=9051 elif [ $ORG -eq 3 ]; then HOST="peer0.org3.example.com" PORT=11051 else errorln "Org${ORG} unknown" fi set -x # Modify the configuration to append the anchor peer jq '.channel_group.groups.Application.groups.'${CORE_PEER_LOCALMSPID}'.values += {"AnchorPeers":{"mod_policy": "Admins","value":{"anchor_peers": [{"host": "'$HOST'","port": '$PORT'}]},"version": "0"}}' ${CORE_PEER_LOCALMSPID}config.json > ${CORE_PEER_LOCALMSPID}modified_config.json { set +x; } 2>/dev/null # Compute a config update, based on the differences between # {orgmsp}config.json and {orgmsp}modified_config.json, write # it as a transaction to {orgmsp}anchors.tx createConfigUpdate ${CHANNEL_NAME} ${CORE_PEER_LOCALMSPID}config.json ${CORE_PEER_LOCALMSPID}modified_config.json ${CORE_PEER_LOCALMSPID}anchors.tx } updateAnchorPeer() { peer channel update -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME -f ${CORE_PEER_LOCALMSPID}anchors.tx --tls --cafile "$ORDERER_CA" >&log.txt res=$? cat log.txt verifyResult $res "Anchor peer update failed" successln "Anchor peer set for org '$CORE_PEER_LOCALMSPID' on channel '$CHANNEL_NAME'" }
チェインコードデプロイ
以下のコマンドで実行していたチェーンコードのデプロイについて確認していきます。
# ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
deployCC.sh の中身を見てみますとpackageChaincode
, installChaincode
, queryInstalled
, approveForMyOrg
, checkCommitReadiness
,commitChaincodeDefinition
,queryCommitted
,chaincodeInvokeInit
を順に実行しているようです。
以下のページを見てみると
hyperledger-fabric.readthedocs.io
チェインコードのデプロイは以下の流れになっているようです。
- チェーンコードをパッケージ化する: このステップは、1 つの組織または各組織で実行できます。
- 自組織のピアにチェーンコードをインストールする: チェーンコードを使用してトランザクションのエンドースや台帳へのクエリをするすべての組織は、このステップを完了する必要があります。
- 自組織のチェーンコード定義を承認する: チェーンコードを使用するすべての組織は、このステップを完了する必要があります。チェーンコード定義は、チャネルでチェーンコードを開始する前に、チャネルの LifecycleEndorsement ポリシー (デフォルトでは過半数) を満たすのに十分な数の組織によって承認される必要があります。
- チェーンコード定義をチャネルにコミットする: チャネル上の必要な数の組織が承認したら、コミットトランザクションを 1 つの組織が送信する必要があります。送信者はまず、承認した組織の十分な数のピアからエンドースメントを集め、次にトランザクションを送信して、チェーンコード定義をコミットします。
チェインコードデプロイで使うコマンドについては以下に説明があります。
hyperledger-fabric.readthedocs.io
それではpackageChaincode
を確認してみるとpeer lifecycle chaincode
コマンドを実行しているようで、 ソースのパス、言語、ラベルを指定して 圧縮してパッケージ化するようです。パッケージ対象asset-transfer-basic/chaincode-go
で go のサンプルを指定しています。
peer lifecycle chaincode — hyperledger-fabricdocs master ドキュメント
packageChaincode() { set -x peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION} >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt verifyResult $res "Chaincode packaging has failed" successln "Chaincode is packaged" }
次にinstallChaincode
はpeer lifecycle chaincode package
で出力したパッケージを指定してpeer lifecycle chaincode install
でインストールしています。ここでのインストールの対象はデプロイの流れの説明であったように自組織になりますが、サンプルではsetGlobals
で組織を指定して org1,org2 の両方にインストールしていますが、通常であれば自組織のみにインストールかと思います。
# installChaincode PEER ORG installChaincode() { ORG=$1 setGlobals $ORG set -x peer lifecycle chaincode install ${CC_NAME}.tar.gz >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt verifyResult $res "Chaincode installation on peer0.org${ORG} has failed" successln "Chaincode is installed on peer0.org${ORG}" }
queryInstalled
はpeer lifecycle chaincode queryinstalled
を実行しています。ここではピアにインストールしたパッケージを承認するために必要となるパッケージの ID を取得しています。パッケージ ID を取得するだけなので実行対象は一つのピアだけで大丈夫です。
queryInstalled() { ORG=$1 setGlobals $ORG set -x peer lifecycle chaincode queryinstalled >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt PACKAGE_ID=$(sed -n "/${CC_NAME}_${CC_VERSION}/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt) verifyResult $res "Query installed on peer0.org${ORG} has failed" successln "Query installed successful on peer0.org${ORG} on channel" }
それからapproveForMyOrg
関数内で peer lifecycle chaincode approveformyorg
コマンドを実行しピア指定でインストールしたパッケージを承認しています。これは各ピアで実行する必要があります。これはチャネルの LifecycleEndorsement ポリシー (デフォルトでは過半数) を満たすのに十分な数の組織によって承認される必要があるようです。
# approveForMyOrg VERSION PEER ORG approveForMyOrg() { ORG=$1 setGlobals $ORG set -x peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt verifyResult $res "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME' failed" successln "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME'" }
それからcheckCommitReadiness
関数ではpeer lifecycle chaincode checkcommitreadiness
コマンドチェインコードをチャネルにコミットできるかが確認できます。ピア指定で実行することで、そのピアで事前にpeer lifecycle chaincode approveformyorg
が実行されて承認されているかが確認できます。
# checkCommitReadiness VERSION PEER ORG checkCommitReadiness() { ORG=$1 shift 1 setGlobals $ORG infoln "Checking the commit readiness of the chaincode definition on peer0.org${ORG} on channel '$CHANNEL_NAME'..." local rc=1 local COUNTER=1 # continue to poll # we either get a successful response, or reach MAX RETRY while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do sleep $DELAY infoln "Attempting to check the commit readiness of the chaincode definition on peer0.org${ORG}, Retry after $DELAY seconds." set -x peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} --output json >&log.txt res=$? { set +x; } 2>/dev/null let rc=0 for var in "$@"; do grep "$var" log.txt &>/dev/null || let rc=1 done COUNTER=$(expr $COUNTER + 1) done cat log.txt if test $rc -eq 0; then infoln "Checking the commit readiness of the chaincode definition successful on peer0.org${ORG} on channel '$CHANNEL_NAME'" else fatalln "After $MAX_RETRY attempts, Check commit readiness result on peer0.org${ORG} is INVALID!" fi }
そしてcommitChaincodeDefinition
関数内でpeer lifecycle chaincode commit
コマンドを実行しチェインコード定義をコミットします。
# commitChaincodeDefinition VERSION PEER ORG (PEER ORG)... commitChaincodeDefinition() { parsePeerConnectionParameters $@ res=$? verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters " # while 'peer chaincode' command can get the orderer endpoint from the # peer (if join was successful), let's supply it directly as we know # it using the "-o" option set -x peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt verifyResult $res "Chaincode definition commit failed on peer0.org${ORG} on channel '$CHANNEL_NAME' failed" successln "Chaincode definition committed on channel '$CHANNEL_NAME'" }
最後にchaincodeInvokeInit
関数内のpeer chaincode invoke
コマンドで--isInit
のオプションを付与し初期化処理の実行にあるのですがサンプルではCC_INIT_FCN=NA
となっているので、ここでは何もしていません。
peer chaincode — hyperledger-fabricdocs master ドキュメント
chaincodeInvokeInit() { parsePeerConnectionParameters $@ res=$? verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters " # while 'peer chaincode' command can get the orderer endpoint from the # peer (if join was successful), let's supply it directly as we know # it using the "-o" option set -x fcn_call='{"function":"'${CC_INIT_FCN}'","Args":[]}' infoln "invoke fcn call:${fcn_call}" peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} "${PEER_CONN_PARMS[@]}" --isInit -c ${fcn_call} >&log.txt res=$? { set +x; } 2>/dev/null cat log.txt verifyResult $res "Invoke execution on $PEERS failed " successln "Invoke transaction successful on $PEERS on channel '$CHANNEL_NAME'" } ## Invoke the chaincode - this does require that the chaincode have the 'initLedger' ## method defined if [ "$CC_INIT_FCN" = "NA" ]; then infoln "Chaincode initialization is not required" else chaincodeInvokeInit 1 2 fi
チェインコード実行
チェインコードの実行ではまず環境変数を設定します。
[root@localhost test-network]# # チュートリアルのコードの設定ファイルのパスを通す export FABRIC_CFG_PATH=$PWD/../config/ [root@localhost test-network]# export FABRIC_CFG_PATH=$PWD/../config/ [root@localhost test-network]# # クエリ実行のためOrg1の組織として実行できるよう環境変数を設定する [root@localhost test-network]# export CORE_PEER_TLS_ENABLED=true [root@localhost test-network]# export CORE_PEER_LOCALMSPID="Org1MSP" [root@localhost test-network]# export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt [root@localhost test-network]# export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp [root@localhost test-network]# export CORE_PEER_ADDRESS=localhost:7051
それからデプロイの手順の最後にもあったpeer chaincode invoke
を実行します。
[root@localhost test-network]# # 台帳初期化のチェインコード実行 [root@localhost test-network]# peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"InitLedger","Args":[]}' 2021-02-28 11:19:00.101 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
証明書やチャンネルなどの指定がありますが実行している内容は-c '{"function":"InitLedger","Args":[]}'
で指定しています。台帳への書き込み処理になるので証明書の指定が必要になるのかと思います。
実行しているInitLedger
は以下のようになっていて go の SDK で定義されたcontractapi.Contract
が埋め込まれたSmartContract
構造体に対して関数を追加しているようです。
fabric-contract-api-go/contract.go at master · hyperledger/fabric-contract-api-go · GitHub
json.Marshal(asset)
で[]Asset
を JSON 文字列にしてctx.GetStub().PutState(asset.ID, assetJSON)
を実行していて
GetStub()
はshim.ChaincodeStubInterface
を返していて
PutState
で台帳に書き込んでいます。
fabric-chaincode-go/stub.go at master · hyperledger/fabric-chaincode-go · GitHub
import ( "encoding/json" "fmt" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) // SmartContract provides functions for managing an Asset type SmartContract struct { contractapi.Contract } // InitLedger adds a base set of assets to the ledger func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { assets := []Asset{ {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}, {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400}, {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500}, {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600}, {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700}, {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800}, } for _, asset := range assets { assetJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(asset.ID, assetJSON) if err != nil { return fmt.Errorf("failed to put to world state. %v", err) } } return nil }
次に以下のようにクエリを実行しています。
[root@localhost test-network]# # クエリを投げて台帳への登録内容を確認する [root@localhost test-network]# peer chaincode query -C mychannel -n basic -c '{"Args":["GetAllAssets"]}' [{"ID":"asset1","color":"blue","size":5,"owner":"Tomoko","appraisedValue":300},{"ID":"asset2","color":"red","size":5,"owner":"Brad","appraisedValue":400},{"ID":"asset3","color":"green","size":10,"owner":"Jin Soo","appraisedValue":500},{"ID":"asset4","color":"yellow","size":10,"owner":"Max","appraisedValue":600},{"ID":"asset5","color":"black","size":15,"owner":"Adriana","appraisedValue":700},{"ID":"asset6","color":"white","size":15,"owner":"Michel","appraisedValue":800}]
GetAllAssets
を実行していて、こちらは以下のように台帳に書き込まれた内容を全件返していて実際そのように動いているのが確認できます。
// GetAllAssets returns all assets found in world state func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) { // range query with empty string for startKey and endKey does an // open-ended query of all assets in the chaincode namespace. resultsIterator, err := ctx.GetStub().GetStateByRange("", "") if err != nil { return nil, err } defer resultsIterator.Close() var assets []*Asset for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } var asset Asset err = json.Unmarshal(queryResponse.Value, &asset) if err != nil { return nil, err } assets = append(assets, &asset) } return assets, nil }