【よくわかる】Amazon Managed Blockchainによるプライベートネットワークの構築-Part 1インフラ構築編

記事タイトルとURLをコピーする

こんにちは、ディベロップメントサービス1課の山本です。

今回の記事では、AMB(Amazon Managed Blockchain) の利用方法について解説します。

特に、プライベートネットワークの構築について、AWS が提供しているサンプルを用いて詳しく説明します。

このサンプルは 9 つのパートに分かれていますので、1 つのパートを 1 つのブログ記事として扱い、それぞれ詳しく解説していきます。

皆様の理解を深めるために、図や詳細な説明を多く取り入れる予定です。

ブロックチェーンやAMBの基本的な内容が知りたい方は、過去のブログを参考ください。

blog.serverworks.co.jp

blog.serverworks.co.jp

この記事の対象者は?

この記事は以下の方々に向けて書かれています:

  • Amazon Managed Blockchain を使用してプライベートネットワークを構築することを検討している開発者の方々
  • AWS の公式ドキュメントを読んだものの、具体的なイメージが湧かなかった方々

進め方

AMB(Amazon Managed Blockchain)のプライベートネットワークについての理解を深めるため、AWS が提供しているサンプルを用いて具体的な実装を進めていきます。

この過程で、各ステップの説明を挟みつつ、実際の操作を行っていきます。

サンプルの詳細は以下のリンクからご覧いただけます:

https://github.com/aws-samples/non-profit-blockchain/blob/master/README.md

サンプルの概要

このサンプルでは、架空の非営利団体(NGO 団体)向けに、寄付金の出納をブロックチェーンで管理するシステムを構築します。

このシステムを通じて、ブロックチェーンの透明性と信頼性を活用し、寄付金の管理をより効率的かつ安全に行う方法を学びます。

サンプルのパート

  1. Amazon マネージド ブロックチェーンを使用して Hyperledger Fabric ブロックチェーン ネットワークを構築することでワークショップを開始します。
  2. 非営利チェーンコードをデプロイします。
  3. RESTful API サーバーを実行します。
  4. アプリケーションを実行します。
  5. 新しいメンバーをネットワークに追加します。
  6. Amazon API Gateway と AWS Lambda を使用したブロックチェーンへの読み取りと書き込み。
  7. ブロックチェーン イベントを使用して、NGO の寄付をユーザーに通知します。
  8. Hyperledger Explorer を展開します。
  9. ブロックチェーンユーザーと Amazon Cognito の統合。

本ブログでは

1. Amazon マネージド ブロックチェーンを使用して Hyperledger Fabric ブロックチェーン ネットワークを構築することでワークショップを開始します。

を解説します。

Hyperledger Fabric ブロックチェーン ネットワークの構築

このセクションでは、こちらの README.mdを参考に、Hyperledger Fabric ブロックチェーン ネットワークの構築を行います。

本ブログの目標は以下の通りです:

  • 構成図通りのインフラを正確に作成する
  • チェーンコード(スマートコントラクト)を利用してデータの取得や変更を実施する
  • 各ステップの内容を、README.md よりもわかりやすく説明する

構成図

このパートでは、以下の図に示すネットワーク構成を構築します。

構成図

前提条件

本作業は、AWS Cloud9 を使用してデプロイを行い、Fabric クライアントノードへのアクセスを実施します。

以下の条件で Cloud9 のインスタンスを構築します。

  • リージョン: us-east-1
  • 環境の名前: fabric-c9
  • インスタンスタイプ: t2.medium

インスタンスの構築には約 30 秒から 60 秒程度かかります。

構築が完了したら、ターミナルを開き、AWS のサンプルリポジトリをホームディレクトリにクローンします。

cd ~
git clone https://github.com/aws-samples/non-profit-blockchain.git

次に、AWS CLI を最新版に更新します。

sudo pip install awscli --upgrade

これで事前準備は完了です。

ステップ 1:Hyperledger Fabric ブロックチェーン ネットワークを作成する

提供された CloudFormation テンプレートを使用してファブリック ネットワークを作成します。
CloudFormation テンプレートはngo-fabric/amb.yamlにあります。

