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

この記事は、OSSの分散型台帳技術である[Hyperledger/fabric](http://hyperledger-fabric.readthedocs.io/en/latest/)を用いて簡易な入出金処理を作り、[docker swarm mode](https://docs.docker.com/engine/swarm/)で分散処理させてみる、という連載の１回目です。

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

1. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（1/3）〜 動作確認編](https://qiita.com/nmatsui/items/cbcb7807984c65694e46)
2. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（2/3）〜 chaincode解説編](https://qiita.com/nmatsui/items/9689982e799ccb1255ed)
3. [OSSの分散型台帳 Hyperledger/fabric をdocker swarmで分散させてみよう（3/3）〜 分散環境解説編](https://qiita.com/nmatsui/items/7eb4e214b628f96814a0)

# Hyperledger/fabricとは
[Hyperleder/fabric](http://hyperledger-fabric.readthedocs.io/en/release-1.1/)は、IBMが開発しThe Linux Foundationに寄贈した、OSSの分散型台帳技術のひとつです。ビジネス用途が意識されていて、IBM Cloud上で[マネージドBlockchainサービスが展開](https://www-03.ibm.com/press/jp/ja/pressrelease/51912.wss)されていたり、ジャパンネット銀行とテックビューロが[Hyperledger/fabricとmijinを連携した実証実験](https://prtimes.jp/main/html/rd/p/000000082.000012906.html)を開始したりと、最近色々盛り上がっているような気がします。

## アーキテクチャ概観
Hyperledger/fabric 1.1を用いた今回のサンプルアプリは、ざっくり次のようなアーキテクチャで動作しています。Hyperledger/fabric 1.1のアーキテクチャ詳細は、公式ドキュメント[Architecture Explained](http://hyperledger-fabric.readthedocs.io/en/release-1.1/arch-deep-dive.html)を参照してください。

![architecture.png](https://qiita-image-store.s3.amazonaws.com/0/37648/8cd523db-ac62-6375-3c02-7073989b5231.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自身の処理に影響を及ぼさないためらしい](https://stackoverflow.com/questions/45728688/what-is-chaincode-container-in-hyperledger-fabric)のですが、そのために`/var/run/docker.sock` をコンテナにmountし`CORE_VM_ENDPOINT`という環境変数にdocker.socketへのパスを定義しています。

```yaml: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)](https://www.lvh.io/posts/dont-expose-the-docker-socket-not-even-to-a-container.html)）。Hyperledger/fabricのネットワークを構成するコンテナは直接外部に晒さず、REST APIを提供するコンテナのみポートを公開するなどの対策が必要になるでしょう。

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

![solo.png](https://qiita-image-store.s3.amazonaws.com/0/37648/ffb61d24-4dbd-5427-ad49-f89d4b40648b.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する

    ```bash
    ubuntu@node0:~$ git clone https://github.com/nmatsui/fabric-payment-sample-docker.git
    ```

1. （1.と同じディレクトリで）サンプル入出金処理のREST APIを提供するnodejsアプリをgithubからcloneする

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

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

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

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

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

    ```bash
    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単体起動用のディレクトリに移動する

    ````bash
    ubuntu@node0:~/fabric-payment-sample-api$ cd ../fabric-payment-sample-docker/dev-solo/
    ````
1. Hyperledger/fabric 1.1.0-rc1のバイナリとdocker imageを取得する
    * この記事を執筆している時点では1.1.0-rc1が最新ですが、もうすぐにでも1.1.0が出るハズ・・・

    ````bash
    ubuntu@node0:~/fabric-payment-sample-docker/dev-solo$ curl -sSL https://goo.gl/6wtTN5 | bash -s 1.1.0-rc1
    ````

1. 環境変数を設定した後、Hyperledger/fabricが必要とする暗号鍵と、fabricネットワークの設定等が記載されているアーティファクトを生成する

    ````bash
    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}
    ```
1. Hyperledger/fabricネットワークを立ち上げる

    ```bash
    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](https://github.com/hyperledger/fabric-sdk-node)を利用したnodejs8+express4アプリです。今回はREST API実装の詳細は解説しませんので、処理内容は[リポジトリ](https://github.com/nmatsui/fabric-payment-sample-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. 口座一覧を確認する

    ```bash
    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が呼び出された際にログが表示されます。
    
    ```bash: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
    ```
    * まだ口座を作っていませんので、当然ながら空の配列が返されます。

1. 口座を二つ開設する
    * 口座１

    ```bash
    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":"口座１"}' | jq .
    {
      "model_type": "account",
      "no": "3178565812566792",
      "name": "口座１",
      "balance": 0
    }
    ```
    * apiコンテナのログには、Transactionのproposalが成功し、ordererにトランザクションのコミットを依頼してpeerがトランザクションをコミットしたというログが出力されています。

    ```bash: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
    ```
    * 口座２

    ```bash
    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":"口座２"}' | jq .
    {
      "model_type": "account",
      "no": "5471085957800648",
      "name": "口座２",
      "balance": 0
    }
    ````
    ```bash: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がナニカとても頑張ってることが目に見えて少し楽しいです。

1. 口座一覧を確認する

    ```bash
    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": "口座１",
        "balance": 0
      },
      {
        "model_type": "account",
        "no": "5471085957800648",
        "name": "口座２",
        "balance": 0
      }
    ]
    ```
    * 登録した二つの口座が取得されます。


1. 口座１に1000入金する

    ```bash
    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": "口座１",
        "previous_balance": 0,
        "current_balance": 1000
      }
    }
    ```
    * 口座１の残高が、0から1000に増えました。

1. 口座１から口座２へ250送金する

    ```bash
    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": "口座１",
        "previous_balance": 1000,
        "current_balance": 750
      },
      "to_account": {
        "no": "5471085957800648",
        "name": "口座２",
        "previous_balance": 0,
        "current_balance": 250
      }
    }
    ```
    * 口座１の残高が1000から750に減り、口座２の残高が0から250に増えています。

1. 口座１から310出金する

    ```bash
    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": "口座１",
        "previous_balance": 750,
        "current_balance": 440
      },
      "to_account": null
    }
    ```
    * 口座１の残高が750から440に減っています。

1. 口座１の変更履歴を確認する

    ```bash
    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": "口座１",
          "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": "口座１",
          "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": "口座１",
          "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": "口座１",
          "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ネットワークを終了する

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



# 次回は

[次回](https://qiita.com/nmatsui/items/9689982e799ccb1255ed)は、Hyperledger/fabricのchaincodeを開発するための環境構築手順と、今回用いている入金・送金・出金サンプルアプリのchaincodeの内容を解説します。