🐵

Hyperledger Besu を動かしてみる(IBFT 2.0編)

2022/03/09に公開

はじめに

今回はコンセンサス・アルゴリズムとして IBPF 2.0 を使って Besu を動かしてみたい。前回は何も考えず PoW で動かしていたが、コンセンサスアルゴリズムが変わると Besuの設定や動作がどうなるのか気になるところである。

なお、だんだん Hyperledger Besu と書くのがめんどくさくなったので、Besu と書きます。

IBFT 2.0 とは

Istanbul Byzantine Fault Tolerance (イスタンブール・ビザンチン・フォルト・トレランス)の略で、PoA(プルーフ・オブ・オーソリティ)コンセンサスアルゴリズムの一種らしい。なぜイスタンブールなのかは不明。ビザンチン帝国ならコンスタンチノープルの方がいい気がするが、そういう話ではないのだろう。

IBFT 2.0の特徴としては、ビザンチン耐性が高いため、競合関係にある他社や、不正アクセスの危険があるノードを含むような信頼のできないネットワークに向いているとのこと。完全なプライベートチェーンよりも、複数の企業によるコンソーシアム型のチェーンに向いているのかもしれない。

参考サイト

https://besu.hyperledger.org/en/stable/Tutorials/Private-Network/Create-IBFT-Network/
https://besu.hyperledger.org/en/stable/HowTo/Configure/Consensus-Protocols/IBFT/
https://consensys.net/blog/news/another-day-another-consensus-algorithm-why-ibft-2-0/

IBFT 2.0

バリデータ

IBFT2.0 ネットワークでは、バリデータと呼ばれる承認済アカウントが取引とブロックを検証する。バリデーターは交代で次のブロックを生成し、バリデータの大多数(66%以上)がブロックに署名する必要がある。

バリデーターはネットワークに常に2/3以上いなければならない。バリデータの2/3以上が参加しなくなると、新しいブロックが作成されなくなり、ネットワークが停止する。そうなると、回復にかなりの時間がかかる。

実際にはバリデータの最小数は4となる。逆に、最大数は決まっていないが、バリデータの数が増えるほど、パフォーマンスは劣化するので多ければ多いほどいいというわけではない。検証の結果として、バリデータが30まではパフォーマンスの劣化はなかったとのこと。そしてバリデータ以外のノードがパフォーマンスに影響することはない。

既存のバリデータは、バリデータの追加や削除の提案と投票を行う。提案の実施にはバリデータの過半数(50%以上)の賛成を必要とする。

Genesis ファイル

当然だが、IBFT 2.0を使うには IBFT 2.0用のGenesisファイルが必要である。

Genesis ファイルの例

以下はGenesisファイルの例であるが、詳細は上記の公式ドキュメントを参照のこと。

{
    "config": {
      "chainId": 1981,
      "muirglacierblock": 0,
      "ibft2": {
	"blockperiodseconds": 2,
	"epochlength": 30000,
	"requesttimeoutseconds": 4,
	"blockreward": "5000000000000000",
	"miningbeneficiary": "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"
      }
    },
    "nonce": "0x0",
    "timestamp": "0x58ee40ba",
    "extraData": "0xf83ea00000000000000000000000000000000000000000000000000000000000000000d594c2ab482b506de561668e07f04547232a72897daf808400000000c0",
    "gasLimit": "0x1fffffffffffff",
    "difficulty": "0x1",
    "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
    "alloc": {}
  }

プライベートネットワークの構築

今回教育用として IBFT 2.0を使ったプライベートネットワークを構築する。ビザンチン耐性のため4つのノードを立ち上げるが、その全てがバリデータとなる。

1. ディレクトリの作成

4つのノードそれぞれデータディレクトリが必要なので作成する。

$ mkdir IBFT-Network; cd IBFT-Network
$ mkdir -p Node-1/data
$ mkdir -p Node-2/data
$ mkdir -p Node-3/data
$ mkdir -p Node-4/data
$ tree IBFT-Network/
IBFT-Network/
├── Node-1
│   ├── data
├── Node-2
│   ├── data
├── Node-3
│   ├── data
└── Node-4
    ├── data

2. 設定ファイルの作成

設定ファイルには IBFTのGenesisファイルとノード数分のキーペアを定義する。

