Node.js
Blockchain
AWSLambda
Hyperledger-fabric
managedBlockchain

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アカウントや権限も必要となります。

> 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コンテナの立ち上げ

> mkdir 任意のディレクトリ

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

AWS Lambdaで利用できるNode.jsのバージョンは8.1010.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


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



docker-compose.yml

version: '3'

services:
app:
build: .
volumes:
- ./:/src
working_dir: /src
tty: true

> docker-compose build


> docker-compose run app bash


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

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


コンテナ内

$ sls create \

--template aws-nodejs \
--path fablic-app

$ cd fablic-app
$ npm init

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


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": [
]
}


コンテナ内

$ npm install



証明書の用意

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


コンテナ外

> 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.jsquery.js のメソッドを呼び出し結果を返す実装にしました。

> 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


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 で同期的に実行する


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 を編集します。

vpciamRoleStatements の定義については下記が参考になりました。

セキュリティグループとサブネットはAmazon Managed Blockchainで構築したブロックチェーンネットワークと同じものを指定します。

ServerlessでLambdaをVPC内にデプロイする - Qiita

https://qiita.com/70_10/items/ae22a7a9bca62c273495


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にデプロイする

> 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...

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

> 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/