はじめに

こんにちは。ブロックチェーン推進室の鈴木です。

私の所属するブロックチェーン推進室では、企業間取引に特化したエンタープライズブロックチェーンプラットフォーム:Cordaに関して機能調査を行っております。
Cordaは世界で350社を超える金融機関、規制当局、中央銀行、業界団体、システム・インテグレーターやソフトウェアベンダーにより構成されるR3エコシステムから、エンドユーザー目線で設計・開発されています。
また、4半期に1度のバージョンアップにより機能追加がされていますが、その機能がお客様に活用できるものなのか、ドキュメントだけではわからないことも多く、ブロックチェーン推進室独自で調査を進めています。

このブログでは、そういったCordaの機能調査をした結果を機能の使い方含めて紹介していきます。

Cordaの導入方法についてはこちらのドキュメントをご参照ください。

今後以下の内容で紹介しようと思います。

  1. Corda JMeterの使い方

  2. Corda Enterprise Performance検証 ノードスペックを変化することによる性能変化

  3. Corda Tokens SDK and Accounts

  4. Corda Enterprise Collaborative Recovery(←今回はこちらの記事)

  5. Corda Enterprise Archive Service

  6. Corda Firewallの設定方法

  7. Corda re-issuance Tokens SDK and Accountsを利用した一括「re-issuance」

  8. Corda Time-windows

  9. Corda Oracle

  10. Corda attachment

  11. Corda Webアプリケーション開発について

  12. Cordaを用いたNonFungibleTokenの実装

  13. CordaのTokens SDKでNFTマーケットプレイスを作ってみた

(以降、バージョンアップの都度、新機能を紹介予定)

第4回目は「Corda Enterprise Collaborative Recovery」について紹介します。

概要

Collaborative RecoveryとはCorda Enterprise4.5で新しく追加された機能です。
Collaborative Recoveryはノード間で発生したトランザクション差異の検出、及び回復を実施するツール群の総称を指します。
指定したノード間でのトランザクションの差異を検出するLedgerSyncや、台帳のデータの差異を修正し整合性を取るLedgerRecoverの2つのCordappから構成されます。
障害などでDBが破損し、バックアップから回復した後、相手ノードと整合性を保つことを目的に開発されたツールになります。

参考:Coolaborative Recovery

◆ LedgerSync

LedgerSyncとは、同じネットワークに存在する相手ノードと共有するトランザクションデータに差異がないかを検知するCordappです。
本来共有しているはずのトランザクションデータが欠落している場合、欠落したデータを検知できます。この検知する行為をReconciliation(リコンサイル)と呼びます。
例えば、破損したDBをバックアップから復旧しても、バックアップを取得した後の取引内容は欠落している状態となります。LedgerSyncを使用すると、その欠落したトランザクションデータを発見できます。

◆LedgerRecover

LedgerRecoverは、欠落したトランザクションデータを復旧して整合性を保つCordappです。LedgerSyncで検知した欠落情報を相手ノードから取得して不足したレコードをDBの各種テーブルにインサートします。
復旧方法は、手動回復と自動回復の2パターンがあります。
手動回復は相手ノードで回復ファイルの出力を依頼し、それを自ノードにインポートするといったファイルのやり取りが発生しますが、自動回復はフローを流すだけで回復できます。
通常、自動回復でデータの復旧は可能ですが、差分データが大量にある場合は手動回復を選択する場合があります。

Collaborative Recoveryの使い方

前提条件として、ノード構成は以下の2台です。

・ノードA(O=NodeA,L=Tokyo,C=JP)

・ノードB(O=NodeB,L=Osaka,C=JP)

ノードAにトランザクションデータの欠落があり、ノードBのDBは整合性が取れている前提とします。

1.自動回復の方法

手順1:LedgerSyncのリコンサイルを実施。
ノードAで以下のフローを実施します。

flow start ScheduleReconciliationFlow parties: 
["O=NodeB,L=Osaka,C=JP"]

実行結果

? Starting

?? Done

Flow completed with result: kotlin.Unit

