まず、この動画がオススメ
blockchain.tokyo #22 Quorum & Microsoft Azure Blockchain!!
Quorumとは
Ethereumのコンソーシアム版。(※Hyperledger版については触れません)
もともと、JPモルガンがEthereumをforkして開発していた。それをConsensysというオープンソースラブな会社が買い取った。ことで、単なる一企業のBlockchainという枠に囚われない、むしろ、エンタープライズ領域で幅広く使われるんじゃないかと思えるような将来性溢れるChainになった。
特徴
- プライベートトランザクションがある
- 特定のグループ間でのみ閲覧可能なトランザクション
- パブリックに公開したくない情報を秘匿できる
- ネットワーク接続の認証機能をサポート
- 信頼のおけないNodeを除外したクローズドなネットワークを構築できる
- セキュアなコンソーシアムチェーンを構築するための機能
- EthereumのSmart Contractと互換性が高い
- Ethereum向けに開発したContractをそのまま使える
- コンパイラはEthereumのものを使う
- スケーラブル
- コンソーシアムチェーンなので当然TPSは高い。
- 要因は、重厚なコンセンサスを必要としない(信頼のおけるメンバーだけなので)ことと、参加人数が少ないことと
- トランザクション手数料がない
- トランザクション手数料は0に設定される
システム構成
Quorum_Architecture_20171016.pdf
Blockchain部分はコンセンサスを実行する「Quorum Node」とprivateトランザクションを作る「Tessera」の2つに別れている。
まず、Quorum Nodeはgo-ethereumのforkでNode同士のP2P通信や、後述する2つのコンセンサスを実行する。publicトランザクション用とprivateトランザクション用の2つのストレージを持つ(実際は共有していてtrieが違うだけっぽい)ストレージはLevelDB。
つぎに、Tesseraはprivateトランザクションの署名や暗号鍵の管理、暗号化された秘匿情報の格納と共有を行う。
トランザクションフロー
簡単に説明すると、Nodeに送られたprivateトランザクションは、Tesseraによって暗号化&ハッシュ化されて、ネットワーク全体で共有される。暗号化された実データはTesseraによって、共有対象の相手のみへ送られる。
コンセンサス
次の2つから選べる
Raftコンセンサス
単一のリーダーNodeがブロックを生成する。リーダーNodeがダウンした場合は、別のNodeがリーダーNodeに選出される。ダウンしたかどうかは、時間で管理されており、一定時間経過しても応答のない場合は新たなリーダーが選出される。
特徴は高速であること。まず、リーダーが単独でブロックを生成するので他Nodeと強調するオーバーヘッドが存在しない。つぎに、トランザクションが来たらほぼすぐ(設定できる)にブロック生成されるので、待ち時間が存在しない。
IBFT(Istanbul Byzantine Fault Tolerant)コンセンサス
PBFTのようなもの。ランダムにProposerがブロックを生成し、Validatorが検証して一定以上の投票を得た場合はファイナライズされる。
特徴はRaftよりも分散でること。一定時間ごとにブロックが生成されるので、空ブロックが存在する。
ネットワーク認証
ベーシックな認証
ホワイトリストに登録されたNodeのみ接続を許可する。
起動時に--permissioned
オプションを指定すると有効となり、設定ファイル(<data-dir>/permissioned-nodes.json
)に記述されたNodeとのみ通信可能となる。
参考: Basic Network Permissioning
※このように公式ドキュメントには書いてあるが、実装は違うっぽい💦
実際にオプションを付けて動かしてみると、エラーが出る。解決するには、以下の高度な認証が必要。つまり、ベーシックな認証単体では動作しない。
高度な認証
Enhanced Permissions Model
AWSのようなグループとロールベースの認証。
スマートコントラクトを使っている。Proxyパターンを採用しており認証ルールのアップデートが可能。コードを読むと、ベーシックな認証の設定ファイル(permissioned-nodes.json)に基づいてアクセス制御をしているっぽい。スマートコントラクトのイベントを監視して、必要に応じて設定ファイルを更新している。
おまけ(実装にDeep Dive)
Raftコンセンサス起動
- Gethを起動しフルノードを作る
0. geth(ctx *cli.Context) - Gethの起動オプションに
--raft
が入っていればRaftコンセンサスのサービスを登録する
3. makeFullNode(ctx *cli.Context) - Raftサービス登録時にRaftコンセンサスのインスタンスを
New
する
4. RegisterRaftService(stack *node.Node, ctx *cli.Context, nodeCfg *node.Config, ethChan chan *eth.Ethereum) - Newしたときにminterを作る
5. New(ctx *node.ServiceContext, chainConfig *params.ChainConfig, raftId, raftPort uint16, joinExisting bool, blockTime time.Duration, e *eth.Ethereum, startPeers []*enode.Node, datadir string, useDns bool) - blockを新規作成する処理を非同期実行する
6. newMinter(config *params.ChainConfig, eth *RaftService, blockTime time.Duration) - 新しいトランザクションが来るか(txPreChan)、新しいブロックが掘られたら(chainHeadChan)、一定時間(blockTime)だけ待って、ブロックを作る
7. mintingLoop()
Private Transactionの送信
- RPCでトランザクションをNodeへ送信
- NodeのSendTransactionメソッドが呼ばれる
3. SendTransaction(ctx context.Context, args SendTxArgs) - TesseraのAPIを呼び出し、payloadを暗号化したもののハッシュ値を取得する
4. setPrivateTransactionHash(sendTxn bool) - Private Transactionであるというフラグをセット(署名のv値を37か38に設定。なおPublic Transactionの場合は27か28)
6. SetPrivate() - Transactionプールに追加
8. SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction)
Private Transactionの実行
-
Raftコンセンサスのmintループで、新しいブロックが追加するメソッドが呼ばれる
2. mintingLoop()
3. mintNewBlock() -
transactionを取得し、コミットするメソッドが呼ばれる
-
transactionを実行するメソッドが呼ばれる
7. ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb, privateState *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) -
transactionを実行するstateを選び(private transactionと通常のトランザクションは同じストレージを共有してるが、merkle木が違うっぽい)EVMを初期化し、transactionを適応する
9. NewEVM(ctx Context, statedb, privateState StateDB, chainConfig *params.ChainConfig, vmConfig Config)
10. ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error)
認証の有効化
- Gethの起動オプションに
--permissioned
が入っていれば認証サービスを登録する
3. makeFullNode(ctx *cli.Context)
4. IsPermissionEnabled()
5. RegisterPermissionService(stack *node.Node) - startNodeメソッド中で、サービスのコールバックが呼ばれる
6. startNode(ctx *cli.Context, stack *node.Node)
7. AfterStart() - 高度な認証でデプロイしたコントラクトのイベントを監視する
9. updatePermissionedNodes() - イベントが発行されたら設定ファイル(permissioned-nodes.json)を更新する
11. updatePermissionedNodes(enodeId string, operation NodeOperation)
接続ノードの認証
- ノードとの接続時に設定ファイル(permissioned-nodes.json)を読み込み、リストに含まれているかチェック
3. setupConn(c *conn, flags connFlag, dialDest *enode.Node)
4. isNodePermissioned(nodename string, currentNode string, datadir string, direction string)
5. ParsePermissionedNodes(DataDir string)