2020.01.09
Plasmaフレームワークのアーキテクチャの紹介
こんにちは。次世代システム研究室のL.W.です。
Plasmaフレームワークというと、システムとしてどのような仕組みだかっていう疑問を持つ方がいるかと思います。
今回はオーバービューで、plasma-rust-frameworkのOSSをベースに、Plasmaの仕組みを覗いていきましょう。
Plasmaフレームワークは、少なくとも三つのコンポーネントが必要です。

クライアント(Client)
エンドユーザ、オペレーター、イーサリアムのスマートコントラクトと対話(インターアクション)する機能を持つコンポーネントです。
ウォレット管理、トランザクション管理、ステート管理、スマートコントラクトとのやり取り(deposit、Exit、Challengeなど)、自分と関わるデータのローカル保持などを行います。
オペレーター(Operator)
クライアント、イーサリアムのスマートコントラクトと対話(インターアクション)する機能を持つコンポーネントです。
クライアントのトランザクションをチェック処理し、一定の時間で集めたトランザクション、ステートをインプットとして、Merkle Treeを構築し、Merkle TreeのRootをイーサリアムに提出します。
全てのデータをローカルに保持し、フルノードの存在となります。
スマートコントラクト(Smart Contract)
レイヤ1でデプロイされます。
disputeロジック実現できたので、レイヤ1と同様のセキュルティーが裏付けます。
クライアント(Plasma-Client)
全体的な構図は以下のようとなります。

