AWS LambdaとHyperledger Fabric SDK for Node.jsを利用してAmazon Managed Blockchainのブロックチェーンネットワークにアクセスする

Amazon Managed BlockchainのブロックチェーンネットワークはVPC内に構築されるため、VPC外からブロックチェーンネットワークへアクセスするにはクライアントとなるアプリなりサービスを開発して経由する必要があります。
AWS LambdaでVPC内に関数を配置するとアクセス可能になるはずなので試してみました。

Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html


## 前提

Dockerを利用して開発環境を構築します。

AWS Lambdaへのデプロイにはserverlessを利用します。

Serverless - The Serverless Application Framework powered by AWS Lambda, API Gateway, and more
https://serverless.com/


AWS Lambdaを利用するのでAWSアカウントや権限も必要となります。


```console
> docker --version
Docker version 18.09.2, build 6247962

> docker-compose --version
docker-compose version 1.23.2, build 1110ad01

> sls --version
1.43.0
```


### Amazon Managed Blockchainでブロックチェーンネットワークが構築済み

下記の2記事の手順でブロックチェーンネットワークが構築済みでfabcarのサンプルが動作する環境がある前提です。

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878


## 開発環境を構築する

Hyperledger FabricのSDKをAWS Lambda上で利用するにはLinuxで`npm install` する必要があったのでDockerを利用して開発環境を構築します。


### Dockerコンテナの立ち上げ

```console
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> touch Dockerfile
> touch docker-compose.yml
```

AWS Lambdaで利用できるNode.jsのバージョンは`8.10` と`10.x` となります。
Hyperledger Fabric SDK for Node.jsは8.x系で動作するのでDockerにも`8.x` をインストールします。

AWS Lambda ランタイム - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html


```txt:Dockerfile
FROM amazonlinux

RUN yum update && \
    curl -sL https://rpm.nodesource.com/setup_8.x | bash - && \
    yum install -y gcc-c++ make nodejs && \
    npm i -g serverless
```

```yaml:docker-compose.yml
version: '3'
services:
  app:
    build: .
    volumes:
      - ./:/src
    working_dir: /src
    tty: true
```

```console
> docker-compose build

> docker-compose run app bash
```

### Node.jsのプロジェクト作成

コンテナが立ち上がったらserverlessでNode.jsのテンプレートでプロジェクトを作成します。

```console:コンテナ内
$ sls create \
  --template aws-nodejs \
  --path fablic-app

$ cd fablic-app
$ npm init
```

Hyperledger Fabric SDK for Node.jsが利用できるように`package.json` を編集して`npm install` を実行します。


```json:package.json
{
    "name": "fabcar",
    "version": "1.0.0",
    "description": "Hyperledger Fabric Car Sample Application",
    "main": "fabcar.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "dependencies": {
        "fabric-ca-client": "~1.2.0",
        "fabric-client": "~1.2.0",
        "fs-extra": "^8.0.1",
        "grpc": "^1.6.0"
    },
    "author": "",
    "license": "Apache-2.0",
    "keywords": [
    ]
}
```

```console:コンテナ内
$ npm install
```


### 証明書の用意

Hyperledger Fabric SDK for Node.jsでブロックチェーンネットワークへアクセスするのに各種証明書が必要となるためプロジェクトに含めます。こちらはDockerコンテナ外で行います。

```console:コンテナ外
> cd fablic-app
> aws s3 cp s3://us-east-1.managedblockchain-preview/etc/managedblockchain-tls-chain.pem ./managedblockchain-tls-chain.pem

# EC2インスタンスからhfc-key-storeフォルダを取得
> scp -r -i [EC2インスタンス用のpemファイルパス] ec2-user@xxx.xxx.xxx.xxx:/home/ec2-user/fabric-samples/fabcar/hfc-key-store ./hfc-key-store
```


### 実装

今回はブロックチェーンネットワークのステートDBから情報を取得する実装を行います。
下記記事でも利用している`query.js` をAWS Lambdaで実行できるように編集しました。

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878

`handler.js` は`query.js` のメソッドを呼び出し結果を返す実装にしました。

```console
> cd fabric-app
> tree -F -L 1 .
.
├── handler.js
├── hfc-key-store/
├── managedblockchain-tls-chain.pem
├── node_modules/
├── package-lock.json
├── package.json
├── query.js
└── serverless.yml
```

```js:handler.js
var query = require('./query');

module.exports.hello = async (event) => {
  var result = await query.run();
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: JSON.parse(result),
      input: event,
    }, null, 2),
  };
};
```

実装のポイントは下記となります。

- 証明書フォルダ`hfc-key-store` を`/tmp` にコピーして利用
- `module.exports.run = async () => {}` で`handler.js` から呼び出し可能にする
- `async/await` で同期的に実行する


