Go
docker
Blockchain
DockerSwarm
Hyperledger-fabric

OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(1/3)〜 動作確認編

この記事は、OSSの分散型台帳技術であるHyperledger/fabricを用いて簡易な入出金処理を作り、docker swarm modeで分散処理させてみる、という連載の1回目です。

今回は、単体のdocker上にHyperledger/fabricネットワークを立ち上げて動作を確認してみます。

  1. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(1/3)〜 動作確認編
  2. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(2/3)〜 chaincode解説編
  3. OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう(3/3)〜 分散環境解説編

Hyperledger/fabricとは

Hyperleder/fabricは、IBMが開発しThe Linux Foundationに寄贈した、OSSの分散型台帳技術のひとつです。ビジネス用途が意識されていて、IBM Cloud上でマネージドBlockchainサービスが展開されていたり、ジャパンネット銀行とテックビューロがHyperledger/fabricとmijinを連携した実証実験を開始したりと、最近色々盛り上がっているような気がします。

アーキテクチャ概観

Hyperledger/fabric 1.1を用いた今回のサンプルアプリは、ざっくり次のようなアーキテクチャで動作しています。Hyperledger/fabric 1.1のアーキテクチャ詳細は、公式ドキュメントArchitecture Explainedを参照してください。

architecture.png

コンテナ 役割
REST API REST APIのエンドポイントを提供する。SDKを通じてpeerと接続し、台帳の最新状態の取得や更新依頼を行う。
認証局(ca) REST APIのSDKがHyperledger/fabricネットワークに接続する際の認証を行う。
Peer(Endorser) 全ての変更履歴が記録された台帳(Ledger)を持つ。SDKを通じて台帳更新の依頼(Porposal)を受信した際には、そのトランザクションが実行できるかのエミュレートを行って結果をSDKに通知する。ordererからトランザクションのコミットを指示された際には、そのトランザクションが矛盾なく記帳できるか(トランザクションが想定している変更前データが既に書き換えられていないか、等)の検証を行い、台帳を更新する。
couchdb 台帳の最新状態(state)を保持する。couchdbが無くとも組み込みのLevelDBで動作するが、高度な検索機能を持つcouchdbを用いた方がchaincodeは記述しやすい(と思う)。
chaincode 台帳の検索や更新に関わるビジネスロジック(chaincode)を実行する。
orderer トランザクションの順序を整列する。分散環境下では、zookeeperとkafakaを用いることで、複数のorderer間で一意な順序を保証する。

chaincode: 業界ではsmart contractと言われるものですが、Hyperledger/fabricはchaincodeと呼びます。

Hyperledger/fabric 1.1のアーキテクチャの注意点

Hyperledger/fabric 1.1のpeerコンテナは、インストールされたchaincodeを初期化する際に、chaincodeを実行するコンテナを動的に起動してdocker networkに接続します。これはchaincodeの暴走がpeer自身の処理に影響を及ぼさないためらしいのですが、そのために/var/run/docker.sock をコンテナにmountしCORE_VM_ENDPOINTという環境変数にdocker.socketへのパスを定義しています。

docker-compose.yaml
...
  peer:
    container_name: peer
    hostname: peer
    image: hyperledger/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/
      - CORE_PEER_ADDRESS=peer:7051
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric-sample-nw
      - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb:5984
      - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=
      - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=
    working_dir: /etc/hyperledger
    command: peer node start
    ports:
      - 7051
      - 7053
    volumes:
      - /var/run/docker.sock:/host/var/run/docker.sock
      - ./artifacts/:/etc/hyperledger/artifacts
      - ./crypto-config/:/etc/hyperledger/crypto-config/
    networks:
      - fabric-sample-nw
    depends_on:
      - orderer
      - couchdb
...