{
 "genesis": {
   "config": {
      "chainId": 1337,
      "muirglacierblock": 0,
      "ibft2": {
	"blockperiodseconds": 2,
	"epochlength": 30000,
	"requesttimeoutseconds": 4
      }
    },
    "nonce": "0x0",
    "timestamp": "0x58ee40ba",
    "gasLimit": "0x47b760",
    "difficulty": "0x1",
    "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {
       "fe3b557e8fb62b89f4916b721be55ceb828dbd73": {
	  "privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
	  "comment": "private key and this comment are ignored.  In a real chain, the private key should NOT be stored",
	  "balance": "0xad78ebc5ac6200000"
       },
       "627306090abaB3A6e1400e9345bC60c78a8BEf57": {
	 "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3",
	 "comment": "private key and this comment are ignored.  In a real chain, the private key should NOT be stored",
	 "balance": "90000000000000000000000"
       },
       "f17f52151EbEF6C7334FAD080c5704D77216b732": {
	 "privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f",
	 "comment": "private key and this comment are ignored.  In a real chain, the private key should NOT be stored",
	 "balance": "90000000000000000000000"
       }
      }
 },
 "blockchain": {
   "nodes": {
     "generate": true,
       "count": 4
   }
 }
}

これを ibftConfigFile.json という名前で IBFT-Network ディレクトリに置く。

3. node keys とGenesisファイルの作成

$ ../besu-22.1.1/bin/besu operator generate-blockchain-config --config-file=ibftConfigFile.json --to=networkFiles --private-key-file-name=key

これで networkFiles というディレクトリの下に node keys とGenesisファイルが作成される。

$ tree .
.
├── Node-1
│   └── data
├── Node-2
│   └── data
├── Node-3
│   └── data
├── Node-4
│   └── data
├── ibftConfigFile.json
└── networkFiles
    ├── genesis.json
    └── keys
	├── 0x59b1276f7295f3dfa96d16e38b5385e233a461ab
	│   ├── key
	│   └── key.pub
	├── 0xc470b991787b76269b39f9a8971c8c2e577e0c4f
	│   ├── key
	│   └── key.pub
	├── 0xd7fac01a40d24ed463fcf2f67ee2ea708b34787b
	│   ├── key
	│   └── key.pub
	└── 0xe93f819df50df397cff7986f74a7aba6644bf0ef
	    ├── key
	    └── key.pub

4. genesis ファイルのコピー

作成された genesis.json ファイルを IBFT-Network ディレクトリにコピーする。

$ cp networkFiles/genesis.json .

5. node private keys のコピー

作成された node private keys を各ノードのディレクトリにコピーする。