手順2:検知結果を表示。
ノードAで以下のフローを実施します。

flow start GetReconciliationStatusesFlow isRequester: true

isRequesterの引数は、自ノードが要求したリコンサイルの結果を表示したい場合はtrue、他ノードから要求されたリコンサイルの結果を表示する場合はfalseを渡します。

実行結果

? Starting

?? Done

Flow completed with result: ReconciliationStatus(

isRequester=true,

lastReconciliationError="",

lastReconciliationStatus=DIFFERENCES_FOUND,

lastReconciliationTimeFinished=1599645372628,

lastReconciliationTimeStarted=1599645371325,

lastSuccessfulReconciliationResult=[

FC04891F691C5B6BB329E696EE122ACB6B80137DA69B1975F110F22AB1783D6F,

A0E0D3BC4C382BEDF4B40FB006ADBCD30598049FFC99AE4F469AD2D8F96F59F6

],

lastSuccessfulReconciliationStatus=DIFFERENCES_FOUND,

lastSuccessfulReconciliationTimeFinished=1599645372628,

lastSuccessfulReconciliationTimeStarted=1599645371325,

partyName="O=NodeB, L=Tokyo, C=JP",

stateMachineRunId="8cd93567-eca1-4fff-88b5-4774cc86ec14"

)

このフローにより欠落したトランザクションが表示されます。

手順3:LedgerRecoverの自動回復を実施。
ノードAで以下のフローを実施します。

flow start AutomaticLedgerRecoverFlow party: "O=NodeB, L=Osaka, C=JP"

実行結果

? Starting

?? Done

Flow completed with result: kotlin.Unit

正常終了すれば、欠落したトランザクションデータがDBに書き込まれます。

【自動回復の注意点】

欠落したデータ量が多いためタイムアウトするといったエラーが発生した場合、フローを手動で強制終了し、cr_reconciliation_statusテーブルのlast_reconciliation_statusをIN_PROGRESSからFAILEDに更新する必要があります。

2.強制終了の手順

手順1:state-machine-idの取得。
ノードAで以下のフローを実行して取得します。

flow start GetCurrentRecoveryRequestWithPartyFlow party: 

"O=NodeB,L=Osaka,C=JP", isRequester: true

手順2:フローの強制終了。
ノードAで以下のフローを実施します。

run killFlow id: <state-machine-id>

state-machine-idには強制終了の手順1で取得した値を入れます。

手順3:ステータスの更新。
ノードAで以下のフローを実施します。

flow start FailAutomaticRecoveryFlow requestId:<recoveryRequestId>, failReason: 
"任意の文字列"

(例)
flow start FailAutomaticRecoveryFlow requestId: 
046dca79-81f9-4b8f-b3a5-0990949b3e12, failReason: "Operator intervention."

3.手動回復の方法

手順1:リコンサイルを実施。
自動回復の手順1と同じです。DBに差分があるかを確認します。

手順2:検知結果の表示。
自動回復の手順2と同じです。

手順3:LedgerRecoverの手動回復の初期化を実施。
ノードAで以下のフローを実施します。

flow start InitiateManualRecoveryFlow party: "O=NodeB,L=Tokyo,C=JP"

実行結果

? Starting

?? Done

Flow completed with result: RecoveryRequest(

recoveryID=5a3703d0-cbce-4df8-ae75-2f84329e9ff4, 

party=O=NodeB, L=Tokyo, C=JP, 

isRequester=true, 

timeStarted=1599645397360, 

timeFinished=null, 

requestedTransactionIDs=[

FC04891F691C5B6BB329E696EE122ACB6B80137DA69B1975F110F22AB1783D6F, 

A0E0D3BC4C382BEDF4B40FB006ADBCD30598049FFC99AE4F469AD2D8F96F59F6

], 

recoveryStatusFlag=REQUESTED,

failureReason='', 

isManual=true, 

stateMachineRunId=null

)

この時、recoveryIDを記録しておきます。