作成するリソースは、ブロックチェーンに参加するメンバー(デフォルト:ngo-member)と そのメンバーが所有するピアノードの二つとなります。

構成図:ステップ1

下記コマンドでテンプレートをデプロイします。 デプロイ完了までには、1 時間程度かかるのでご注意ください。

export REGION=us-east-1
export STACKNAME=non-profit-amb
cd ~/non-profit-blockchain/ngo-fabric
./amb.sh

環境変数を上書きすることにより、テンプレート内のパラメータを変更することができるので、必要に応じて実施ください。
ただし、サンプルで作成済みの AMI を利用するため、リージョンはus-east-1を推奨します。

ステップ 2:ネットワークが利用可能であることを確認する

デプロイが完了するまでコーヒーでも飲んで待ちましょう。
私のおすすめは、スタバのペットボトルコーヒーです。

CloudFormation のスタックステータスが CREATE_COMPLETE になったら完了です。

CloudFormation:完了画面

ステップ 3:ファブリック クライアント ノードを作成する

ファブリック CLI をホストするファブリッククライアントノードを EC2 で作成します。

ファブリック CLI とはブロックチェーンを構成するファブリックネットワークの設定を行う CLI となります。

このノードは、独自の VPC に作成されて、VPC エンドポイント経由でステップ 1 で作成したファブリックネットワークにアクセスします。

構成図:ステップ3

ngo-fabric/fabric-client-node.yamlにあるテンプレートを利用して、以下のリソースを作成します。
※ 作成用の AMI は us-east-1 でのみ利用可能です。

別リージョンで構築されている方は、そのリージョンにコピー後、AMI ID を置き換えてください。

作成するリソースは、クライアントノードとなる EC2 とネットワーク周りのリソースとなります。

下記コマンドでテンプレートをデプロイします。

export REGION=us-east-1
cd ~/non-profit-blockchain/ngo-fabric
./vpc-client-node.sh

下記エラーが出た場合も問題ないので、無視してください。 これは、スクリプトがキーペアを作成時に、既存のキーペアを上書きしない処理になっているため発生します。

An error occurred (InvalidKeyPair.NotFound)

ステップ 4:ファブリック クライアントノードを準備し、ID を登録する

ファブリック CLI を利用する際、下記の情報をクライアントノードに設定する必要があります。

  • 接続先のファブリックネットワーク
  • 接続先のピアノード
  • 利用する TLS 証明書

このステップではクライアントノードに cloud9 から SSH で接続し、環境変数の更新を実施します。

構成図:ステップ4

クライアントノードへの SSH 接続

SSH 用のキー(.pem ファイル)は スタック生成時に code9 のホームディレクトリに格納されてます。

administrator:~ $ ls
environment  ngo-keypair.pem  node_modules  non-profit-blockchain  package.json  package-lock.json

DNS はステップ 3 で作成したスタックの出力に記載しています。
※対象リソースは全て削除済みです。

EC2:DNS名

下記コマンドでクライアントノードに SSH でアクセスします。

cd ~
ssh ec2-user@<dns of EC2 instance> -i ~/<Fabric network name>-keypair.pem

Are you sure you want to continue connecting (yes/no) が表示されたら、yesを入力します。

クライアントノードの環境変数更新

まず、サンプルのリポジトリをクライアントノードでクローンします。

cd ~
git clone https://github.com/aws-samples/non-profit-blockchain.git

次に環境変数を上書きします。
※SSH セッションを今後一旦終了する場合は、再度ファイルをソースする必要があります。

export REGION=us-east-1
cd ~/non-profit-blockchain/ngo-fabric
cp templates/exports-template.sh fabric-exports.sh
source fabric-exports.sh
source ~/peer-exports.sh

ここで変更した環境変数は以下の通りです。
※この環境は既に削除済みです。

