Node.js
TypeScript
Hyperledger
Hyperledger-fabric

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

BlockChain Advent Calendar 2017 の12日目の記事です。

動機とやったこと

専門学校の卒業研究として、ブロックチェーンを組み込んだシステム開発をやる流れになりました。勢い。
その中で、Hyperleger FabricのNode.js用Chaincode SDKをTypeScriptで書けるよう、ソースコードを眺めつつ型定義ファイルをさぐりさぐり作成してみました。

前置き

Chaincode とは

http://hyperledger-fabric.readthedocs.io/en/latest/chaincode.html

Blockchain一般でいうところの、いわゆるスマートコントラクトです。
Hyperledger Fabricでは Chaincode という呼び方をします。

RDBでいうところのストアドプロシージャみたいなもので、保持されているデータにアクセスするためにはChaincodeを介する必要がある、くらいのふわっとした認識を私はしています。(多分結構違う)

Chaincode開発に使える言語

Hyperledger Fabricでは、クライアントアプリケーション開発用のSDK (fabric-sdk-xxx) と、チェインコード開発用のライブラリ (fabric-chaincode-xxx) が複数のプログラミング言語用に実装されています。
他のプラットフォームだと専用言語だったりするので、その点ではハードルがかなり低いのではないかと思います。

以下に言語対応状況をなんとなくまとめてみます。

言語 SDK Chaincode 備考
Go Fabric本体がGoで開発されており、強い
Node 1.1.0くらいでChaincode対応しそう
Java SDKはKotlinでも叩けました。Chaincodeは今Mavenに上がっていません
Python - in incubation
Rest - work in progress
EVM - draft

Chaincode開発の方は、記事投稿時点の最新リリースであるv1.0.0では、Goしか対応していません。
ですが、fabric-chaincode-nodeはもうnpmにプレビュー版が上がっていますし、割と簡単に試用できます。
さらになんとなくv1.1.0くらいでちゃんと対応リストに加わりそうな雰囲気があります。
fabric-samples のmasterブランチにNode.jsによるChaincodeのサンプルが加わっていたり、 公式のドキュメント のlatestのほうを見るとnode.jsでも開発できるという記述が追加されたりしています。)

sdkとchaincode両方を同じ言語で書けるのは素敵ですし、今回はnodeを使います。Goはわからない。
でも今時型のない言語を触りたくないというのもあるので、折角だからTypeScriptで開発しようということになりました。

APIみながら型定義

https://github.com/hyperledger/fabric-chaincode-node

GitHubに上がっていたソースコードを読みながら雰囲気で定義していきます。
結構丁寧にJSDoc書いてくれているので、だいたいコピペみたいなものです。

ChaincodeInterface

interface ChaincodeInterface {

    Init(stub: ChaincodeStub): Promise<Response>;

    Invoke(stub: ChaincodeStub): Promise<Response>;

}

チェインコードとして登録するオブジェクトは、InitInvoke のメソッドを実装している必要があります。
API全体を通してですが、 async/await を前提として設計されているので、結構モダンなJSコードが書ける感じです。

Init

Chaincodeの初期化時と更新時に呼び出されます。
ここで Assets を初期化するらしいですが、とりあえず shim.success() を返すようになっていればいいはず。

Invoke

SDKからChaincode呼び出しをされると、このメソッドが実行されます。
この中でデータを追加したり読み出したりする処理を記述します。
公式のサンプルでは、引数で渡された関数名等の情報から、他のメソッドを呼び出すようなことをしています。

let ret = stub.getFunctionAndParams();
let method = this[ret.fcn];
// (中略)
let payload = await method(stub, ret.params);
return shim.success(payload);

割と力業ですね。
この通り、メソッド名で自動でルーティングされるような枠組みが容易されているわけではないので、無理にサンプルのようにメソッドとして生やす必要はないと思います。

ChaincodeStub

ChaincodeInterfaceのメソッドに引数として渡されます。
実際にブロックチェーン上のassetにアクセスするAPIを提供するのはこのオブジェクトです。
結構でかい定義なので抜粋になります。

getFunctionAndParameters

getFunctionAndParameters(): { fcn: string; params: string[]; };

呼び出された関数名とパラメタの情報が格納されたオブジェクトを取得します。
パラメタは単なる文字列配列。

getState

getState(key: string): Promise<Buffer>;

キーに結びつけられた情報を取得します。
一般的なKVSのようですね。
Fabricは、最新の情報をState Databaseと呼ばれるデータベースに保持しており、Chaincode内でのクエリもそのStateDBに対して行う感じで提供されるようです。

putState

putState(key: string, value: Buffer): Promise<void>;

キーに対応する情報を登録します。

deleteState

deleteState(key: string): Promise<void>;

削除します。

getQueryResult

getQueryResult(query: string): Promise<StateQueryIterator>;

リッチなクエリーを実行します。
FabricはState DatabaseとしてLevelDBとCouchDBが選択できるようですが、このメソッドはCouchDBを使っているときのみ有効なようです。
引数で渡すクエリ文字列も、CouchDBが要求する形式(JSON)のはず。

Shim

class Shim {

    static start(chaincode: ChaincodeInterface): void;

    static success(payload?: Buffer | void): Response;

    static error(msg: Buffer): Response;

    static newLogger(name: string): log4js.Logger;

}

require("fabric-shim") したときに実際に渡されてくるオブジェクトです。
shim.start(new MyChaincode()) みたいな感じで使います。

その他

class CommonIterator extends EventEmitter {
    close(): Promise<void>;
    next(): Promise<QueryResult>;
}

interface QueryResult {
    done: boolean;
    value?: { key: string; value: Buffer; };
}

export class StateQueryIterator extends CommonIterator {}

export class HistoryQueryIterator extends CommonIterator {}

QueryIterator まわりがちょっとよくわからない。gRPCで生成された部分が関わっているようなので……。
後でもうちょっとちゃんと調べます。

ソースコード

一応GitHubに置いています。

https://github.com/kbc-itw/BookChain-Chaincode/blob/master/src/d/fabric-shim.d.ts

全体のAPIを理解していない状態でJSソースコードからコピペしただけなのがほとんどなので、不必要な定義や、漏れもあるかと思います。
npmにfabric-shim 1.0.0のプレビューじゃないやつが上がったら、まじめに書き直そうと思います。

参考