Posted at

Hyperledger Fabricのチェーンコード(Node.js)をTypeScriptで実装できるようにしてみた

Hyperledger Fabricのチェーンコードを実装するにはGo、Node.js、Javaが利用できますが、Node.jsを利用するならTypeScriptで実装したい!ので、環境を作ってみました。

Hyperledger Fabric 入門, 第 5 回: チェーンコードの書き方

https://www.ibm.com/developerworks/jp/cloud/library/cl-hyperledger-fabric-basic-5/index.html


調べてみる

やってる人いないかなぁと調べてみたらおられました!

自前で型定義されてます。oh...

Hyperledger FabricのChaincodeをNode.jsでかく(TypeScript) - Qiita

https://qiita.com/Huruikagi/items/0321460f467847e7438e

少し古い記事だったので、状況が変わってないかなとチェーンコードのリポジトリを見てみたら変わってました!

hyperledger/fabric-chaincode-node: Read-only mirror of

https://gerrit.hyperledger.org/r/#/admin/projects/fabric-chaincode-node https://github.com/hyperledger/fabric-chaincode-node

v1.3.0でTypeScript対応されてました!

fabric-chaincode-node/v1.3.0.txt at release-1.4 · hyperledger/fabric-chaincode-node

https://github.com/hyperledger/fabric-chaincode-node/blob/release-1.4/release_notes/v1.3.0.txt


Typescript definitions are now included.



環境構築してみる

下記を参考にしてTypeScriptの実装をJavaScriptにビルドできるようにします。

TypeScript + Node.js プロジェクトのはじめかた2019 - Qiita

https://qiita.com/notakaos/items/3bbd2293e2ff286d9f49

# fabric-shimがNode.js v9.0.0以下でしか利用できないので

> node -v
v8.10.0

> npm -v
5.6.0

> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> mkdir src
> mkdir dist
> touch package.json

fabric-chaincode-nodeリポジトリのREADME.mdを参考にしてpackage.json を用意します。

fabric-shim のバージョンを~1.4.1 としています。


package.json

{

"name": "fabric-chaincode",
"version": "1.0.0",
"description": "My first exciting chaincode implemented in node.js",
"engines": {
"node": ">=8.4.0",
"npm": ">=5.3.0"
},
"scripts": {
"start": "node chaincode.js"
},
"engine-strict": true,
"license": "Apache-2.0",
"dependencies": {
"fabric-shim": "~1.4.1"
}
}


必要なライブラリをインストールする

> npm install


[grpc] Success: "/Users/kai/dev/livelock/livelock-blockchain/use-typescript-with-chaincode/node_modules/grpc/src/node/extension_binary/node-v64-darwin-x64-unknown/grpc_node.node" is installed via remote
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN fabric-chaincode@1.0.0 No repository field.

added 193 packages from 113 contributors and audited 324 packages in 165.543s
found 0 vulnerabilities

> npm install --save-dev typescript @types/node

npm WARN fabric-chaincode@1.0.0 No repository field.

+ @types/node@12.0.4
+ typescript@3.5.1
added 2 packages from 25 contributors, updated 1 package and audited 326 packages in 13.708s
found 0 vulnerabilities


tsconfig.json を生成して編集する

> npx tsc --init


tsconfig.json

{

"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true
},
"include": [
"src/**/*"
]
}


チェーンコードの実装をする

> touch src/main.ts

ざっくりと実装します。

TypeScriptでどう実装したらよいかは下記が参考になりました。

fabric-chaincode-node/chaincode.ts at release-1.4 · hyperledger/fabric-chaincode-node

https://github.com/hyperledger/fabric-chaincode-node/blob/release-1.4/fabric-shim/test/typescript/chaincode.ts

Invoke メソッドでメソッド名からconst method = (this as any)[fcn]; でメソッドを動的に取得してるのがなんともですが、ひとまずお試しなので^^


src/main.ts

import {

Shim,
ChaincodeInterface,
ChaincodeStub,
ChaincodeResponse,
} from 'fabric-shim';

class Chaincode implements ChaincodeInterface {

async Init(stub: ChaincodeStub): Promise<ChaincodeResponse> {
let hello_chaincode = {
message: "hello chaincode!"
};
await stub.putState("hello", Buffer.from(JSON.stringify(hello_chaincode)));
return Shim.success();
}

async Invoke(stub: ChaincodeStub): Promise<ChaincodeResponse> {
const {fcn, params} = stub.getFunctionAndParameters();
const method = (this as any)[fcn];
if (!method) {
console.error('no function of name:' + fcn + ' found');
throw new Error('Received unknown function ' + fcn + ' invocation');
}

try {
let payload = await method(stub, params);
return Shim.success(payload);
} catch (err) {
console.log(err);
return Shim.error(err);
}
}

async queryHello(stub: ChaincodeStub, args: string[]): Promise<Buffer> {
if (args.length != 1) {
throw new Error('Incorrect number of arguments. Expecting key ex: hello');
}
let key = args[0];

let valueAsBytes = await stub.getState(key);
if (!valueAsBytes || valueAsBytes.toString().length <= 0) {
throw new Error(key + ' does not exist: ');
}
console.log(valueAsBytes.toString());
return valueAsBytes;
}

async putHello(stub: ChaincodeStub, args: string[]): Promise<void> {
if (args.length != 1) {
throw new Error('Incorrect number of arguments. Expecting 1');
}

var data = JSON.parse(args[0]);

await stub.putState(data.key, Buffer.from(JSON.stringify(data)));
}
};