```js:query.js
'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
 * Chaincode query
 */

var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');
var fs = require('fs-extra');

//
var fabric_client = new Fabric_Client();

// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpcs://nd-xxxxxxxxxxxxxxxxxxxxxxxxxx.m-xxxxxxxxxxxxxxxxxxxxxxxxxx.n-xxxxxxxxxxxxxxxxxxxxxxxxxx.managedblockchain.us-east-1.amazonaws.com:30003',
	{ pem: fs.readFileSync('./managedblockchain-tls-chain.pem').toString(), 'ssl-target-name-override': null});
channel.addPeer(peer);

//
var member_user = null;
var store_base_path = path.join(__dirname, 'hfc-key-store');
var store_path = path.join('/tmp', 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
module.exports.run = async () => {
	// 証明書ファイルを/tmp ディレクトリにコピーして利用する
	fs.copySync(store_base_path, store_path);
	console.log('Store copied!');

	return await Fabric_Client.newDefaultKeyValueStore({ path: store_path
	}).then((state_store) => {
		// assign the store to the fabric client
		fabric_client.setStateStore(state_store);
		var crypto_suite = Fabric_Client.newCryptoSuite();
		// use the same location for the state store (where the users' certificate are kept)
		// and the crypto store (where the users' keys are kept)
		var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
		crypto_suite.setCryptoKeyStore(crypto_store);
		fabric_client.setCryptoSuite(crypto_suite);

		// get the enrolled user from persistence, this user will sign all requests
		return fabric_client.getUserContext('user1', true);
	}).then((user_from_store) => {
		if (user_from_store && user_from_store.isEnrolled()) {
			console.log('Successfully loaded user1 from persistence');
			member_user = user_from_store;
		} else {
			throw new Error('Failed to get user1.... run registerUser.js');
		}

		// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
		// queryAllCars chaincode function - requires no arguments , ex: args: [''],
		const request = {
			//targets : --- letting this default to the peers assigned to the channel
			chaincodeId: 'fabcar',
			fcn: 'queryAllCars',
			args: ['']
		};

		// send the query proposal to the peer
		return channel.queryByChaincode(request);
	}).then((query_responses) => {
		console.log("Query has completed, checking results");
		// query_responses could have more than one  results if there multiple peers were used as targets
		if (query_responses && query_responses.length == 1) {
			if (query_responses[0] instanceof Error) {
				console.error("error from query = ", query_responses[0]);
			} else {
				console.log("Response is ", query_responses[0].toString());
				return query_responses[0].toString();
			}
		} else {
			console.log("No payloads were returned from query");
		}
	}).catch((err) => {
		console.error('Failed to query successfully :: ' + err);
	});
};
```

### serverlessの設定

AWS LambdaでVPC内配置されるように`serverless.yml` を編集します。
`vpc` や`iamRoleStatements` の定義については下記が参考になりました。
セキュリティグループとサブネットはAmazon Managed Blockchainで構築したブロックチェーンネットワークと同じものを指定します。

ServerlessでLambdaをVPC内にデプロイする - Qiita
https://qiita.com/70_10/items/ae22a7a9bca62c273495

```yaml:serverless.yml
service: fabric-app

provider:
  name: aws
  runtime: nodejs8.10

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "ec2:CreateNetworkInterface"
        - "ec2:DescribeNetworkInterfaces"
        - "ec2:DeleteNetworkInterface"
      Resource:
        - "*"

  vpc:
    securityGroupIds:
      - sg-xxxxxxxxxxxxxxxxx
    subnetIds:
      - subnet-xxxxxxxx
      - subnet-yyyyyyyy

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
```


### AWS Lambdaにデプロイする

```console
> sls deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service fabric-app.zip file to S3 (40.12 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: fabric-app
stage: dev
region: us-east-1
stack: fabric-app-dev
resources: 10
api keys:
  None
endpoints:
  GET - https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello
functions:
  hello: fabric-app-dev-hello
layers:
  None
Serverless: Removing old service artifacts from S3...
```

デプロイができたらエンドポイントにアクセスしてみます。

```console
> curl https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/dev/hello

{
  "message": [
    {
      "Key": "CAR0",
      "Record": {
        "make": "Toyota",
        "model": "Prius",
        "colour": "blue",
        "owner": "Tomoko"
      }
    },
    {
      "Key": "CAR1",
      "Record": {
        "make": "Ford",
        "model": "Mustang",
        "colour": "red",
        "owner": "Brad"
      }
    },
    (略)
  ],
  "input": {
    "resource": "/hello",
    "path": "/hello",
    "httpMethod": "GET",
    (略)
  }
}
```

はい。
無事にAWS Lambda関数からHyperledger Fabric SDK for Node.jsを利用してブロックチェーンネットワークにアクセスすることができました。

VPC内にLamnbda関数を配置する必要があるため、ENI（仮想ネットワークインターフェース）の利用に伴う制限や起動速度に課題が発生するかもしれませんので、実際に利用する際には負荷検証などしっかりと行う必要がありそうです。

AWS LambdaをVPC内に配置する際の注意点 | そるでぶろぐ
https://devlog.arksystems.co.jp/2018/04/04/4807/



## 参考

Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/vpc.html

Serverless - The Serverless Application Framework powered by AWS Lambda, API Gateway, and more
https://serverless.com/

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita
https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

Amazon Managed Blockchainで作成したブロックチェーンネットワークにHyperledger Fabric SDK for Node.jsでアクセスしてみる - Qiita
https://qiita.com/kai_kou/items/5a6b0fc148f04857a878

AWS Lambda ランタイム - AWS Lambda
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html

ServerlessでLambdaをVPC内にデプロイする - Qiita
https://qiita.com/70_10/items/ae22a7a9bca62c273495

AWS LambdaをVPC内に配置する際の注意点 | そるでぶろぐ
https://devlog.arksystems.co.jp/2018/04/04/4807/