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


```console
# 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` としています。

```json: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"
	}
}
```

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

```console
> 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` を生成して編集する

```console
> npx tsc --init
```

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


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

```console
> 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];` でメソッドを動的に取得してるのがなんともですが、ひとまずお試しなので^^

```ts: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` コマンドでビルドしてみます。

```console
> npx tsc
```

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

```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`を用意します。

```console
> 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` なのが原因かもしれません。。。

```json: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