Symbolの生きているノードリストの作成方法について

# 概要

Symbolには全世界に分散化された多数のフルノードが存在します。それらのフルノードではREST APIが有効化されており、開発者はそのREST APIを利用しブロックチェーン上のデータを参照、トランザクションを送信したりすることになります。

例えば、WEB開発を行う場合は、このようにしてノードURLを指定してリポジトリを生成するとことになります。

```javascript
const repositoryFactory = new SymbolSdk.RepositoryFactoryHttp("http://dual-1.nodes-xym.work:3000");
```

このノードURLは、Symbolエクスプローラーのノードリストより取得できるようになっています。

* メインネット: https://symbol.fyi/nodes
* テストネット：https://testnet.symbol.fyi/nodes

または、下記のようなノードリストもありますので参考にされると良いでしょう。

* メインネット: https://symbolnodes.org/nodes/
* テストネット: https://symbolnodes.org/nodes_testnet/

この辺りの情報については 松岡靖典（@salaryman-toushi） さんが書いてくださっています。

https://qiita.com/salaryman-toushi/items/a1c399bee5505b250836

松岡さんも言っておられますが、
>多数のノードを活かすには、ノードのリストをもっと動的に取得して、適宜それらを使用していくということが必要になってきます。

ですので、私は下記のようなツールを作成して公開しています。

https://rohr.sfn.tools/api/references

さて、ここからが本題ですが、このノードリストの作成方法について説明をしたいと思います。

# やること

* ノードからピアノードを再帰的に取得し配列やデータベース等に保存する
* 取得したノードリストでHTTP・HTTPSアクセスができるかどうかチェックする

たったこれだけです。

具体的には、下記の様に/node/peersでノードの通信しているピアノードリストが取得できます。
http://dual-1.nodes-xym.work:3000/node/peers​

実際に、上記ULRにアクセスすると下記の図のようにノードのリストが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/07899637-dc78-ac94-e335-ea059c10bc9e.png)

/node/peersより取得できたノードのホストに対して再度、http://185.5.55.39:3000/node/peers のようにしてピアノードを取得していきます。
これをずっと繰り返していけば最終的に全ノードのリストが出来上がるという感じです。

ただし、/node/peersで取得できるノードは、アクセスできなものも存在しています。ですので、/node/peersから取得したノードリストを作成した後は、実際にHTTPアクセスができるかどうかなどのチェックが必要になってきます。

## 実装

私はPHPでプログラミングすることが多いので、下記のような実装を行いました。（Laravelのキューで実装）

```php

...略

//$this->nodesには配列やデータベースに保存されているノードリストが入る
foreach($this->nodes as $key => $node){
  $get = Http::timeout(3)->get("http://" . $node['host'] . ":3000" . '/node/peers');
  $peers = $get->json();
  if($peers){
    foreach($peers as $key => $peer){
      if($peer['host'] !== ""){
　　　　 //既に保存済みかどうか確認（しなくてもよいかも）
        $node = NodeList::where('host', $peer['host'])->get();
        if(count($node) === 0){
          ...
          //データベースに保存する処理
        }
      }
    }
  }
}
```

DBから取得するノードを分割してLaravelのキュー（並列実行できる環境）を利用し実装しています。（ここではキューの説明は省きます）
最初はpeersノード情報を配列に保持→配列に対して処理済みホストを除外→HTTPアクセス確認...といった感じで１つのスクリプトで全処理を行っていたのですが、このような実装では処理完了まで数時間かかってしまったので上記のような並列処理で実装するように変えました。
$this->nodesには10個ほどのホスト情報を入れて30スレッドで並列処理しています。

参考になるかどうかわからないですが、一応キューの登録方法も載せておきます。

```php

private $chunkSize = 10;

...略

public function handle()
{

  // DBから現在のノードを取得
  $nodes = NodeList::all();

  $collect = collect($nodes);
  //指定件数で分割
  $chunkNodes = $collect->chunk($this->chunkSize);
  //ジョブ登録
  $this->storeQueue($chunkNodes);
  
}

function storeQueue($nodes){
  $i = 1;
  $count = 1;
  $max_thread = 30;
  foreach($nodes as $key => $node){
    $queueName = 'checkPeersNode_' . $i;
    dhpPeerNodeScreeningJob::dispatch($node)
        ->onQueue($queueName);    
    if($i >= $max_thread){
        $i = 1;
    }else{
        $i++;
    }
    $count++;
  }
}
```


この処理が完了すると現在の全ノードリストが作成できます。ただし、/node/peersで取得できるノードはHTTPアクセスの出来ないものも含まれます。次の処理で実際にHTTPアクセスできるかどうかチェックしていきます。（こちらもキューを利用）

```php

...略

// $this->nodesにはDBより取得したノードが入る
foreach($this->nodes as $key => $currentPeer){
try{
  $get = Http::timeout(3)->get("http://" . $currentPeer["host"] . ":3000" . '/node/health');
  $health = $get->json();
  if($health){
    if($health['status']['apiNode'] === "up" && $health['status']['db'] === "up"){
        // "正常アクセス可能
        ...データベースを更新する処理
    }else{
        //ヘルスチェックエラー
        ...データベースを更新する処理
    }
  }else{
      //HTTPアクセス不可
      ...データベースを更新する処理
  }
});
```

HTTPSについても同じ様に処理することで判別可能です。

## 最後に

[nem Advent Calendar 2021](https://qiita.com/advent-calendar/2021/nem) の [初日](https://qiita.com/GodTanu2/items/11aee2a7040f7f6406a9) に @GodTanu2 さんも仰っておられましたが、開発者の皆さんには、 **より沢⼭の時間を「何を作るか」に費やして欲しい** と思いノードリストを作成・公開しています。（ノード選択で迷わなくても良いように）
ぜひ、ご利用頂ければと思います。
また、同じようなノードリストを公開してくれる人も現れてくれることを期待しています。