空谷に吼える

ブロックチェーン/DLTまわりのなにかしらを書いていく所存

Hyperledger FabricでChaincode内Chaincode呼び出しするときに理解しておくべきこと

なんの話

  • Hyperledger FabricでChaincode内Chaincode呼び出しするときに理解しておくべきこと

内容

動機

この資料の最後のほうにもちらっと書いといたんですけどあんまり知られてなさそうなこともあり、もうちょっと詳しくまとめておきます。

前提:ChannelとChaincode、State DBの関係

Channelは複数作成できる。ひとつのChannelがひとつの台帳(State DBおよびブロックチェーン)です。

あるChaincodeはいずれかのChannelでインスタンス化することで実行可能になります。そして、あるChaincodeが(直接)アクセスできるのは、自身がインスタンス化されている、つまり稼働しているChannelの台帳のみです。

ひとつのChannelで複数のChaincodeを稼働させる(インスタンス化して実行可能可能状態にする)ことができます。しかし、あるChaincodeから書き込んだレコードを(直接)読み取り、更新できるのはそのレコードを書き込んだChaincodeだけ、という制約があります(例:Channel X上でChaincode Aが書いたレコードA1は、Channel X上のChaincode Bからは読み取りも更新もできない)。これについてはChaincodeはState内の名前空間として振る舞っていると考えるとわかりやすいかと思います(実際HLFのSDK見てるとChaincodeはNamespaceであると説明している箇所もあり、また、State DB内でのユニーク制約はChaincodeID+Keyの複合要素になっている)。

本題:できること、できないこと

そんで本題です。Chaincode内から他のChaincodeを呼び出して実行することができます。ずばりinvokeChaincode(chaincodeName, args, channel)という関数が用意されており、これを使います。この関数の引数のうち、chaincodeNameはそのままChaincodeのID、argsは呼び出し先のChaincodeに渡す引数(複数を配列として渡せます)、channelはどのChannelでインスタンス化されているChaincodeを呼び出すのか、をそれぞれ表しています。

で、Channelが指定できることからわかるように、あるChannelで実行されているChaincodeから別のChannelでのChaincode実行を行うことができるんですよね。しかしこのChannelまたぎでのChaincode実行は気をつけないとならない制約があるよ、というのをここで最終的に説明していきます。

カテゴリ分け

実はこのへんの話はこのHLF JIRAのIssue→[FAB-1788] Chaincode calling chaincode - Hyperledger JIRAにまとめられてるんですが、ここで使われている3つのカテゴリ分けはやや粗いので、この記事では「呼び出し元と呼び出し先のChaincodeが同一のChannelか/別のChannelか」と「State DBの読み取りのみを行うか/更新を行うか」の軸で以下4つに分けて説明していきます。

  1. 呼び出し先のChaincodeが同一Channelで、State DBの読み取りのみを行う場合
  2. 呼び出し先のChaincodeが同一Channelで、State DBの更新を行う場合
  3. 呼び出し先のChaincodeが別のChannelで、State DBの読み取りのみを行う場合
  4. 呼び出し先のChaincodeが別のChannelで、State DBの更新を行う場合

ちなみに呼び出し先のChaincodeから更に別のChaincodeを呼ぶこともできるんですが、その場合どうなるかも以下の説明の応用で理解できると思うので特に触れません。

1. 呼び出し先が同一Channel×読み取りのみ

これはほぼ制約なく扱えます。というのは、呼び出し元と呼び出し先のChaincodeが同一Channelでの実行であれば、単一のトランザクションとして呼び出し元と呼び出し先のコンテクストがまとめられるためです。つまり、呼び出し先でのクエリでのRead Setやクエリ条件などが呼び出し元のそれらとまとめられたうえで、ValidationフェーズでRead Set ValidationやPhantom Read Validationの評価が行われます。呼び出し元でState DBの更新を行っており、呼び出し先でのクエリがValidationで引っかかった場合には、呼び出し元のState DB更新も含めてセットで無効になります。

ただし、もちろん呼び出し先のChaincodeも当該実行Peerにインストールされていることが必要です。この点は他のカテゴリでも共通です。

2. 呼び出し先が同一Channel×更新あり

なんとこれも特に制約なく扱えます。読みとりのときと同様、呼び出し先と呼び出し元のトランザクションコンテクストが同一にできるからですね。呼び出し元、呼び出し先双方でState DBの更新を行っているような場合にも、呼び出し先での更新だけ成功して呼び出し元の更新は無効、といったことは起きず、ちゃんと両方セットで有効/無効が評価されます。

3. 呼び出し先が別Channel×読み取りのみ

別Channelであることに伴う制約、注意点が増えます。まずわかりやすいものとしては当該実行Peerに呼び出し先のChaincodeのChannelのReader権限がないと動きません。Reader権限がない=そのChannelの台帳持ってない、なので当然の話ですね。

2点目、呼び出し先でのクエリについては、Validationのための情報であるRead Setとクエリ条件がトランザクションコンテクストに保持されません。つまり、呼び出し先でのクエリはRead Set ValidationもPhantom Read Validationもされないという制約です。

そして2点目と合わせて気をつけておきたいのが、そもそもHLFは仕組み上、トランザクションがState DBに反映される順序はChannelごとにしか保証されていないということです。例えば:

  • あるChannel ChX上でChaincode C1を実行し、このC1中では別のChannel ChYのState DBを読み取るChaincode C2が呼び出されている
  • このC1の実行を複数Peerに依頼したうち、Peer P1ではChannel Y上のトランザクションTxY3が反映されている状態でC1が実行された一方で、Peer P2にはTxY3はまだ反映されていない状態で実行された
  • なのでP1とP2でのChYクエリは異なる結果になった +( P1とP2からのEndorsementに含まれるRW-Setが異なるのでクライアントアプリケーションが困る)

というようなことが起こり得ますね。注意しましょう。

これらの制約、注意点はありますが、呼び出し先の台帳はそんなに頻繁には更新されないなどの条件が満たせればそこそこ安全かつ便利に使えると思います。

4. 呼び出し先が別Channel×更新あり

なんと、これはできません

呼び出し先でputState()を使うことはできるんですが、Write Setが保持されないのですなわちこのputState()で書き込んだ値が台帳に反映されることはありません。意味ないのでやめましょう。これがこの記事のオチです