Name Value 説明
ADMINPWD **** メンバー管理者パスワード
ADMINUSER admin メンバー管理者名
CAFILE /opt/home/managedblockchain-tls-chain.pem CA ファイル置き場
CASERVICEENDPOINT ca.m-n5mspx3yfja35hkrjs5ofvbwca.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30002 CA サービスエンドポイント
CHAINCODEDIR github.com/chaincode_example02/go チェインコードのパス
CHAINCODENAME mycc チェインコード名
CHAINCODEVERSION v0 チェインコードバージョン
CHANNEL mychannel チャネル名
MEMBERID m-N5MSPX3YFJA35HKRJS5OFVBWCA メンバー ID
MEMBERNAME member-ngo メンバー名
MSP_PATH /opt/home/admin-msp MSP(Membership Service Provider)のパス
MSP m-N5MSPX3YFJA35HKRJS5OFVBWCA MSP の ID
NETWORKID n-WTU2LODE4JENXN6TS2JRHJTUJA ネットワークの ID
NETWORKNAME ngo ネットワーク名
NETWORKVERSION 1.4 Hyperledger Fabric のバージョン
ORDERER orderer.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30001 Ordering Service のエンドポイント
ORDERINGSERVICEENDPOINT orderer.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30001 Ordering Service のエンドポイント
ORDERINGSERVICEENDPOINTNOPORT orderer.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com Ordering Service のエンドポイント(ポート番号無し)
PEER nd-iy5ft3o7fjcl7oe4dpftpdzuzy.m-n5mspx3yfja35hkrjs5ofvbwca.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30003  ピアエンドポイント
PEEREVENTENDPOINT nd-iy5ft3o7fjcl7oe4dpftpdzuzy.m-n5mspx3yfja35hkrjs5ofvbwca.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30004 ピアイベントエンドポイント 
PEERNODEID nd-IY5FT3O7FJCL7OE4DPFTPDZUZY ピアノード ID
PEERSERVICEENDPOINT nd-iy5ft3o7fjcl7oe4dpftpdzuzy.m-n5mspx3yfja35hkrjs5ofvbwca.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com:30003 ピアエンドポイント
PEERSERVICEENDPOINTNOPORT nd-iy5ft3o7fjcl7oe4dpftpdzuzy.m-n5mspx3yfja35hkrjs5ofvbwca.n-wtu2lode4jenxn6ts2jrhjtuja.managedblockchain.us-east-1.amazonaws.com ピアエンドポイント(ポート番号無し)
STACKNAME non-profit-amb CFN スタック名
VPCENDPOINTSERVICENAME com.amazonaws.us-east-1.managedblockchain.n-wtu2lode4jenxn6ts2jrhjtuja VPC エンドポイント

こちらの情報を元に、AMB へと接続します。

各種証明書の取得・生成

次に、最新バージョンの AMB のサーバー証明書を取得します。

aws s3 cp s3://us-east-1.managedblockchain/etc/managedblockchain-tls-chain.pem  /home/ec2-user/managedblockchain-tls-chain.pem

取得したサーバー証明書を利用して、管理者 ID をファブリック CA(認証局)に登録します。

この ID はファブリックネットワークを管理し、チャネルの生成やチェーンコードのインスタンス化ができます。

export PATH=$PATH:/home/ec2-user/go/src/github.com/hyperledger/fabric-ca/bin
cd ~
fabric-ca-client enroll -u https://$ADMINUSER:$ADMINPWD@$CASERVICEENDPOINT --tls.certfiles /home/ec2-user/managedblockchain-tls-chain.pem -M /home/ec2-user/admin-msp

これにより、/home/ec2-user/admin-mspの配下に各種証明書や公開鍵が生成されます。

最後に生成した証明書をコピーします。