実はdocker.sockをコンテナにマウントするのは、危険な行為です。docker.sockへのアクセス権があるpeerコンテナが乗っ取られた場合、それはホストOSのrootを奪取されたのと同等の脅威になります(Don't expose the Docker socket (not even to a container))。Hyperledger/fabricのネットワークを構成するコンテナは直接外部に晒さず、REST APIを提供するコンテナのみポートを公開するなどの対策が必要になるでしょう。

単体のdockerで動作確認してみよう

それでは、単一node上のdockerで最小構成のHyperledger/fabricネットワークを起動し、動作確認してみましょう。以下のようなコンテナ群でHyperledger/fabricネットワークを構成します。

solo.png

検証した環境

Host version
OS macOS Siera 10.12.6
VirtualBox 5.2.8 r121009
vagrant 2.0.2
Guest version
OS Ubuntu 16.04.4 LTS 4.4.0-116-generic
golang go1.10 linux/amd64
docker 17.12.1-ce, build 7390fc6
docker-compose 1.19.0, build 9e633ef
  • Hyperledger/fabricのdocker imageはそれなりに巨大なので、GuestOSの仮想ディスクは15~20GB程度は確保しておいたほうが良いと思われます。私は10GBの仮想ディスクで始めて、ディスクが足りなくなって諸々止まりました・・・

環境を準備する

  1. 検証した環境のバージョンに従って、dockerとgoをインストールします。

ソースコードや設定ファイル等を取得する

  1. Hyperledger/fabricやdockerの設定ファイルが入っているリポジトリをgithubからcloneする

    ubuntu@node0:~$ git clone https://github.com/nmatsui/fabric-payment-sample-docker.git
    
  2. (1.と同じディレクトリで)サンプル入出金処理のREST APIを提供するnodejsアプリをgithubからcloneする

    ubuntu@node0:~$ git clone https://github.com/nmatsui/fabric-payment-sample-api.git
    
  3. サンプル入出金処理のchaincodeを取得する

    ubuntu@node0:~$ go get -u -d github.com/nmatsui/fabric-payment-sample-chaincode
    
    • この段階ではローカルでchaincodeのビルドができないため、-dフラグを付けて自動ビルドを無効にしています。

REST API用コンテナイメージを作成する

  1. nodejsアプリのディレクトリに移動

    ubuntu@node0:~$ cd fabric-payment-sample-api/
    
  2. REST APIアプリのBearer tokenを生成し、Docker imageを生成

    ubuntu@node0:~/fabric-payment-sample-api$ ./generate_token.sh
    ubuntu@node0:~/fabric-payment-sample-api$ docker build -t fabric-payment/api .
    

Hyperledger/fabricのネットワークを起動する

  1. docker単体起動用のディレクトリに移動する

    ubuntu@node0:~/fabric-payment-sample-api$ cd ../fabric-payment-sample-docker/dev-solo/
    
  2. Hyperledger/fabric 1.1.0-rc1のバイナリとdocker imageを取得する

    • この記事を執筆している時点では1.1.0-rc1が最新ですが、もうすぐにでも1.1.0が出るハズ・・・
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -sSL https://goo.gl/6wtTN5 | bash -s 1.1.0-rc1
    
  3. 環境変数を設定した後、Hyperledger/fabricが必要とする暗号鍵と、fabricネットワークの設定等が記載されているアーティファクトを生成する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ source .env
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ export CA_ADMIN_PASSWORD=<<任意の文字列>>
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ ./generate_artifact.sh ${CA_ADMIN_PASSWORD}
    
  4. Hyperledger/fabricネットワークを立ち上げる

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ ./start.sh
    
    • dockerコンテナを立ち上げるだけでなく、チャネルの生成やコンテナのチャネルへのjoin、chaincodeのインストールと初期化など、Hyperledger/fabricを利用可能にするための様々な設定も行います。

入出金APIを試してみる

それでは、chaincodeを利用するREST APIを試してみましょう。REST APIは、Hyperledger/fabricのnode SDKを利用したnodejs8+express4アプリです。今回はREST API実装の詳細は解説しませんので、処理内容はリポジトリを参照してください。

現時点のnode sdkは、9系はサポートされていないようです。
http://hyperledger-fabric.readthedocs.io/en/release-1.1/prereqs.html#node-js-runtime-and-npm

  • 注意
    • REST APIには、nodejs側で認証がかけられています。利用する際には、6.で作成したTokenでBearer認証ヘッダを付与してください。
  1. 口座一覧を確認する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ TOKEN=$(cat ${API_PATH}/api/.config/token.json | jq .token -r)
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/accounts/ | jq .
    []
    
    • apiコンテナのログを表示しておくと、REST APIが呼び出された際にログが表示されます。
    apiコンテナのログ
    > fabric-payment-sample-api@0.0.1 start /etc/hyperledger
    > node ./api/bin/www
    
    Successfully loaded fabricuser from persistence
    Query has completed, checking results
    response from fabric query = []
    GET /fabric-payment/accounts/ 200 385.646 ms - 2
    
    • まだ口座を作っていませんので、当然ながら空の配列が返されます。
  2. 口座を二つ開設する

    • 口座1
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/accounts/ -X POST -d '{"name":"口座1"}' | jq .
    {
      "model_type": "account",
      "no": "3178565812566792",
      "name": "口座1",
      "balance": 0
    }
    
    • apiコンテナのログには、Transactionのproposalが成功し、ordererにトランザクションのコミットを依頼してpeerがトランザクションをコミットしたというログが出力されています。
    apiコンテナのログ
    Transaction proposal was good
    Successfully sent Proposal and received ProposalResponse: Status - 200, message - OK
    info: [EventHub.js]: _connect - options {}
    The transaction has been committed on peer peer:7053
    Send transaction promise and event listener promise have completed
    Successfully sent transaction to the orderer.
    Successfully committed the change to the ledger by the peer
    POST /fabric-payment/accounts/ 200 2138.768 ms - 79
    
    • 口座2
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/accounts/ -X POST -d '{"name":"口座2"}' | jq .
    {
      "model_type": "account",
      "no": "5471085957800648",
      "name": "口座2",
      "balance": 0
    }
    
    apiコンテナのログ
    Transaction proposal was good
    Successfully sent Proposal and received ProposalResponse: Status - 200, message - OK
    info: [EventHub.js]: _connect - options {}
    The transaction has been committed on peer peer:7053
    Send transaction promise and event listener promise have completed
    Successfully sent transaction to the orderer.
    Successfully committed the change to the ledger by the peer
    POST /fabric-payment/accounts/ 200 2096.217 ms - 79
    
    • ordererのログも表示しておくと、台帳の状態が変更された場合にHyperledter/fabricがナニカとても頑張ってることが目に見えて少し楽しいです。
  3. 口座一覧を確認する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/accounts/ | jq .
    [
      {
        "model_type": "account",
        "no": "3178565812566792",
        "name": "口座1",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "5471085957800648",
        "name": "口座2",
        "balance": 0
      }
    ]
    
    • 登録した二つの口座が取得されます。
  4. 口座1に1000入金する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/events/deposit/ -X POST -d '{"to_account_no":"3178565812566792", "amount":1000}' | jq .
    {
      "model_type": "event",
      "event_type": "deposit",
      "no": "yLO0QK0rnxGg1m24",
      "amount": 1000,
      "from_account": null,
      "to_account": {
        "no": "3178565812566792",
        "name": "口座1",
        "previous_balance": 0,
        "current_balance": 1000
      }
    }
    
    • 口座1の残高が、0から1000に増えました。
  5. 口座1から口座2へ250送金する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/events/remit/ -X POST -d '{"from_account_no":"3178565812566792", "to_account_no":"5471085957800648", "amount":250}' | jq .
    {
      "model_type": "event",
      "event_type": "remit",
      "no": "bMr89bmEOIyDIi7i",
      "amount": 250,
      "from_account": {
        "no": "3178565812566792",
        "name": "口座1",
        "previous_balance": 1000,
        "current_balance": 750
      },
      "to_account": {
        "no": "5471085957800648",
        "name": "口座2",
        "previous_balance": 0,
        "current_balance": 250
      }
    }
    
    • 口座1の残高が1000から750に減り、口座2の残高が0から250に増えています。
  6. 口座1から310出金する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/events/withdraw/ -X POST -d '{"from_account_no":"3178565812566792", "amount":310}' | jq .
    {
      "model_type": "event",
      "event_type": "withdraw",
      "no": "17PpwISNTaNdJ8Yo",
      "amount": 310,
      "from_account": {
        "no": "3178565812566792",
        "name": "口座1",
        "previous_balance": 750,
        "current_balance": 440
      },
      "to_account": null
    }
    
    • 口座1の残高が750から440に減っています。
  7. 口座1の変更履歴を確認する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -H "Content-Type: application/json" -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/fabric-payment/accounts/3178565812566792/histories/ | jq .
    [
      {
        "tx_id": "7c94e0f7f0c12b5410eb9d6f49e5b68a7e1b1c7b61472f59892aee1df1b5208e",
        "no": "3178565812566792",
        "state": {
          "balance": 0,
          "model_type": "account",
          "name": "口座1",
          "no": "3178565812566792"
        },
        "timestamp": "2018-03-04 07:44:43.008 +0000 UTC",
        "is_delete": false
      },
      {
        "tx_id": "e4177720f5fc988f01455ea330969409999942caf4710ac9ae3bb9511714234a",
        "no": "3178565812566792",
        "state": {
          "balance": 1000,
          "model_type": "account",
          "name": "口座1",
          "no": "3178565812566792"
        },
        "timestamp": "2018-03-04 07:58:00.476 +0000 UTC",
        "is_delete": false
      },
      {
        "tx_id": "910b05e7884e67af75d07d6e59152bd7905aebeef3333a38745d32b1b24d062b",
        "no": "3178565812566792",
        "state": {
          "balance": 750,
          "model_type": "account",
          "name": "口座1",
          "no": "3178565812566792"
        },
        "timestamp": "2018-03-04 08:00:34.905 +0000 UTC",
        "is_delete": false
      },
      {
        "tx_id": "9c22f2c7221d54b8f4a0bdd59063c243f96d3f0ab321291a79e33e843581a515",
        "no": "3178565812566792",
        "state": {
          "balance": 440,
          "model_type": "account",
          "name": "口座1",
          "no": "3178565812566792"
        },
        "timestamp": "2018-03-04 08:03:20.327 +0000 UTC",
        "is_delete": false
      }
    ]
    
    • 残高が 0 -[入金]-> 1000 -[送金]-> 750 -[出金]-> 440 と更新されていった履歴を確認できます。
    • 今回のサンプルアプリには、例示したもの以外にもREST APIが実装されていますので、ぜひ試してみてください。
    機能 method endpoint
    口座一覧取得 GET /fabric-payment/accounts/
    口座新規作成 POST /fabric-payment/accounts/
    口座情報取得 GET /fabric-payment/accounts/<<口座番号>>/
    口座情報更新 PUT /fabric-payment/accounts/<<口座番号>>/
    口座削除 DELETE /fabric-payment/accounts/<<口座番号>>/
    入金 POST /fabric-payment/events/deposit/
    送金 POST /fabric-payment/events/remit/
    出金 POST /fabric-payment/events/withdraw/
    入金・送金・出金一覧取得 GET /fabric-payment/events/
    入金一覧取得 GET /fabric-payment/events/deposit/
    送金一覧取得 GET /fabric-payment/events/remit/
    出金一覧取得 GET /fabric-payment/events/withdraw/
    口座の履歴 GET /fabric-payment/accounts/<<口座番号>>/histories/
    イベントの履歴 GET /fabric-payment/events/<<イベント番号>>/histories/

Hyperledger/fabricのネットワークを終了する

  1. Hyperledger/fabricネットワークを終了する

    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ ./terminate.sh
    

次回は

次回は、Hyperledger/fabricのchaincodeを開発するための環境構築手順と、今回用いている入金・送金・出金サンプルアプリのchaincodeの内容を解説します。