$ cp 0x59b1276f7295f3dfa96d16e38b5385e233a461ab/* ../../Node-1/data/
$ cp 0xd7fac01a40d24ed463fcf2f67ee2ea708b34787b/* ../../Node-2/data/
$ cp 0xc470b991787b76269b39f9a8971c8c2e577e0c4f/* ../../Node-3/data/
$ cp 0xe93f819df50df397cff7986f74a7aba6644bf0ef/* ../../Node-4/data/

6. ブートノードとして最初のノードを起動

Node-1 ディレクトリに移動し、Node-1を起動する。

$ ../../besu-22.1.1/bin/besu --data-path=data --genesis-file=../genesis.json --rpc-http-enabled --rpc-http-api=ETH,NET,IBFT --host-allowlist="*" --rpc-http-cors-origins="all"

起動するとメッセージの最後の方で enode URL というものが表示されるのでコピーしておく

Enode URL enode://e1f0f86fce7ed83061095d5d6e359aacc7ffe0304467d0faaf17da9cc54b3ce1fd46919537dd7ebfd8b1ed8938bb1c899cbf7fe2f3a44019fc4cd1445040647d@127.0.0.1:30303

7. Node-2の起動

次のNode-2を起動させる。--bootnodes= の部分には Node-1の Enode URLを入れる。

$ ../../besu-22.1.1/bin/besu --data-path=data --genesis-file=../genesis.json --bootnodes=enode://e1f0f86fce7ed83061095d5d6e359aacc7ffe0304467d0faaf17da9cc54b3ce1fd46919537dd7ebfd8b1ed8938bb1c899cbf7fe2f3a44019fc4cd1445040647d@127.0.0.1:30303 --p2p-port=30304 --rpc-http-enabled --rpc-http-api=ETH,NET,IBFT --host-allowlist="*" --rpc-http-cors-origins="all" --rpc-http-port=8546

ところが、以下のエラーで止まってしまう。

Service engineJsonRpc failed to start

To display full help:
besu [COMMAND] --help
2022-03-09 15:50:40.480+09:00 | BesuCommand-Shutdown-Hook | ERROR | Besu | Failed to stop Besu

どうやら、Node-1で起動された2つの JSON-RPC-HTTP サービスのうちの一つのポート番号がバッティングしているようだ。

Node-1で起動されたサービス

JsonRpcHttpService | Starting JSON-RPC service on 127.0.0.1:8545
JsonRpcHttpService | Starting JSON-RPC service on 127.0.0.1:8550

Node-2で起動しようとするサービス

JsonRpcHttpService | Starting JSON-RPC service on 127.0.0.1:8546
JsonRpcHttpService | Starting JSON-RPC service on 127.0.0.1:8550

最初の方はオプションで変更してあるのでバッティングしないが、2つ目のポートが特に指定がないためバッティングしている。

けれども、指定の方法が公式ドキュメントに書いてなく、やり方が不明だった。色々調べた結果、以下のオプションを追加すればうまくいった。

--engine-rpc-http-port=8551

よくわからないが、RPCのAPIには通常のやつと Engine 版があり、このように指定する必要があるようだ。

$ ../../besu-22.1.1/bin/besu --data-path=data --genesis-file=../genesis.json --bootnodes=enode://e1f0f86fce7ed83061095d5d6e359aacc7ffe0304467d0faaf17da9cc54b3ce1fd46919537dd7ebfd8b1ed8938bb1c899cbf7fe2f3a44019fc4cd1445040647d@127.0.0.1:30303 --p2p-port=30304 --rpc-http-enabled --rpc-http-api=ETH,NET,IBFT --host-allowlist="*" --rpc-http-cors-origins="all" --rpc-http-port=8546 --engine-rpc-http-port=8551

これでNode-2も無事に起動した。

8. Node-3の起動

ポート番号以外、Node-2と同じ。

$ ../../besu-22.1.1/bin/besu --data-path=data --genesis-file=../genesis.json --bootnodes=enode://e1f0f86fce7ed83061095d5d6e359aacc7ffe0304467d0faaf17da9cc54b3ce1fd46919537dd7ebfd8b1ed8938bb1c899cbf7fe2f3a44019fc4cd1445040647d@127.0.0.1:30303 --p2p-port=30305 --rpc-http-enabled --rpc-http-api=ETH,NET,IBFT --host-allowlist="*" --rpc-http-cors-origins="all" --rpc-http-port=8547 --engine-rpc-http-port=8552

9. Node-4の起動

ポート番号以外、Node-2と同じ。

$ ../../besu-22.1.1/bin/besu --data-path=data --genesis-file=../genesis.json --bootnodes=enode://e1f0f86fce7ed83061095d5d6e359aacc7ffe0304467d0faaf17da9cc54b3ce1fd46919537dd7ebfd8b1ed8938bb1c899cbf7fe2f3a44019fc4cd1445040647d@127.0.0.1:30303 --p2p-port=30306 --rpc-http-enabled --rpc-http-api=ETH,NET,IBFT --host-allowlist="*" --rpc-http-cors-origins="all" --rpc-http-port=8548 --engine-rpc-http-port=8553

10. 動作確認

curl -X POST --data '{"jsonrpc":"2.0","method":"ibft_getValidatorsByBlockNumber","params":["latest"], "id":1}' http://127.0.0.1:8545

このコマンドを叩いて、4つのバリデータが表示されればOK。

{
  "jsonrpc" : "2.0",
  "id" : 1,
  "result" : [ "0x59b1276f7295f3dfa96d16e38b5385e233a461ab", "0xc470b991787b76269b39f9a8971c8c2e577e0c4f", "0xd7fac01a40d24ed463fcf2f67ee2ea708b34787b", "0xe93f819df50df397cff7986f74a7aba6644bf0ef" ]

なんかブロックも生成されているようだし、動いているようだ。

しかし、一つ気になったのが、pythonで叩いたときに接続できなかったこと。

>>> from web3 import Web3
>>> w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))
>>> w3.isConnected()
False

接続に失敗する。んー、curlやtelnetで叩けるのに pythonで叩け無いということがあるのか。。

ちょっとまた別の機会に調べることにして、今回はこれで終わりとします。

3/31 追記

上記のエラーについて色々調べた結果、なにかしらのタイミングで環境変数にHTTP_PROXYを追加していたのが原因だった。。pythonがURIにアクセスする際、環境変数があることでプロキシを使おうとしてエラーになっていたようだ。

気づくまでに2時間ぐらいかかった。tcpdumpとかしてたらもっと早くわかったはずだった。あほや。。

Discussion