手順4:相手ノードでリカバリーファイルを出力。
ノードBで以下のフローを実施。この時requestIdには手順3のrecoveryIDを記載します。pathにはファイルが出力されるパスを記載します。

flow start ExportTransactionsFlow requestId: 
"5a3703d0-cbce-4df8-ae75-2f84329e9ff4", path: "/home/corda/NodeB/collaborative-recovery/export"

実行結果

? Starting

?? Done

Flow completed with result: kotlin.Unit

フローを実施した結果、下記画像のようにrecoveryIDが名前のディレクトリが生成されます。 ディレクトリ内には回復に必要なファイルが生成されています。

手順5:出力されたリカバリーファイルを自ノードにインポート。
手順4で出力されたディレクトリを丸ごとNodeAにコピーします。

手順6:手動回復の実施
ディレクトリのコピーが完了したら、ノードAで以下のフローを実施。requestIdには手順3のrecoveryIDを記載します。pathはディレクトリがコピーされたパスを記載します。

flow start ImportTransactionsFlow requestId: 
"5a3703d0-cbce-4df8-ae75-2f84329e9ff4", path: "/home/corda/NodeA/collaborative-recovery/import/"

実行結果

?? Starting

Done

フローが正常に終了すれば回復は完了です。

Collaborative Recoveryの検証~どのようなパターンが復旧できるか~

Collaborative Recoveryではどのようなパターンが復旧できるのか、また復旧できないのかについて検証しました。
具体的にはトランザクションに関係あるデータベーステーブルのデータを直接消したり、書き換えたり、いわゆる改ざんを行い、Collaborative Recoveryで検知、もしくは復旧できるかを確認しました。

以下の手順で確認しました。

手順1.通常ノード(ノードA)から通常ノード(ノードB)、オブザーバー、Notaryに対してトランザクションを発行。

手順2.ノードAまたはオブザーバー、Notaryのデータベーステーブルのレコードを直接SQLにて変更、または削除を実施。

手順3.ノードAでCollaborative Recoveryを実施。

対象となるテーブルはトランザクションに関係あるデータが含まれている以下の4つに絞りました。

・node_transactions

・vault_states

・vault_linear_states

・token_states(独自のスキーマで生成されたテーブル)

【結果】

結果としては、以下の通りです。

表内の各テーブル列にある「〇」は書き換え、削除をしないことを指します。

Collaborative Recoveryの検証パターンと結果

検知できるパターンは、LedgerSyncの比較相手が通常のノードでnode_transactionテーブルのレコードが削除されている時のみでした。
その他のパターンでは検知されることはありませんでした。
また、回復できるパターンは、対象の全テーブルから、同じTxIDのレコードが削除されている必要があります。
理由として、回復時に欠落したレコードがインサートされますが、対象テーブルのいずれかに同じtx_idを持つレコードが存在していると主キー制約でエラーが発生するためです。
オブザーバーノード、ノータリーノードについてはnode_transactionテーブルのレコードを削除してもLedgerSyncで検知できませんでした。

最後に

今回、Collaborative Recoveryの使い方と検知・回復できるパターンを調査しました。
所感としては、検知できるパターンが思ったよりも少なく、DBが中途半端な状態(一部のレコードが残っているなど)だと主キー制約のエラーが発生して回復できないなど扱いにくい点が見受けられました。
Collaborative Recoveryの目的としては、「バックアップから回復した後に整合性を保つ」為に設計されているため、このような結果となります。
もちろん、想定される状態(全テーブルから対象データが欠落している)であれば問題なく回復できることは確認済みです。
1ノードにおけるデータの改ざんや削除には対応していないため、自ノードでの管理者による改ざん検知や復旧は別のやり方を検討しなければなりません。
現時点においてAccounts機能で生成されたアカウント情報は回復しないなど完全に回復できる訳ではありません。
基本的にCordaはRDBが利用できる為、RDBのバックアップリカバリ運用を確実に実施するのが基本となります。それでも復旧できなかった場合の最終手段としてCollaborative Recoveryの利用を検討するべきと考えます。

今回は以上になります。