技術パート監修・執筆: @shinsa82
1. はじめに
Hyperledger Fabric (以下Fabric) のサンプルである "fabcar" を使ったチュートリアルがRead the Docsの Writing Your First Application に公開されている。
このチュートリアルでは、Fabricのブロックチェーン用のアプリケーション開発を簡単なサンプルを通して体験することができる。
下図はチュートリアルを通して構築できるfabcar用ブロックチェーンネットワークのノード構成とコーディング対象範囲のイメージである。
図のイメージのように、Fabricのブロックチェーン用のアプリケーション開発としては大きく分けて以下の2つがある。
- ブロックチェーンに接続するクライアントアプリケーションの開発
- ブロックチェーンの台帳を参照/更新するためのチェーンコードの開発
前者(クライアントアプリケーション)の開発にはSDKが提供されており、これを使ってFabricのブロックチェーンネットワークに対して証明書を取得したり、台帳の参照/更新を要求したりする。なおSDKには現時点(2018年11月現在)において、Node.js向けとJava向けが公式にリリースされており、チュートリアルではNode.jsを使った開発を行う。
一方、後者(チェーンコード)の開発には台帳を参照/更新するためのAPIが提供されている。このAPIはブロックチェーンに対するアクセスの他、Wolrd State(NoSQLデータベースであるLevelDBまたはCouchDB)に対するアクセスを行う。チュートリアルで扱う”fabcar”はCouchDBによりデータを保持しており、このデータに対する参照/更新を行う。なおチェーンコードは現時点(2018月11月現在)において、Fabric v1.2ではNode.jsまたはGo言語による開発が必要であり、APIもこれに準ずる形となる。(Fabric v1.3からはJavaによる開発も可能)
以下の解説は、公式ドキュメントに書かかれていることの和訳と解説である:
- https://hyperledger-fabric.readthedocs.io/en/release-1.2/write_first_app.html
- https://hyperledger-fabric.readthedocs.io/en/release-1.2/understand_fabcar_network.html
fabcar
はFabricのサンプル群である fabric-samples
のサンプルの1つで、Fabricネットワーク上のスマートコントラクト (チェーンコード) をNode.jsから呼び出すものである。
ネットワーク設定およびチェーンコードは、同じくサンプルの basic-network
を使用している。これはピアが1台だけのシンプルなネットワークである。
参考:
2. 前提条件
第1回 の内容にしたがって、次の作業が実施済みであること。
- Dockerイメージのpull
- Platform-specific binariesのダウンロード
- fabric-samplesのクローン
なお、このサンプルを動かすにはNode.js v8.9.x が必要である。gRPCのバージョンとの関係で、v9以降では動かないことは確認済みである。v8.10以降で動くかについては未調査である。
3. すでにコンテナが動いているなら止める
Dockerは名前空間がないので、他のアプリケーション用にFabricを動かしていると上手くいかない。そちらは止める必要がある。このサンプルにかかわらず、日頃から作業を始める前はこれをやっておくとよい
(first-networkを動かしている場合) (cd ../first-network && ./byfn down)
docker rm -f $(docker ps -aq) // 不要なコンテナの削除
docker network prune // ネットワークの削除
(チェーンコードのdockerイメージが残っている場合削除) docker rmi dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba
イメージ名は場合により異なるが、fabric-samplesのものは dev-* という名前のイメージになっていることが多い。
4. 試す
fabcarディレクトリに移動する
~/fabric-121$ cd fabric-samples/fabcar
~/fabric-121/fabric-samples/fabcar$
ls したときに以下のファイルがあればよい:
-
package.json
-
enrollAdmin.js
-
invoke.js
-
query.js
-
registerUser.js
-
startFabric.sh
.js
ファイルはNode.jsのスクリプトである。
package.json
はNode.jsのプロジェクト定義ファイルであり、依存ライブラリなどが書かれている。
.sh
ファイルはFabricネットワークを起動するためのシェルスクリプトである。
4.0 依存パッケージのインストール
ネットワーク接続がある状態で、Node.jsの依存ライブラリをインストールしておく。失敗する場合は、(1) Nodeのバージョンが適切でない (v9以降ではだめ)、もしくは (2) Pythonのバージョンが適切でない (python3はだめ)、のどちらかであることが多い。
npm install
4.1 Fabricの起動
startFabric.sh を実行する。
./startFabric.sh
startFabric.sh
は、まず basic-network
(BYFNではない) のdocker-compose.ymlを使って、通常のFabricコンテナ群を立ち上げる (/start.sh
)。
それに加えて、チェーンコードのデプロイを行うためのcliコンテナを立ち上げている (docker-compose -f ./docker-compose.yml up -d cli
)。
cliコンテナでは、チャネルの作成、peerのチャネルへのjoin、および、チェーンコードのデプロイを行っている。ただしチェーンコードはfabcarをデプロイしている。
デプロイ後は初期データをセットアップするためのチェーンコードメソッド initLedger()
を呼んでいる。
なお第1引数でインストールするチェーンコードの言語を選べる。デフォルトはgolang (Go言語) であるが、
./startFabric.sh node
のようにnode
またはNODE
を指定するとNode.jsのチェーンコードをインストール対象とする。
"Total setup execution time..." などと5行くらいメッセージが表示されれば成功。
ちなみにdocker psの結果はこのようになる。dev- で始まっているのがチェーンコードのコンテナである。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e90794d0cdab dev-peer0.org1.example.com-fabcar-1.0-5c906e402ed29f20260ae42283216aa75549c571e2e380f3615826365d8269ba "chaincode -peer.add…" About a minute ago Up About a minute dev-peer0.org1.example.com-fabcar-1.0
eeb6fd6f41c9 hyperledger/fabric-tools "/bin/bash" About a minute ago Up About a minute cli
7e640da0ec3f hyperledger/fabric-peer "peer node start" About a minute ago Up About a minute 0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp peer0.org1.example.com
6a3fbf31fa78 hyperledger/fabric-couchdb "tini -- /docker-ent…" About a minute ago Up About a minute 4369/tcp, 9100/tcp, 0.0.0.0:5984->5984/tcp couchdb
5028fb5e8a88 hyperledger/fabric-ca "sh -c 'fabric-ca-se…" About a minute ago Up About a minute 0.0.0.0:7054->7054/tcp ca.example.com
d48a905e9d6e hyperledger/fabric-orderer "orderer" About a minute ago Up About a minute 0.0.0.0:7050->7050/tcp orderer.example.com
4.2 (参考) Fabricの終了
basic-networkディレクトリで
(cd ../first-network && ./teardown.sh)
4.3 (参考) ログの閲覧
デフォルト設定では、dockerのコンテナの出力 (標準出力および標準エラー出力) はホストマシンのファイルに書き込まれている。docker logsコマンドでそのログを見ることができる。
docker logs [-f] <コンテナ名もしくはID>
-f
オプションを付けるとtailコマンドでファイルを表示した場合と同様になる。
Fabricのログは標準エラーに出るものが多いのでgrepなどするときに注意すること。例えばこうやるとよい
docker logs <コンテナ> 2>&1 | grep ...
4.4 fabcarスクリプト群の実行
ここから、スクリプト群を用いてFabric Node.js SDKの使用例を学ぶ。
とりあえず動くことが確認できればいいのなら、5.4にあるように invoke.js
を修正したうえで以下のコマンドを順に実行してエラーが出なければ、正しく動作している。
node enrollAdmin.js
node registerUser.js
node query.js
node invoke.js
動作の詳細が知りたいなら以下をお読みいただきたい。
5. 動作詳細
5.1 Adminとしてログイン (証明書の取得) [enrollAdmin.js]
以下のコマンドを実行する:
node enrollAdmin.js
Fabricネットワークにトランザクションを発行するには、ユーザとしての証明書が必要である。
証明書はCAに対して要求するが、そのときにパスワードが必要である。
basic-network/docker-compose.yml の21行目にあるように、CAの起動時にCA adminのユーザ名とパスワードを指定している。これを用いてまずCAのadminユーザの証明書を取得しているのが、enrollAdmin.js
がやっていることである。
つまり、CAのadmin情報はデフォルトの
admin:adminpw
を使ってはいけない。実運用では必ず変更すること。
Node SDKのモジュールは2つ、(1) PeerやOrdererと通信するための fabric-client
と、(2) CAと通信するための fabric-ca-client
がある。
証明書を取得するのが目的なのでここで主に使うのは fabric-ca-client
であるが、CryptoSuiteを作るために fabric-client
も必要である。
- はじめにCryptoSuiteオブジェクトの生成と初期設定を行う。このあたりはおまじないだと思っておけばよい。
- 設定が済んだら42行目でCA clientを作成。CAのエンドポイント、TLSのオプション、ホスト名 (TLSの認証に必要)、作成済のCryptoSuiteを指定する。
- 45行目ではすでにenroll済か (証明書が手元にあるか) を
getUserContext()
でチェックしている。ただし何回でもenrollできるので特にチェックしなくても構わない - 47行目ではユーザコンテキスト (ユーザオブジェクト) を取得できて、それがすでにenroll済であることがわかればそのままthen()を抜ける。
そうでなければ53行目でenroll()メソッドを呼び出す。(ここではadminの) ユーザ名とパスワードを指定する。 - 成功すると58行目で、証明書や秘密鍵を含むenrollmentオブジェクトが返ってくる。これらはさきほど指定したCryptoSuiteの中で指定したKeyStoreの場所に保存される。ここからcreateUser()メソッドでユーザオブジェクトを作成する。ユーザ名、ユーザの所属するMSPID、証明書と秘密鍵 (のPEM表現) を指定する。
このあたり回りくどいが我慢する。 - 65行目ではユーザオブジェクトをsetUserContext()でfablic clientに設定する。このあとトランザクションを起こすときにはこのclientを使って行うことになる。
初回実行時の反応は以下のようになる:
Store path:/Users/shinsa/git/tecj/fabric-samples/fabcar/hfc-key-store
(node:23480) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead
Successfully enrolled admin user "admin"
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"c5c52f3df2eae995244a9aae832e690754e6f6ec2d09168a2d4ad5bb0f54c11f","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICATCCAaigAwIBAgIUH2BABlal9+tuiouAEI3rw/IUKg8wCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTgxMDA3MTI0NzAwWhcNMTkxMDA3MTI1\nMjAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAEzABVuYSVoRJIuwvE7wv9oQ/x8HWPenRpgTBEeZ/3\nQyvNl5cVqi9XhpjaWy7x5qE504zBXhdztpcYkCV91yjC7qNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMmjy1KOobpeD6TGmONjVXTB\ncyRDMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0cAMEQCIEdIIBbtkvg5/UWSq6LRy1aek/hnMvLBGEOXQkzA\nW97fAiA1yvh1z6j7ESvktYmaeVU9lA7VwF3a1WxmdbsDDnkKFQ==\n-----END CERTIFICATE-----\n"}}}
一方、2回目以降の実行では以下のようになり、4行目のメッセージが異なる:
なお、gRPCのwarningが出ているが、こちらではどうしようもないので気にしない。
Store path:/Users/shinsa/git/tecj/fabric-samples/fabcar/hfc-key-store
(node:23515) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead
Successfully loaded admin from persistence
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"c5c52f3df2eae995244a9aae832e690754e6f6ec2d09168a2d4ad5bb0f54c11f","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICATCCAaigAwIBAgIUH2BABlal9+tuiouAEI3rw/IUKg8wCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTgxMDA3MTI0NzAwWhcNMTkxMDA3MTI1\nMjAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAEzABVuYSVoRJIuwvE7wv9oQ/x8HWPenRpgTBEeZ/3\nQyvNl5cVqi9XhpjaWy7x5qE504zBXhdztpcYkCV91yjC7qNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMmjy1KOobpeD6TGmONjVXTB\ncyRDMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0cAMEQCIEdIIBbtkvg5/UWSq6LRy1aek/hnMvLBGEOXQkzA\nW97fAiA1yvh1z6j7ESvktYmaeVU9lA7VwF3a1WxmdbsDDnkKFQ==\n-----END CERTIFICATE-----\n"}}}
fabcarディレクトリの hfc-key-store
ディレクトリにadminのユーザ情報 (admin)、adminの秘密鍵 (<16進の数>-priv) および公開鍵 (<同じ数>-pub) が格納されていることがわかる。この中で、秘密鍵は他人に知られてはいけないので注意する。
このディレクトリを消してスクリプトを再実行すると、1回目と同様の出力になる。
5.2 ユーザ登録 (証明書の取得) [registerUser.js]
次に、トランザクションを実行する (一般) ユーザの登録および証明書の取得を行う。
以下のコマンドを実行する:
node registerUser.js
実行結果が以下のようになれば成功である。とくに最後の行に「ユーザがregisterされ、enrollされたこと」が表示されているか確認すること。
Store path:/Users/shinsa/git/tecj/fabric-121/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:gLxRaGwKvlUi
Successfully enrolled member user "user1"
User1 was successfully registered and enrolled and is ready to interact with the fabric network
ちなみにスクリプトをもう一度実行すると以下のようなエラーになる。
指定されたユーザはすでに登録されているというエラーである。
Store path:/Users/shinsa/git/tecj/fabric-121/fabric-samples/fabcar/hfc-key-store
Successfully loaded admin from persistence
Failed to register: Error: fabric-ca request register failed with errors [[{"code":0,"message":"Registration of 'user1' failed: Identity 'user1' is already registered"}]]
スクリプトを解説する。
- 52行目までは管理者のenrollと同様に、
getUserContext()
で管理者アカウントによる証明書の取得を行う。 - 56行目では
register()
を呼んで、ユーザuser1
をCAに登録する。
第1引数は新たに登録するユーザの情報である。
ここで (たしか) Affiliationを指定する必要がある。
これは階層的なアクセス制御などに使用することができるはずだが、筆者は使用例を見たことがない。
Roleはclient
としておく。 - ユーザの登録が成功すると、57行目の
then
メソッドのsecret
引数に、CAで生成されたユーザのパスワードが返ってくる。
なお、ユーザ登録が成功するのは、admin
ユーザが新たなユーザの登録権限を持っているからである。 - 61行目ではこのパスワードを用いてenroll (証明書の取得) を行う。
- Enrollが成功した後は前述のスクリプトの例と同様である。まず
createUser()
でユーザオブジェクトを生成しsetUserContext()
でクライアントにセットする。
これでユーザ user1
の権限でトランザクションを発行できるようになった。
5.3 参照系トランザクションの実行 [query.js]
ユーザ権限が設定できたら、いよいよトランザクションの発行を行う。
以下のコマンドを実行すると問い合わせを行う:
node query.js
結果が以下のようになれば正常である:
Store path:/Users/shinsa/git/tecj/fabric-121/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
スクリプトを解説する。
- トランザクションを発行するためには、各ピアに接続する必要がある。
そのため、20行目ではチャネル名を指定してチャネルインスタンスを作成する。
21行目ではピアのエンドポイントアドレスを指定してピアインスタンスを作成する。
アドレスはbasic-network/docker-compose.yml
に書かれている。
ピアとの通信はgRPCで行うのでgrpc:
を指定している。
同様にオーダラーインスタンスを作成しているが、このスクリプト (参照系トランザクション) では使わない。 - ユーザ権限の設定はこれまでと同様である。43行目で
getUserContext()
を呼び、ユーザ情報を取得する。
当該ユーザが正しくenrollされている場合には次へ進む。 - 54行目からはトランザクションのリクエストを作成する。チェーンコードID (
chaincodeId
) は必須で、その他は任意である。ここでは関数名 (fcn
) と、引数リスト (args
) を指定している。
引数は文字列の配列として渡す。
その他の引数についてはAPIドキュメント (https://fabric-sdk-node.github.io/global.html#ChaincodeQueryRequest) を見てほしい。
なお、関数名は単に引数データの一部であり、引数リストの先頭に挿入されてチェーンコードに渡される。 - 62行目では
queryByChaincode()
問い合わせトランザクションの発行を行う。
クライアントは各ピアにリクエストを送り、各ピアはチェーンコードを実行してステートDBへの問い合わせを行い、結果をクライアントに返す。
次のセクションで説明するが、チェーンコードから見たときには参照系/更新系のトランザクションで処理に違いはない。
クライアント側での処理が異なるため、クライアントAPIとしては別のメソッドになっている。 - 63行目で各ピアからのレスポンス (Bufferインスタンス) の配列を受け取る。
66行目ではその長さが1であることを確認している。今回使用しているbasic-network
はピアが1台だからである。
返ったのがエラーレスポンスであるなら (67行目)、68行目でその旨表示している。
そうでないなら70行目でレスポンスを文字列に変換した後に画面に表示している。これで問い合わせトランザクションの処理完了となる。
5.4 更新系トランザクションの実行 [invoke.js]
次に、ステートDBの更新を伴うトランザクションを発行する。
参照系と比べてクライアントサイドで行うことがかなり多い。
なぜかこのスクリプトだけ実習形式になっている。内容を追記してから実行する必要がある。
invoke.js
の61行目、request
インスタンスを作成している部分を以下のように書きかえる
var request = {
//targets: let default to the peer assigned to the client
chaincodeId: 'fabcar',
fcn: 'createCar',
args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'],
chainId: 'mychannel',
txId: tx_id
};
そうしてから以下のコマンドを実行すると更新処理を行う:
node invoke.js
成功すると以下のようなメッセージが表示される:
Store path:/Users/shinsa/git/tecj/fabric-121/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Assigning transaction_id: 9cc04331245c82b8f85b2a24eaf856e70bd3d02d1306a920452750beca8060a5
Transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - ""
The transaction has been committed on peer localhost:7051
Send transaction promise and event listener promise have completed
Successfully sent transaction to the orderer.
Successfully committed the change to the ledger by the peer
これが成功したあとで、もう一度問い合わせを行うと、以下のように CAR10
というエントリが追加される:
Store path:/Users/shinsa/git/tecj/fabric-121/fabric-samples/fabcar/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{"Key":"CAR10", "Record":{"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"}},{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
スクリプトを解説する。コードを理解するにはJavscriptの非同期処理プリミティブである Promise
に関する知識が必要である。
- トランザクションを送信するための、チャネル・ピア・オーダラーインスタンスを作成するところは同じである。
参照系と異なり、今回はオーダラーインスタンスも使用する。 - ユーザ権限の設定もこれまでと同様である。
- 55行目からはトランザクションの内容を設定する。
tx_id
にはクライアントが生成したトランザクションID (以下TXID) を設定する。
実は、全てのトランザクションにはユニークなTXIDが必要である。
参照系トランザクションでは指定を省略したがライブラリが自動生成してくれていた。
更新系トランザクションではトランザクションの完了イベントを待つ必要があり、購読の際にTXIDが必要になるため明示的に生成している。 - 71行目では
sendTransactionProposal()
を呼び、更新系トランザクションの実行を行う。
ここでピアにリクエストを送り、ピアにチェーンコードを実行してもらい、
クライアントはその結果 (Read-Write Set) を受け取る。 - 実行が成功した場合、72行目で結果を受け取る。
これは2要素の配列で、第0要素にトランザクションの実行結果 (エンドースメント) が、第1要素にリクエスト自体が入っている。 - 76行目以降ではレスポンスがエラーでないことを確認する。
本サンプルではピアが1台なのでこれでよいが、複数台の場合は「各レスポンスの結果が一致すること」を確認した方が処理性能に貢献する。確認用のメソッドがあるはずだが名前を失念した。
なお、一般には更新系トランザクションは返り値を返さないことが多いが、このサンプルのように返り値を取得しても構わない。 - ここからが更新系処理の特徴である。
レスポンスが正常なら、89行目でオーダラーに送信するトランザクションを作成している。
通常、これはsendTransactionProposal()
の返り値そのままでよい。 - 97行目ではTXIDを文字列として取得している。
- 100行目ではチャネルインスタンスに対して
sendTransaction()
を呼び、オーダラーに対して送信している。
このメソッドは (非同期処理に用いられる) Promiseインスタンスを返す。
これをpromises
配列に格納しておく (100行目)。 - 105行目ではトランザクションのコミットイベントを購読するために、チャネルインスタンスの
newChannelEventHub()
メソッドでEvent Hubインスタンスを作成している。 - 110行目では「トランザクションのコミット待ち」を処理するPromiseインスタンスを作成している。
ここではコミットイベントを待つとともに、30秒のタイムアウトを設定している。
Event HubインスタンスのregisterTxEvent()
メソッドでイベントリスナを登録する (116行目)。
登録した後はEvent Hubに接続する必要もある (136行目)。
イベントハンドラには当該トランザクションtx
とステータスコードcode
が渡されるので、
その内容に基づき画面への出力などを行う。
code
がVALID
でないとき (123行目) はトランザクションがコミットされなかったことを示す。
原因にはMVCC (説明略) の不整合やエンドースメント・ポリシーによる拒絶などがあるが、
これらの場合はコミット拒絶という正常終了の一種とみなす (123行目)。
code
がVALID
ならコミット完了、正常終了とみなす (128行目)。
一方、タイムアウトはこのPromise作成から30秒に設定され (115行目)、タイムアウトした場合はタイムアウトによる正常終了とみなしている (114行目)。
リクエスト部分を書き換えると他のチェーンコードメソッドも同様に呼ぶことができる。
コメントにあるように、これはエラーとみなしてもよい。 - 139行目で、さきほど作成したPromiseを
promises
に追加する。
これで、トランザクションの送信に関するものと、コミット待ちに関するものとの、2つのPromiseが格納されたことになる。 - 141行目ではこれらの完了を
Promise.all()
で待つもの、つまり2つのPromiseの完了を待つPromise、返している。
これは72行目から続く長いthen()
の返り値である。 - これらが正常に完了した場合には、146行目以降で結果の出力を行う。
1つ目のPromiseに関してはstatus
フィールドで成功したか否かの判定を行う (149行目)。
(なお、Promise.all()
が成功しているのだから常に成功じゃないかと思うのだが。)
2つ目に関してはevent_status
フィールドで判定を行う。