mkdir -p /home/ec2-user/admin-msp/admincerts
cp ~/admin-msp/signcerts/* ~/admin-msp/admincerts/
cd ~/non-profit-blockchain/ngo-fabric

ステップ 5:configtx チャネル構成を更新する

ネットワーク内で、通信する際にチャネルと呼ばれるグループが必要となります。

このステップでは、チャネルを生成時に必要となる設定ファイルを作成します。

チャネル説明

configtx.yaml->{チャネル名}.pbの順番で作成します。

まず、サンプルのconfigtx.yamlのメンバー ID を実際のものに置換します。

cp ~/non-profit-blockchain/ngo-fabric/configtx.yaml ~
sed -i "s|__MEMBERID__|$MEMBERID|g" ~/configtx.yaml

このconfigtx.yamlの内容を、ファブリック CLI を利用して{チャネル名}.pbに変換します。 次ステップでチャネルを作成した際、この内容がブロックの最初の内容になります。

docker exec cli configtxgen -outputCreateChannelTx /opt/home/$CHANNEL.pb -profile OneOrgChannel -channelID $CHANNEL --configPath /opt/home/

{チャネル名}.pbが生成されたことを確認します。

ls -lt ~/$CHANNEL.pb

実行結果

-rw-r--r-- 1 root root 327 Nov 24 07:05 /home/ec2-user/mychannel.pb

ステップ 6:ファブリックチャネルを作成する

早速、ファブリックチャネルを生成しましょう。
ただし、このステップではピアノードの参加はまだとなります。

構成図:ステップ6

次のコマンドを実行します。

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer channel create -c $CHANNEL -f /opt/home/$CHANNEL.pb -o $ORDERER --cafile $CAFILE --tls --timeout 900s

/opt/home/fabric-samples/chaincode/hyperledger/fabric/peer内に mychannel.blockというファイルが生成されます。

ファイル生成の確認。

ls -lt /home/ec2-user/fabric-samples/chaincode/hyperledger/fabric/peer

実行結果

-rw-r--r-- 1 root root 13185 Nov 24 07:15 mychannel.block

このディレクトリは、ピアノードからマウントされているので、ブロックファイルの確認はこのディレクトリ内で行えます。

ステップ 7:ピアノードをチャネルに参加させる

ピアノードをファブリックチャネルに参加させます。

構成図:ステップ7

次のコマンドを実行します。

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer channel join -b $CHANNEL.block  -o $ORDERER --cafile $CAFILE --tls

実行結果

2023-11-24 07:21:29.100 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2023-11-24 07:21:29.772 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel

ステップ 8:ピアノードにチェーンコードをインストールする

ピアノードにチェーンコードをインストールします。

チェーンコードとは、Hyperledger Fabric において、スマートコントラクトを実現させるためのコードになってます。

構成図:ステップ8

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer chaincode install -n $CHAINCODENAME -v $CHAINCODEVERSION -p $CHAINCODEDIR

チェーンコードの内容は、docker イメージ内の$CHAINCODEVERSION(github.com/chaincode_example02/go)になります。

下記の git に格納されているコードと同一です。

fabric/examples/chaincode/go/chaincode_example02/chaincode_example02.go at master · hyperledger-archives/fabric · GitHub

ステップ 9:チェーンコードをインスタンス化する

ファブリックチャネルでチェーンコードをインスタンス化します。

インスタンス化とは、インストールしたチェーンコードのコンパイル、ビルドを行い利用可能な状態にすることです。 初期値が必要な場合は、同時に引数で設定します。

構成図:ステップ9

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer chaincode instantiate -o $ORDERER -C $CHANNEL -n $CHAINCODENAME -v $CHAINCODEVERSION \
    -c '{"Args":["init","a","100","b","200"]}' --cafile $CAFILE --tls

このステートメントには 30 秒ほどかかる場合があります。 特定の成功応答は表示されません。

先ほど紹介した、git のコードを見ると init の処理は下記のようになってます。 a、b という名前のデータに足して、値をそれぞれ 100, 200 セットして初期化する処理です。

func (t *SimpleChaincode) Init(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) {
    var A, B string    // Entities
    var Aval, Bval int // Asset holdings
    var err error

    if len(args) != 4 {
        return nil, errors.New("Incorrect number of arguments. Expecting 4")
    }

    // Initialize the chaincode
    A = args[0]
    Aval, err = strconv.Atoi(args[1])
    if err != nil {
        return nil, errors.New("Expecting integer value for asset holding")
    }
    B = args[2]
    Bval, err = strconv.Atoi(args[3])
    if err != nil {
        return nil, errors.New("Expecting integer value for asset holding")
    }
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // Write the state to the ledger
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return nil, err
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return nil, err
    }

    return nil, nil
}

ステップ 10:チェーンコードをクエリする

ファブリックピアのチェーンコードをクエリします。
現在の台帳データを取得することを指します。

構成図:ステップ10

今回は、a の現在値を取得しましょう。

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer chaincode query -C $CHANNEL -n $CHAINCODENAME -c '{"Args":["query","a"]}'

結果

100

init の処理は下記のようになってます。 stub.GetState を利用して、台帳から a の値を取得して、返却してます。

func (t *SimpleChaincode) Query(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) {
    if function != "query" {
        return nil, errors.New("Invalid query function name. Expecting \"query\"")
    }
    var A string // Entities
    var err error

    if len(args) != 1 {
        return nil, errors.New("Incorrect number of arguments. Expecting name of the person to query")
    }

    A = args[0]

    // Get the state from the ledger
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
        return nil, errors.New(jsonResp)
    }

    if Avalbytes == nil {
        jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
        return nil, errors.New(jsonResp)
    }

    jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
    fmt.Printf("Query Response:%s\n", jsonResp)
    return Avalbytes, nil
}

ステップ 11:トランザクションを呼び出す

チェーンコードを利用してトランザクションを呼び出します。
現在の台帳データを変更することを指します。

構成図:ステップ11

今回は、a -> b に 10 だけ値を移譲させます。

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer chaincode invoke -o $ORDERER -C $CHANNEL -n $CHAINCODENAME \
    -c '{"Args":["invoke","a","b","10"]}' --cafile $CAFILE --tls

結果

2023-11-24 07:29:34.177 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

invoke の処理は下記のようになってます。 この引数で、a の値を-10, b の値を+10 する処理となります。

func (t *SimpleChaincode) Invoke(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) {
    if function == "delete" {
        // Deletes an entity from its state
        return t.delete(stub, args)
    }

    var A, B string    // Entities
    var Aval, Bval int // Asset holdings
    var X int          // Transaction value
    var err error

    if len(args) != 3 {
        return nil, errors.New("Incorrect number of arguments. Expecting 3")
    }

    A = args[0]
    B = args[1]

    // Get the state from the ledger
    // TODO: will be nice to have a GetAllState call to ledger
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        return nil, errors.New("Failed to get state")
    }
    if Avalbytes == nil {
        return nil, errors.New("Entity not found")
    }
    Aval, _ = strconv.Atoi(string(Avalbytes))

    Bvalbytes, err := stub.GetState(B)
    if err != nil {
        return nil, errors.New("Failed to get state")
    }
    if Bvalbytes == nil {
        return nil, errors.New("Entity not found")
    }
    Bval, _ = strconv.Atoi(string(Bvalbytes))

    // Perform the execution
    X, err = strconv.Atoi(args[2])
    Aval = Aval - X
    Bval = Bval + X
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // Write the state back to the ledger
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return nil, err
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return nil, err
    }

    return nil, nil
}

ステップ 12:チェーンコードを再度クエリし、値の変化を確認する

ファブリックピアのチェーンコードを再度クエリします。
ステップ 11 で、a, b 間で 10 の移動があったので、その変化が見えるはずです。

docker exec -e "CORE_PEER_TLS_ENABLED=true" -e "CORE_PEER_TLS_ROOTCERT_FILE=/opt/home/managedblockchain-tls-chain.pem" \
    -e "CORE_PEER_ADDRESS=$PEER" -e "CORE_PEER_LOCALMSPID=$MSP" -e "CORE_PEER_MSPCONFIGPATH=$MSP_PATH" \
    cli peer chaincode query -C $CHANNEL -n $CHAINCODENAME -c '{"Args":["query","a"]}'
90

a の値は、無事 10 減ってました。

さいごに

この記事では、AMBを利用した、Hyperledger Fabric ブロックチェーン ネットワークの構築について解説しました。 コマンドを叩くだけのサンプルが、少しでも理解しやすくなっていたのならば幸いです。

今後も、以降のサンプルパートについて、さらに詳しく解説していきますので、ぜひご期待ください。

本ブログがどなかたのお役に立てれば幸いです。

山本 真大(執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス1課

2023年8月入社。カピバラさんが好き。