Shim.start(new Chaincode());



ビルドしてみる

npx tsc コマンドでビルドしてみます。

> npx tsc

無事にビルドできたらdist フォルダにjsファイルが作成されているはずです。


dist/main.js

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const fabric_shim_1 = require("fabric-shim");
class Chaincode {
async Init(stub) {
let hello_chaincode = {
message: "hello chaincode!"
};
await stub.putState("hello", Buffer.from(JSON.stringify(hello_chaincode)));
return fabric_shim_1.Shim.success();
}
async Invoke(stub) {
const { fcn, params } = stub.getFunctionAndParameters();
const method = this[fcn];
if (!method) {
console.error('no function of name:' + fcn + ' found');
throw new Error('Received unknown function ' + fcn + ' invocation');
}
try {
let payload = await method(stub, params);
return fabric_shim_1.Shim.success(payload);
}
catch (err) {
console.log(err);
return fabric_shim_1.Shim.error(err);
}
}
async queryHello(stub, args) {
if (args.length != 1) {
throw new Error('Incorrect number of arguments. Expecting key ex: hello');
}
let key = args[0];
let valueAsBytes = await stub.getState(key);
if (!valueAsBytes || valueAsBytes.toString().length <= 0) {
throw new Error(key + ' does not exist: ');
}
console.log(valueAsBytes.toString());
return valueAsBytes;
}
async putHello(stub, args) {
if (args.length != 1) {
throw new Error('Incorrect number of arguments. Expecting 1');
}
var data = JSON.parse(args[0]);
await stub.putState(data.key, Buffer.from(JSON.stringify(data)));
}
}
;
fabric_shim_1.Shim.start(new Chaincode());


チェーンコードのデプロイができるようにする

Peerノードのチェーンコードをデプロイするにはチェーンコードのjsファイルとpackage.json が必要となるので、package.jsonを用意します。

> touch dist/package.json

先に作成したpackage.json が利用できたら良かったのですが、Peerにデプロイする際にfabric-shim のバージョンが1.4.1 だとPeerノードでのnpm install 時にfailed: cache mode is 'only-if-cached' but no cached response available. エラーが出てしまったので1.2.1 がインストールされるようにしています。

私が検証で利用したブロックチェーンネットワークがAmazon Managed Blockchainで構築したもので、Hyperledger Fabricのバージョンが1.2.1 なのが原因かもしれません。。。


package.json

{

"name": "fabric-chaincode",
"version": "1.0.0",
"description": "My first exciting chaincode implemented in node.js",
"engines": {
"node": ">=8.4.0",
"npm": ">=5.3.0"
},
"scripts": {
"start": "node main.js"
},
"engine-strict": true,
"license": "Apache-2.0",
"dependencies": {
"fabric-shim": "~1.2.1"
}
}


動作確認してみる

下記の手順で構築したブロックチェーンネットワークに対して、チェーンコードをデプロイしたところ、無事に動作しました。

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita

https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e

懸念として開発時とデプロイ時でfabric-shim のバージョンが異なるので利用するAPIによってはバージョン違いによる不整合があるかもしれません。(未確認)

ひとまず、TypeScriptでも実装できなくはないというご参考までに^^


参考

Hyperledger Fabric 入門, 第 5 回: チェーンコードの書き方

https://www.ibm.com/developerworks/jp/cloud/library/cl-hyperledger-fabric-basic-5/index.html

Hyperledger FabricのChaincodeをNode.jsでかく(TypeScript) - Qiita

https://qiita.com/Huruikagi/items/0321460f467847e7438e

hyperledger/fabric-chaincode-node: Read-only mirror of

https://gerrit.hyperledger.org/r/#/admin/projects/fabric-chaincode-node https://github.com/hyperledger/fabric-chaincode-node

fabric-chaincode-node/v1.3.0.txt at release-1.4 · hyperledger/fabric-chaincode-node

https://github.com/hyperledger/fabric-chaincode-node/blob/release-1.4/release_notes/v1.3.0.txt

TypeScript + Node.js プロジェクトのはじめかた2019 - Qiita

https://qiita.com/notakaos/items/3bbd2293e2ff286d9f49

fabric-chaincode-node/chaincode.ts at release-1.4 · hyperledger/fabric-chaincode-node

https://github.com/hyperledger/fabric-chaincode-node/blob/release-1.4/fabric-shim/test/typescript/chaincode.ts

Amazon Managed BlockchainでHyperledger Fabricのブロックチェーンネットワークを構築してみた - Qiita

https://qiita.com/kai_kou/items/e02e34dd9abb26219a7e