Hyperledger Iroha Peerの追加方法

# 記事の内容
本記事ではHyperledger IrohaでPeerを追加する方法を記載しています。
「iroha-cli」で追加する方法もありますが、今回はJavaのライブラリを使って追加しています。
「iroha-cli」で追加したい方は、記事中のコードで公開鍵、秘密鍵を取得できますので、その鍵情報を使えば簡単に出来ると思います。

結論を先に書いておきますが、この手順ではPeerの追加に失敗します。
Peer追加のプログラムは正常終了し、ブロックにも取り込まれますが、Peer間通信が上手くいきません。

これが環境の問題なのかIrohaの問題なのか不明のため、上手くいかなかった方法としてメモを残しておきます。

※戻し用のプログラム（Peer削除）も準備しましたが、現時点のライブラリ（6.1.0）では実装されてなさそうです。

# 環境
- Hyperledger Iroha v1.0
- JDK 1.8

本記事の作業ではコンソールを4つ起動し作業を行っています。
記事中のコンソールが分かり辛くならないように、注意して書きます。

1. iroha 1号機 irohaデーモンのログ確認用コンソール
2. iroha 1号機のirohaコンテナ操作用コンソール
3. iroha 1号機のsome-postgresコンテナ操作用コンソール
4. iroha 2号機のirohaコンテナ操作用コンソール

# 事前準備
irohaの環境構築を２台分実施します。
1号機には適当に送金のブロックなどを事前に積んでおきます。
2号機はジェネシスブロックのみという状態にしておきます。


# Peer追加時に使用する鍵を用意
本来は？OpenSSL等を使って公開鍵を用意するのかなと思います。
Irohaのドキュメントにも書いてあったサンプルコードを使うと簡単に鍵を作成出来るので、今回はJavaで鍵情報を作成します。

# Peerの追加処理
## Peerの追加プログラム
Peer追加のみを行うコードです。

~~~ AddPeer.java
import java.net.URI;
import java.security.KeyPair;

import org.testcontainers.shaded.org.apache.commons.codec.binary.Hex;

import jp.co.soramitsu.crypto.ed25519.Ed25519Sha3;
import jp.co.soramitsu.iroha.java.IrohaAPI;
import jp.co.soramitsu.iroha.java.Transaction;
import jp.co.soramitsu.iroha.java.TransactionStatusObserver;
import lombok.val;

public class AddPeer {

    private static final Ed25519Sha3 crypto = new Ed25519Sha3();

    private static final KeyPair peerKeypair = crypto.generateKeypair();

    public static void main(String[] args) throws Exception{

        // このIPはiroha 1号機のアドレス
        URI uri = new URI(null,null, "192.168.33.20",50051,null,null,null);

        IrohaAPI api = new IrohaAPI(uri);

        byte[] pubByte = Hex.decodeHex("313a07e6384776ed95447710d15e59148473ccfc052a681317a72a69f2a49910");
        byte[] privByte = Hex.decodeHex("f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70");

        KeyPair adminKeyPair = Ed25519Sha3.keyPairFromBytes(privByte, pubByte);

        // このIPはiroha 2号機のIPアドレス
        val addPeerTx = Transaction.builder("admin@test")
                          .addPeer("192.168.33.21:10001", peerKeypair.getPublic())
                          .sign(adminKeyPair)
                          .build();
                          
        val observer = TransactionStatusObserver.builder()
            // executed when stateless or stateful validation is failed
            .onTransactionFailed(t -> System.out.println(String.format(
                "transaction %s failed with msg: %s",
                t.getTxHash(),
                t.getErrOrCmdName()
            )))
            // executed when got any exception in handlers or grpc
            .onError(e -> System.out.println("Failed with exception: " + e))
            // executed when we receive "committed" status
            .onTransactionCommitted((t) -> System.out.println("Committed :)"))
            // executed when transfer is complete (failed or succeed) and observable is closed
            .onComplete(() -> System.out.println("Complete"))
            .build();

        // Trransaction 実行
        api.transaction(addPeerTx).blockingSubscribe(observer);

        api.close();
        System.out.println("PubKey :" + Hex.encodeHexString(peerKeypair.getPublic().getEncoded()));
        System.out.println("PrivKey:" + Hex.encodeHexString(peerKeypair.getPrivate().getEncoded()));       
    }
}

~~~
※irohaのネットワーク内でPeer間の通信に使用するポートは「10001」がデフォルト
　外部からクエリを受け付けるポートは「50051」がデフォルト

## 実行結果の確認（ブロック）
irohaコンテナの「/tmp/block_store」配下にAddPeerを行ったブロックが作成されています。

(iroha 1号機のirohaコンテナ操作用コンソール)

~~~ 
{
  "blockV1": {
    "payload": {
      "transactions": [
        {
          "payload": {
            "reducedPayload": {
              "commands": [
                {
                  "addPeer": {
                    "peer": {
                      "address": "192.168.33.21:10001",
                      "peerKey": "28CF79DEF117456ABC5413DC15F7B99B192C6D5A162ABDEF39F0BAF6C7CE0B48"
                    }
                  }
                }
              ],
              "creatorAccountId": "admin@test",
              "createdTime": "1571213725207",
              "quorum": 1
            }
          },
          "signatures": [
            {
              "publicKey": "313A07E6384776ED95447710D15E59148473CCFC052A681317A72A69F2A49910",
              "signature": "A018F697EB5F6797BD478F8CF04759F8D0ACDA48D4E1FE8B8BBE5D538E6D47A423896E689AA780DD260763ED1B97B12F9AF1EA41D7D391FB2F9C49EDB429DC0C"
            }
          ]
        }
      ],
      "height": "16",
      "prevBlockHash": "e602693381ff5ac383a2d62503d94ede87951ffdedefe9b4628a188036fd6c89",
      "createdTime": "1571213727036"
    },
    "signatures": [
      {
        "publicKey": "bddd58404d1315e0eb27902c5d7c8eb0602c16238f005773df406bc191308929",
        "signature": "863b38942a290640e05bbb2339ab63d94a8ab6ca3bfba1abaf673fe490f7eb46236a72cdb54d95a3c11254c440ebe3ef1110466a989e0dc4221952749b87ec0b"
      }
    ]
  }
}
~~~
addPeerコマンドにPeer情報としてプログラムに埋め込んだIPアドレスと作成したpubKeyが登録されています。

## 実行結果の確認（postgres）
Dockerコンテナの「some-postgres」に接続して、Peer情報を保持しているテーブルの中身を見てみます。
(iroha 1号機のsome-postgresコンテナ操作用コンソール)

~~~ 
root@f7d412e03bfb:/# psql -U postgres
psql (9.5.19)
Type "help" for help.

postgres=# \l
                                 List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

postgres=# \d
                        List of relations
 Schema |               Name                |   Type   |  Owner
--------+-----------------------------------+----------+----------
 public | account                           | table    | postgres
 public | account_has_asset                 | table    | postgres
 public | account_has_grantable_permissions | table    | postgres
 public | account_has_roles                 | table    | postgres
 public | account_has_signatory             | table    | postgres
 public | asset                             | table    | postgres
 public | domain                            | table    | postgres
 public | height_by_account_set             | table    | postgres
 public | index_by_creator_height           | table    | postgres
 public | index_by_creator_height_id_seq    | sequence | postgres
 public | peer                              | table    | postgres
 public | position_by_account_asset         | table    | postgres
 public | position_by_hash                  | table    | postgres
 public | role                              | table    | postgres
 public | role_has_permissions              | table    | postgres
 public | signatory                         | table    | postgres
 public | tx_status_by_hash                 | table    | postgres
(17 rows)

postgres=# select * from peer;
                            public_key                            |       address
------------------------------------------------------------------+---------------------
 bddd58404d1315e0eb27902c5d7c8eb0602c16238f005773df406bc191308929 | 127.0.0.1:10001
 28cf79def117456abc5413dc15f7b99b192c6d5a162abdef39f0baf6c7ce0b48 | 192.168.33.21:10001
(2 rows)
~~~

PeerテーブルにaddPeerで設定したpublicKeyとIPアドレスが登録されました。

## 実行結果の確認（iroha 2号機）
次は2号機にブロックの情報が同期されたか確認します。

(iroha 2号機のirohaコンテナ操作用コンソール)

~~~ 
root@eaa7050903db:/opt/iroha_data# ls /tmp/block_store/
0000000000000001
~~~

同期されてません。。
irohadのログを確認してみます。

## 実行結果の確認（irohaデーモン）
(iroha 1号機 irohaデーモンのログ確認用コンソール)

~~~ 
root@c8d233a3c261:/opt/iroha_data# [2019-10-17 00:12:09.459123458][I][Irohad/CommandService/Processor]: handle batch
[2019-10-17 00:12:09.459140102][I][Irohad/CommandService/Processor]: propagating batch to PCS
[2019-10-17 00:12:09.459158309][I][Irohad/PeerCommunicationService]: propagate batch
[2019-10-17 00:12:09.460952243][I][Irohad/Ordering/Service]: onBatches => collection size = 1
[2019-10-17 00:12:10.400058025][W][Irohad/AsyncNetworkClient]: RPC failed: Connect Failed
[2019-10-17 00:12:10.400080412][W][Irohad/AsyncNetworkClient]: RPC failed: Connect Failed
[2019-10-17 00:12:10.400088212][W][Irohad/AsyncNetworkClient]: RPC failed: Connect Failed
[2019-10-17 00:12:10.400095314][W][Irohad/AsyncNetworkClient]: RPC failed: Connect Failed
[2019-10-17 00:12:10.400102280][W][Irohad/AsyncNetworkClient]: RPC failed: Connect Failed
~~~
どうもgRPCを使った2号機との接続に失敗しているようです。

恐らくですが、これと同じ事象だと思います。
https://github.com/hyperledger/iroha/issues/191

こちらで管理されてます
https://jira.hyperledger.org/browse/IR-588

# 戻し（Peer削除）
このままだと使えないのでPeer削除用のプログラムも用意しました。

~~~ RemovePeer.java
import java.net.URI;
import java.security.KeyPair;

import org.testcontainers.shaded.org.apache.commons.codec.binary.Hex;

import jp.co.soramitsu.crypto.ed25519.Ed25519Sha3;
import jp.co.soramitsu.iroha.java.IrohaAPI;
import jp.co.soramitsu.iroha.java.Transaction;
import jp.co.soramitsu.iroha.java.TransactionStatusObserver;
import lombok.val;

public class RemovePeer {

    public static void main(String[] args) throws Exception{

        URI uri = new URI(null,null, "192.168.33.20",50051,null,null,null);

        IrohaAPI api = new IrohaAPI(uri);

        byte[] pubByte = Hex.decodeHex("313a07e6384776ed95447710d15e59148473ccfc052a681317a72a69f2a49910");
        byte[] privByte = Hex.decodeHex("f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70");
        
        // ↓ブロックに取り込まれたPeerの公開鍵を設定してください↓
        byte[] peerPubKey = Hex.decodeHex("28CF79DEF117456ABC5413DC15F7B99B192C6D5A162ABDEF39F0BAF6C7CE0B48");

        KeyPair adminKeyPair = Ed25519Sha3.keyPairFromBytes(privByte, pubByte);


        val addPeerTx = Transaction.builder("admin@test")
                            .removePeer(peerPubKey)  // ← ここがaddPeerからremovePeerに変わっただけ
                            .sign(adminKeyPair)
                            .build();

        val observer = TransactionStatusObserver.builder()
            // executed when stateless or stateful validation is failed
            .onTransactionFailed(t -> System.out.println(String.format(
                "transaction %s failed with msg: %s",
                t.getTxHash(),
                t.getErrOrCmdName()
            )))
            // executed when got any exception in handlers or grpc
            .onError(e -> System.out.println("Failed with exception: " + e))
            // executed when we receive "committed" status
            .onTransactionCommitted((t) -> System.out.println("Committed :)"))
            // executed when transfer is complete (failed or succeed) and observable is closed
            .onComplete(() -> System.out.println("Complete"))
            .build();

        // Trransaction 実行
        api.transaction(addPeerTx).blockingSubscribe(observer);

        api.close();
    }
}
~~~

実行します。

~~~
transaction 6069fe1e6c2f6b7437ce145e0db552355f3d8631d16b7bc18b53894fcafec2bf failed with msg: Protobuf Transaction: [[Undefined command is found ]]

Complete
~~~

なんと！Undefined command is found！
removePeerがまだ実装されていない様子。
デバッグでTransactionの内容を確認してみたところ、こんな感じになっていました。

~~~
reduced_payload {
  commands {
    remove_peer {
      public_key: "28CF79DEF117456ABC5413DC15F7B99B192C6D5A162ABDEF39F0BAF6C7CE0B48"
    }
  }
  creator_account_id: "admin@test"
  created_time: 1571276110853
  quorum: 1
}
~~~


引き続き他のやり方がないか探してみたり、上記のサイトの情報をトレースしたりしてみます。
今回はここまでとなります。


# 気になった点
今回いろいろ触っていて気になった点が２つあります。

1点目はPeer間の通信が上手くいかなかった場合にTransactionがブロックに取り込まれません。
まあ、当たり前な気がしますが、実運用でPeerが2台構成だった場合に、片方がダウンしたらTransactionが全く取り込まれなくなります。
今後、3台、4台と台数を増やして検証しようと思いますが、単一障害点を作らない為には、最低3台はPeerが必要になりそうです。

2点目は大した問題じゃないかもしれませんが、irohaデーモン起動時に既存のブロック情報の確認を全て行っているっぽいです。
(iroha 1号機 irohaデーモンのログ確認用コンソール)

~~~ 
[2019-10-16 23:41:19.917112613][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 1, hash 9debdb1a70db2cede2222427b849f6bf7ab20845da7c3db1837c0df25ec1c61a
[2019-10-16 23:41:19.930740040][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 2, hash d2d9906eb82dd799e6cb68161208738e8383cae31bdf499b2b5f7f14d55b2ba0
[2019-10-16 23:41:19.950225041][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 3, hash 686b05c2553b70cd26fdb6e9108066a33e2b09653ccb33b3b3c9f6e4f12e72d2
[2019-10-16 23:41:19.952860890][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 4, hash fd8541954c9ab55229e8f6e095a7484ece7d64066e78c775359e0d8736a4ea4b
[2019-10-16 23:41:19.960015934][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 5, hash cc5a4fe828811e217a5de828047b9c0d5939628d809b2265b66db1b73f9d0f98
[2019-10-16 23:41:19.972008320][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 6, hash 4fa8f208d9c85b08edc3504aed35f257de943942f250639111c334724cfe6d26
[2019-10-16 23:41:19.983822479][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 7, hash 471894ae7fa36f09d971be563686b2aa06ff28a9bd9a49d6e802451cb018ac1b
[2019-10-16 23:41:19.985555973][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 8, hash 68ef83c09eb4bc8152dd7feaeb0e10a1fb0c71b63e6c6e66e829fd730a765f18
[2019-10-16 23:41:19.987011731][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 9, hash e5dcd0505aa726d25d318af78ea41126f02c44f3647011582017140e1c7e55e5
[2019-10-16 23:41:19.988539990][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 10, hash c6fb5fff4bf3c440b6846761d50d248beb75199342b8b9431e19c1e2b5e7e303
[2019-10-16 23:41:19.989874002][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 11, hash 278c9c5e5a72b5513d55290f2687c623595b7825b747c525ee00135cccfb1149
[2019-10-16 23:41:19.991317395][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 12, hash d33d2adabf75be7380cc6b35b84bcb4b2055f47a927cf1090c1d49853fee154b
[2019-10-16 23:41:19.992694976][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 13, hash b0d19d57da0f0d0677052409e625adfea8dedbb6297edd49968d5f102de73c27
[2019-10-16 23:41:20.008512960][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 14, hash 337830921603cc703cd8f95a6624b3d2d0fce090d3ddca53c69d9989620f2c44
[2019-10-16 23:41:20.022142829][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 15, hash e602693381ff5ac383a2d62503d94ede87951ffdedefe9b4628a188036fd6c89
[2019-10-16 23:41:20.024153577][I][Irohad/Storage/MutableStorageImpl]: Applying block: height 16, hash d3e5c825cb9b1ca298ccf6b7b7f8a139803e6e1c9850fed65a0241f378f5125e
~~~

正常に取り込まれたブロックだと約0.01秒、rejectされたブロックだと約0.002秒ほど掛かっているようです。
サーバーの性能によっても当然処理速度は変わってくると思いますが、実運用でデーモンを再起動しないといけないようなことがあると、この処理に意外と時間がかかってしまうかもしれません。



pythonのライブラリだとaddPeerやremovePeerは動くのか？
ここら辺を次は調べてみたいと思います。