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

それからコンテナ内で実行するのは以下のcreateAnchorPeerUpdateupdateAnchorPeerになります。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. チェーンコードをパッケージ化する: このステップは、1 つの組織または各組織で実行できます。
  2. 自組織のピアにチェーンコードをインストールする: チェーンコードを使用してトランザクションのエンドースや台帳へのクエリをするすべての組織は、このステップを完了する必要があります。
  3. 自組織のチェーンコード定義を承認する: チェーンコードを使用するすべての組織は、このステップを完了する必要があります。チェーンコード定義は、チャネルでチェーンコードを開始する前に、チャネルの LifecycleEndorsement ポリシー (デフォルトでは過半数) を満たすのに十分な数の組織によって承認される必要があります。
  4. チェーンコード定義をチャネルにコミットする: チャネル上の必要な数の組織が承認したら、コミットトランザクションを 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"
}

次にinstallChaincodepeer 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}"
}

queryInstalledpeer 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)[]AssetJSON 文字列にしてctx.GetStub().PutState(asset.ID, assetJSON)を実行していて

GetStub()shim.ChaincodeStubInterfaceを返していて

fabric-contract-api-go/transaction_context.go at master · hyperledger/fabric-contract-api-go · GitHub

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
}