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.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
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
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
を実行します。
{
"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.js
はquery.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
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
で同期的に実行する
'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
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/