RpcServer
RpcServerは、エンドユーザーが操作できるJSON RPCサービスを提供します。
これらのリクエストを満たすためには、他のさまざまなRPCメソッドを呼び出します。
ウォレットアカウントの作成、トークンの残高の表示、支払履歴の表示、送金、スワップのトランザクションの作成などのエンドユーザーからのリクエストは想定されます。
RpcClient
RpcClientは、他のクライアントおよびオペレーターとのインターアクションを行います。
Plasmaクライアントの場合、オペレーターに新規作成したトランザクションを送ったり、オペレーターからブロックコンテンツあるいはMerkle Treeの存在証明などを求めたりなどを行います。
State Channelクライアントの場合、他のクライアント同士とのメッセージ交換などを行います。
OVM Runtime
OVMはあくまでバーチャルマシンであり、バーチャルマシンの役目はインストラクションを実行することです。 そのためには、OVMはインストラクションのベースインターフェイスを定義する必要があります。 OVMはレイヤ2からのクレームとそのクレームの紛争に完全に基づいているため、そのインストラクションはすべて「入力NがルールセットSに従って有効かどうかを判断する」という形式に従う必要があります。
こうするために、OVM RuntimeにはOVMならではのプロパティ(Property)、ステート(StateUpdate)、トランザクション(Transaction)の概念が定められています。
Property
struct Property { pub decider: Address, // L1のスマートコントラクトのアドレス。対応するコントラクトはインプットを真/偽か判定できます。 pub inputs: Vec<PropertyInput>, // レイヤ2のローカル情報であり、判定される対象となります。 }
StateUpdate
struct StateUpdate { block_number: Integer, // ブロック番号 deposit_contract_address: Address, // トークンアドレス range: Range, // 紐づいているレンジ property: Property, // プロパティ }
Transaction
struct Transaction { deposit_contract_address: Address, // トークンアドレス range: Range, // レンジ parameters: Bytes, // プロパティ signature: Bytes, // 署名後のメッセージ(トークンアドレス、レンジ、プロパティ) metadata: Metadata, // メタデータ(トランザクションの性質を定義する) }
StateUpdateとTransactionの関係
ownershipの場合を例として挙げます。

EventWatcher
EventWatcherは、Ethereumのさまざまな重要なイベントを監視します。 他のコンポーネントは、EventWatcherに特定のイベントを監視するようにリクエストでき、イベントが発生するたびに通知されます。イベントを見逃したり、同じイベントをコンポーネントに複数回通知したりしないように設計されています。
クライアントには、オペレーターのブロック提出したイベント(BlockSubmitted)、他のクライアントのExitのリクエストしたイベント(ExitStarted)などを監視しないといけないです。
オペレーターには、クライアントのExitのリクエストしたイベント(ExitStarted)、入金デポジットしたイベント(Deposited)などを監視しないといけないです。
ContractWrapper
ContractWrapperは、イーサリアムのスマートコントラクトの単純なラッパーです。
各コンポーネントはイーサリアムとのインターアクションはContractWrapper経由となります。
DB
このPlasma Rustフレームワークでは、LevelDBは使われています。
LevelDBはkey-value型のデータストアの一つです。イーサリアムもこれを利用します。
イメージとして、以下のようになります。

データシリアライズするために、RLPエンコーディングを採用しました。
Plasma Rustの場合、Rangeベースにステート、トランザクション、Merkle Treeの存在証明のCRUDは頻繁に行われるなので、設計では工夫されました。
StateDB
ステート(ブロック番号、トークンアドレス、プロパティ)に関する情報を保存します。
Key: deposit_contract_address + Range(start, end).end
Value:
struct StateUpdateRecord { block_number: Integer, deposit_contract_address: Address, property: Property, }
上の「StateUpdateとTransactionの関係」の例では、StateDBの変化は以下のようとなります。

TransactionDB
トランザクション(トークンアドレス、レンジ、パラメータ、サイン、メタデータ)に関する情報を保存します。
Key: 参照されたステートのblock_No. + Range(start, end).end
Value:
struct Transaction { deposit_contract_address: Address, range: Range, parameters: Bytes, signature: Bytes, metadata: Metadata, }
上の「StateUpdateとTransactionの関係」の例では、TransactionDBの変化は以下のようとなります。

RangeAtBlockDB
Plasma RustではMerkle Interval Treeを採用しました。

あるステートはブロックに存在するかの証明情報(Merkle TreeのRoot、チェック結果、証明情報、対象のステート)を保存します。
Key: 新規作成されたステートのblock_No. + deposit_contract_address + Range(start, end).end
Value:
struct RangeAtBlockRecord { pub root: Bytes, pub is_included: bool, pub inclusion_proof: Bytes, pub state_update: StateUpdate, }
上の「StateUpdateとTransactionの関係」の例では、Range(0,5)のステートをUTXOとしてDBに更新する。

SignedByDB
署名されたメッセージ情報(パブリックキー、メッセージ、署名)を保存します。
Key: PublicKey + Hash(Message)
Value:
struct SignedByRecord { pub public_key: Address, pub message: Bytes, pub signature: Bytes, }
トランザクション毎にSignedByDBを追加します。

WalletManager
エンドユーザー向けに、ウォレットを管理するコンポーネントです。
例えば、Public keyとPrivate keyのペアの生成、Private Keyのインポートなどを行います。
WalletDB
セッションとPrivate Keyのペアを保存します。
Key: セッション
Value: Private Key
EventDB
イーサリアム発火したイベントと関する情報を保存します。
オペレーター(Plasma-Operator)
全体的な構図は以下のようとなります。

RpcServer
クライアントのリクエストに応じて、レスポンスを送ります。
例えば、クライアントはイーサリアムからBlockSubmittedというイベントを受信したら、オペレーターにブロックコンテンツをリクエストし、
誠実なオペレーターの場合、正しいトランザクション、ステートの含めたブロックコンテンツを送付します。
トランザクションのMerkle Tree存在証明を送ったり、クライアントからのトランザクションをブロードキャストしたりすることもあります。
BlockManager
トランザクション、ステートをDBに一時的に保存したり、まとめたステートをleafとして、Merkle Treeを構築したりします。
ContractWrapperモジュールを呼び出して、イーサリアムに接続してから、Merkle TreeのRootを提出します。
イーサリアムのOVM ContractにRootはきちんと登録されたら、BlockManagerはブロックコンテンツをDBに永久に保存します。
BlockDB
ブロックに関する情報を保存します。
Key: Block番号
Value:
struct PlasmaBlock { block_number: Integer, state_updates: Vec<StateUpdate>, // ブロックに含めたステート transactions: Vec<NewTransactionEvent>, // ブロックに含めたトランザクション tree: Option<DoubleLayerTree>, // Merkle Interval Tree }
送金のシナリオ
AliceはBobに5ETHを送金することとします。

クライアントのAliceサイド
1.Aliceは送金アカウントを作ります。
WalletManagerとWalletDBモジュールが使われます。
2.ステートでのRangeの残高を確認する
OVM RuntimeモジュールからStateDBで5ETHあるいは5ETH以上の持つState(UTXO)を検索します。
検索結果がない場合、送金不能となります。
検索結果が出たら、このStateをUTXOとしてトランザクションを新規に作成します。
3.トランザクションの作成、署名、発送
ウォレットのPrivate Keyでトランザクションを署名し、RpcClientモジュールを通して、オペレーターに送ります。
オペレーターサイド
1.トランザクションのチェック処理、保持処理を行います。
このトランザクションはオーナー本人からの署名だか、二重払いないかをチェックを行います。
問題がなければ、OVM RuntimeモジュールよりStateDB、TransactionDB、SignedByDBを更新します。
不正が発見できれば、廃棄します。
同時ににこのトランザクションとこのトランザクションの含めたのステートをBlockManagerモジュールによりBlockDBで一時的に保持されます。
2.トランザクションのブロードキャストを行います。
受け取る側のBobを受信できるように、このトランザクションをブロードキャストを行います。
3.ブロックのMerkle Treeの構築とRootの提出を行います
一定の時間間隔あるいは集まったトランザクションは上限値を超えた場合、BlockManagerモジュールにより一時的に集めたトランザクションとステートをインプットとしてMerkle Treeを構築します。
ContractWrapperモジュールを通して、イーサリアムのOVM Contractに提出します。
BlockDBで一時的に保持されたトランザクションとステートをクリアします。
クライアントのBobサイド
1.イベントが監視されて、オペレーターにブロックコンテンツをリクエストします。
EventWatcherモジュールにより、BlockSubmittedというイベントを監視できたら、RpcClientモジュールよりブロックコンテンツをリクエストします。
2.トランザクションのチェック処理を行います。
トランザクションの正しさとトランザクションの含めたステートの履歴をチェック処理を行います。
問題がある場合(不正署名、不正履歴)、取引は失敗となります。
3.ローカルDBのアップデートを行います
ライトクライアントの場合、自分と関わるトランザクション、存在証明情報だけをローカルに保持します。
ライトクライアントではない場合、全てのトランザクション、存在証明情報をローカルに保持します。(現状のPlasma Rustはこのケースに該当します。)
StateDB、TransactionDB、RangeAtBlockDB、SignedByDBをアップデートをします。
4.取引は成立となります。
ファイナリティとみなされる場合、取引は成立となります。
実際にはPlasmaの実質的なファイナリティはイーサリアムに頼りますが、Cryptoeconomic finality という案でファイナリティを短縮できる研究は進んでいます。
スマートコントラクト(OVM Smart-Contract)
レイヤ1が得意なことだけにレイヤ1を活用し、スマートコントラクトは明確に定義されたルールセットに基づいて、不変で全会一致のコンセンサスを達成することです。
OVM Contractではレイヤ2のクレームの裁定に使用する明確なルールセットが定義されています。
裁判官のように、ルールが常に守られていることを保証するためにすべてを積極的に監督することより、ルールが守られていないという主張(クレーム)を遡及的に処理するために使用する方がはるかに効率的です。
全体的な構図は以下のようとなります。

ソースはまだ未完成かと思いますが、こちらも解読中です。
ただ、ロジックは複雑ですが、セキュリティー問題が伴っているかと思います。監査(Audit)はどのようにしますか、興味深いですね。
今回はスマートコントラクトの詳しい解説を割愛させて頂きます。
submitRoot
Merkle TreeのRootをスマートコントラクトのストレージに保持します。同時にPlasmaチェーンのブロック番号を割振りします。
イーサリアムのブロックに無事に含めば、BlockSubmittedのようなイベントを発火します。
deposit
ETHあるいはERC-20を入金します。
入金金額により、Rangeを割振りします。源のステートを生成します。
finalizeExit
ExitのプロパティのクレームのステータスはTrueと判定できれば、イーサリアムのアカウントに出金します。
finalizeCheckpoint
challenge
不正クレームにチャレンジします。
claimProperty
decideClaimToTrue/decideClaimToFalse
decideTrue/decideFalse
setPredicateDecision
isValidChallenge
verifyInclusion
まとめ
Plasma Rustはまだ開発中に見えますが、Todoもまだ沢山が残っています。
読めない部分もあれば、こちらの理解間違い部分もありますが、皆さんのこのOSSへの理解に一助となれば幸いと思います。
これからはOVM contractの進展を見守って行き、心得ができれば、また共有します。
さいごに
次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。
皆さんのご応募をお待ちしています。
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD