連載 [第5回] :
  Hyperledger Fabric再入門

Fabric SDK(Software Development Kit)を用いてNode.jsからchaincodeを呼び出す

2021年1月26日(火)
西島 直

はじめに

前回はfabric-samplesにあるfabcarを用いたchaincodeの解説と新たな関数を追加しました。Hyperledger FabricのchaincodeはgoやNode.jsで書くことができます。Chaincode特有のコードはそこまで多くなく、容易に習得できると思います。

これまではpeerコマンドでchaincodeを呼び出していましたが、今回はFabric SDK(Software Development Kit)を用いてNode.jsからchaincodeを呼び出してみたいと思います。

Fabric SDKとは

Hyperledger Fabricが提供する最初の2つのSDKはNode.js(fabric-sdk-node)とjava(fabric-sdk-java)です。また、開発途中ではありますが、PythongoのSDKも利用できます。

Blockchainが一般的なアプリケーションと違うところは、トランザクションを発行するときにユーザーの署名が必要なことです。これは署名を行うための秘密鍵をどこに配置するのかが課題となります。秘密鍵やユーザーの証明書などを保存する場所をHyperledger Fabricでは「Wallet」と呼びます。

Walletにはファイルシステム、インメモリ、CounchDBの3つのタイプがあります。ファイルシステムは一般的な保存場所です。アプリケーションが動作しているマシンに保存できますし、ネットワーク越しにマウントすることもできます。インメモリはアプリケーションが動作しているマシンのメモリに保存されます。アプリケーションが終了したり、マシンがクラッシュしたりすると失われます。CouchDBはデータベースに保存されます。バックアップ・リストアなど有用な機能を利用できます。また、秘密鍵だけwalletではなく、ハードウェアセキュリティモジュール(HSM)と呼ばれる特別な機器に保存することも可能です。

これらのWalletはユーザー側でなくアプリケーションを実行するマシンに秘密鍵を置きますが、ユーザー側に秘密鍵を置き署名することができます(Client Signingとも、Offline Private Keyとも呼ばれる)。これは、携帯電話の指紋認証や生体認証などの特別なデバイスでユーザーの秘密鍵を管理する場合に利用します。

asset-transfer-basicアプリケーション

今回はfabric-samplesに入っているasset-transfer-basicのNode.jsアプリケーションを解説します。fabric-samples直下のasset-transfer-basicディレクトにはgoやjava, node.jsのSDKを用いたサンプルが用意されています。

$ tree asset-transfer-basic/application* -L 2
asset-transfer-basic/application-go
├── assetTransfer.go
├── go.mod
└── go.sum
asset-transfer-basic/application-java
├── build.gradle
├── gradle
│   └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    └── main
asset-transfer-basic/application-javascript
├── app.js
└── package.json
asset-transfer-basic/application-typescript
├── package-lock.json
├── package.json
├── src
│   ├── app.ts
│   └── utils
├── tsconfig.json
└── tslint.json

application-javascriptディレクトリにあるapp.jsはnode.js用のsdkを用いて書かれたサンプルアプリケーションです。app.jsは186行とシンプルなプログラムです。

最初の方を見てみます。

$ cat fabric-samples/asset-transfer-basic/application-javascript/app.js
…
const { Gateway, Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js');

(後略)

3行目でfabric-networkモジュールを読み込んでいます。このモジュールはpeerへの接続やWalletに利用されます。4行目ではfabric-ca-clientモジュールを読み込みます。このサンプルでは、fabric caに新たにユーザーを登録してからトランザクションを発行しています。

6行目はCAUtils.jsを読み込んでいます。これにはCAへ接続するための関数やAdminやユーザーを登録する関数が用意されています。7行目はAppUtil.jsを読み込んでいます。これにはコネクションプロファイルの読み込みやWalletを構築するための関数が定義されています。これら2つのファイルは、サンプルアプリケーションを容易に作成するための便利な関数をfabric-samplesが用意したものです。

コネクションプロファイルは、ブロックチェーンネットワークの構成をまとめた設定ファイルです。各組織のpeerやcaへのURLや証明書が記述されています。アプリケーションはこれらの情報をもとに各ノードへ接続します。Fabric-samplesのnetwork.shでブロックチェーンネットワークを立ち上げると、以下の場所に生成されます。

$ls ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection*
connection-org1.json  connection-org1.yaml
$ cat ~/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json
{ 
    "name": "test-network-org1",
    "version": "1.0.0",
    "client": {
        "organization": "Org1",
        "connection": {"timeout": {"peer": {"endorser":"300"}}}
    }, 
    "organizations": { 
        "Org1": {
            "mspid": "Org1MSP",
            "peers": ["peer0.org1.example.com"],
            "certificateAuthorities": ["ca.org1.example.com"]
        }
    },
    "peers": {
        "peer0.org1.example.com": {
            "url": "grpcs://localhost:7051",
            "tlsCACerts": {
                "pem": "-----BEGIN CERTIFICATE-----\nMIICVzCCAf2gAw…"
            },
            "grpcOptions": {
                "ssl-target-name-override": "peer0.org1.example.com",
                "hostnameOverride": "peer0.org1.example.com"
            }
    },
    "certificateAuthorities": {
        "ca.org1.example.com": {
            "url": "https://localhost:7054",
            "caName": "ca-org1",
            "tlsCACerts": {
                "pem": ["-----BEGIN CERTIFICATE-----\nMIICUTCCAfigAwIBAg…"]
            },
            "httpOptions": { "verify": false }
        }
    }
}

main関数の中を見てみます。

(前略)
const ccp = buildCCPOrg1();

// build an instance of the fabric ca services client based on
// the information in the network configuration
const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com');

// setup the wallet to hold the credentials of the application user
const wallet = await buildWallet(Wallets, walletPath);

// in a real application this would be done on an administrative flow, and only once
await enrollAdmin(caClient, wallet, mspOrg1);

// in a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1');

(後略)

2行目ではAppUtils.jsで定義されたbuildCCPOrg1関数を読み込んでいます。この関数はCAやgatewayへ接続するためのコネクションプロファイルを読み込みます。Gatewayはアプリケーションがビジネスロジックに集中できるようにPeerやCAへの接続を管理します。GatewayにはStaticとDynamicがあります。Staticのgateway構成はコネクションプロファイルで定義されます。Dynamicのgateway構成にはコネクションプロファイルを利用しますが、接続したpeerのサービスディスカバリー機能を用いて利用可能なネットワーク構成を取得します。これにより、途中でpeerのTLSの証明書やエンドポイントが更新された場合でも接続できるようになります。

6行目ではCAへアクセスするための準備をしています。引数にfabric-ca-clientモジュールとコネクションプロファイル、接続先のCAを指定しています。

9行目はWalletを生成しています。fabric-sdk-nodeモジュールから読み込んだWalletsを指定しています。今回はWalletの保存場所にファイルシステムを利用するため、2つ目の引数にはWalletの情報を保存するためのパスを指定しています。AppUtils.jsのbuildWallet関数を見ると、引数のwalletPathの有無によりWalletのタイプを切り替えています。walletPathを指定した場合はWallets.newFileSystemWallet()が呼ばれてWalletの情報をファイルシステムに保存します。そうでない場合はWallets.newInMemoryWallet()が呼ばれ、Walletの情報をインメモリに保存します。

12行目はCAにAdminの情報を登録(enroll)しています。CAにはRegisterとEnrollというアクションがあります。RegisterはCAに「これからユーザーのidentity情報を登録します」ということを伝えます。EnrollはRegisterしたユーザーの秘密鍵を生成し、CSR(Certificate Signing Request)を作成してCAに送り、CAは証明書を発行して返送します。コメントにある通り、実際のアプリケーションではこの操作は1回だけ行われます。

16行目で新しいユーザーを登録(RegisterとEnroll)しています。この操作はadminだけが行えるため、実際にはadmin用のアプリケーションを作成します。

以上でWalletの作成部分が終わりました。次はトランザクションを発行するための準備を行います。トランザクションを発行するには各組織のエンドースメントが必要となるため、各組織のpeerノードの接続先や状態などを知らなくてはいけません。これらの情報は更新される可能性があります。Peer同士であればgossipプロトコルを用いてお互いの組織の情報を交換しますが、アプリケーションで接続する場合はどのようにすれば良いのでしょうか。

Hyperledger Fabricではサービスディスカバリという機能を用意しています。アプリケーションはこの機能を用いて接続先の情報を取得します。

(前略)
const gateway = new Gateway();

try {
            await gateway.connect(ccp, {
                wallet,
                identity: org1UserId,
                discovery: { enabled: true, asLocalhost: true }
            });
(中略)
            const network = await gateway.getNetwork(channelName);
(中略)
            const contract = network.getContract(chaincodeName);
(中略)
            await contract.submitTransaction('InitLedger');
(中略)
            let result = await contract.evaluateTransaction('GetAllAssets');
(中略)
            result = await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300');
(後略)

2行目でgatewayインスタンスを生成し、5行目でgatewayに接続しています。コネクションプロファイルやidentityの情報、サービスディスカバリを利用するか、gatewayをローカルにデプロイされたブロックチェーンネットワークに利用するかを引数に入れています。

11行目はchannelとのネットワークを構築し、13行目は接続するchaincodeのインスタンスを生成します。15行目でchaincodeの初期化関数を呼び出しています。17行目でchaincodeのGetAllAssets関数を呼び出し登録されている情報を取得します。19行目はchaincodeのCreateAsset関数を呼び出し、新しい情報を登録しています。

asset-transfer-basicアプリケーションのテスト

実際にasset-transfer-basicのアプリケーションを用いてchaincodeを動かしてみます。fabric-samplesのtest-networkディレクトに移動して、下記のコマンドを実行しchaincodeをデプロイします。ユーザーの作成等を行うためCAも起動します。

$ ./network.sh up createChannel -ca
Creating channel 'mychannel'.
If network is not up, starting nodes with CLI timeout of '5' tries and CLI delay of '3' seconds and using database 'leveldb with cryp
to from 'cryptogen'
Bringing up network
(中略)
$ ./network.sh deployCC
deploying chaincode on channel 'mychannel'
executing with the following
- CHANNEL_NAME: mychannel
- CC_NAME: basic
- CC_SRC_PATH: NA
(後略)

次に、アプリケーションを実行するためアプリケーションがあるディレクトリ(fabric-samples/asset-transfer-basic/application-javascript)に移動してnpmコマンドを実行します(fabric-sdk-nodeはNode.jsのバージョン12.13.1以上をサポートしています)。

$ npm install

それでは、アプリケーションを実行してみます。

$ node app.js
Loaded the network configuration located at /home/ubuntu/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.json
Built a CA Client named ca-org1 
Built a file system wallet at /home/ubuntu/fabric-samples/asset-transfer-basic/application-javascript/wallet
Successfully enrolled admin user and imported it into the wallet 
Successfully registered and enrolled user appUser and imported it into the wallet

--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger
*** Result: committed 

--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger
*** Result: [
  { 
    "ID": "asset1", 
    "color": "blue", 
    "size": 5, 
    "owner": "Tomoko", 
    "appraisedValue": 300 
},
(中略)
--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes
*** Result: { 
  "ID": "asset1",
  "color": "blue",
  "size": 5, 
  "owner": "Tom", 
  "appraisedValue": 350
}

おわりに

今回は、fabric-samplesにあるasset-transfer-basicを用いてfabric-sdk-nodeについて解説しました。これで簡単なブロックチェーンアプリケーションを作成できると思います。fabric-samplesの中にはgoやjavaなどのアプリケーションもあり充実しています。ぜひ、他の言語のchaincodeやアプリケーションも動かしてみてくだい。

次回は、Hyperledger Fabricの関連プロジェクを紹介してみたいと思います。

株式会社日立製作所

研究開発グループ デジタルテクノロジーイノベーションセンタ OSSテクノロジーラボラトリ員
OSSの評価・検証・機能開発、upstream活動、社内外へのOSS普及に従事。
Linux KernelやKVM、OpenStackでOSSコミュニティの参加した経験を持つ。
現在はHyperledgerコミュニティに参加しブロックチェーンの普及に勤めている。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています