これまではアプリの起動に際してユーザーがノードURLを明示的に設定していました。 そこで、この小規模なライブラリ「Node Tracker for Symbol」を使うことで、アプリが全ノードをトラッキングし最適なものを自動的に選択できるようになります。 その為ユーザーはノードURLをいちいち設定する必要が無くなります。
Node Tracker for Symbol ではノードリストを収集する為に Symbol Statistics Service
(メインネットの場合 https://symbol.services/nodes
、テストネットの場合 https://testnet.symbol.services/nodes
) を使用する想定です。
取得した全ノードに対して「ヘルスチェック」を実行し、アクセスできないものを除外し、かつレイテンシーが低い順でソートを行います。
低レイテンシー順位トップ n
個のノードからランダムピックアップを行い、接続ノードを決定します。
尚 n
はアプリケーションが任意に指定可能です。
git clone https://github.com/OPENSPHERE-Inc/node-tracker-poc.git
cd node-tracker-poc
yarn
yarn build
テストネットの場合
STATS_SERVICE_URL=https://testnet.symbol.services/nodes
NETWORK_TYPE=152
メインネットの場合
STATS_SERVICE_URL=https://symbol.services/nodes
NETWORK_TYPE=104
・ランダムピック1件サンプル
node dist/samples/randomPick.js
randomPick
サンプルでは、低レイテンシー上位10個から一つノードをランダムピックして転送トランザクションを実行します。
送信元のプライベートキーと送信先のアドレス、転送量(XYM)をそれぞれ聞かれるので入力してください。
・任意選択サンプル
node dist/samples/userPick.js
userPick
サンプルでは、ユーザーがノードリストから選択して転送トランザクションを実行します。
ノードリストは低レイテンシー上位10個になります。
ランダムピックと同様に送信元プライベートキー、送信先アドレス、転送量(XYM)の入力を求められます。
こちら にサンプルを用意しました。
4.1. NodeTrackerService クラス
サンプルコード
const nodeTracker = new NodeTrackerService("https://testnet.symbol.services/nodes", 152);
await nodeTracker.discovery();
await nodeTracker.pingAll();
// 低レイテンシートップ 10 以内かつレイテンシー 1000 msec 以下の物からピック
let node = nodeTracker.pickOne(10, 1000);
// ピック済みノードのヘルスチェックを行い、不良であれば新しいノードをピック
while (!await nodeTracker.checkHealth(node.apiStatus.restGatewayUrl)) {
node = await nodeTracker.pickOne(10, 1000);
}
// 健康なノードを 5 個ピック
const healthyNodes = nodeTracker.pickMulti(5, 10, 1000);
const options: NodeTrackerServiceOptions = {
cachedNodes: [] as NodeStatistics,
cacheTimestamp: 12345678,
noWebSocketChallenge: false,
webSocketTimeout: 60000,
maxParallels: 10,
};
const nodeTracker = new NodeTrackerService(statsServiceURL, networkType, options);
引数
statsServiceURL: string
- Symbol Statistics Service の URL。Testnet:https://testnet.symbol.services/nodes
, Mainnet:https://symbol.services/nodes
networkType: NetworkType
- Testnet:152
, Mainnet:104
option: NodeTrackerServiceOptions
- (Optional)cachedNodes: NodeStatistics[]
- (Optional)availableNodes
をローカルキャッシュしていた場合はここで渡すcacheTimestamp: number
- (Optional) ローカルキャッシュ作成日時(Unix時間ミリ秒)noWebSocketChallenge: boolean
- (Optional)WebSocket 接続のチェックを行わない(その分高速)。デフォルトはfalse
webSocketTimeout: number
- (Optional) WebSocket 接続のタイムアウト時間をミリ秒で指定。デフォルトは60
秒maxParallels: number
- (Optional) ヘルスチェックの同時実行数。デフォルトは10
。 値を大きくするとヘルスチェックがスピードアップしますが、やりすぎると接続エラーが頻発する場合があります。 試した限りだと50
位が限度かもしれません。
const avaiableNodes: NodeStatistics[] = await nodeTracker.discovery(nodeUrls);
Symbol Statistics Service からノードリストを取得します。 有効でないノード(API status が有効でない物、https が有効でない物、WebSocket が有効でない物)は除外されます。
引数
nodeUrls: string[]
- (Optional) 特定のノードを検索するための、ノードURLのリストを渡せます。
戻り値
NodeStatistics[]
- ノードリスト
const availableNodes: NodeStatistics[] = await nodeTracker.pingAll();
有効なノード全てにヘルスチェックを実行します。 ヘルスチェックでは、REST API でデータにアクセスを試みて掛かる時間をミリ秒単位で計測します。 また、WebSocket の接続を試みます。
全ノードのヘルスチェックが完了すると Promise が Resolve されます。
途中経過は pingObserver
プロパティを購読してください。
戻り値
NodeStatistics[]
- ノードリスト
nodeTracker.abortPinging();
pingAll
の実行を中止します。
実行して程なく pingAll()
メソッドが途中終了して戻ります(その際、WebSocket が全てクローズされるのを待ちます)
既に開始しているノードは WebSocket は強制的にクローズされ、中止させられます。
この時、NodeStatsitics
の latency
プロパティは undefined
になり、
last_error
プロパティには WebSocket connection interrupted.
エラーが格納されます。
未だ開始していないノードは、それ以上実行されず latency
も last_error
もどちらも変化なしです。
const node: NodeStatistics | undefined = await nodeTracker.pickOne(top, maxLatency);
ノードリストからノードを1件ランダムピックします。
引数
top: number
- (Optional) 低レイテンシートップ何位以内からランダムピックするかを指定できます。 1 なら常に最もレイテンシーが低いノードがピックされます。省略した場合はリスト全体からのランダムピックとなります。maxLatency: number
- (Optional) 許容する最大レイテンシーをミリ秒単位で指定できます。省略した場合は制限なしとなります。
戻り値
NodeStatistics | undefined
- ピックされたノード
const nodes: NodeStatistics[] = await nodeTracker.pickMulti(count, top, maxLatency);
ノードリストからノードを複数件ランダムピックします。
引数
count: number
- ピック数。有効なノードが足りない場合、要求を下回る場合があります。top: number
- (Optional) 低レイテンシートップ何位以内からランダムピックするかを指定できます。 1 なら常に最もレイテンシーが低いノードがピックされます。省略した場合はリスト全体からのランダムピックとなります。maxLatency: number
- (Optional) 許容する最大レイテンシーをミリ秒単位で指定できます。省略した場合は制限なしとなります。
戻り値
NodeStatistics[]
- ピックされたノードリスト(ソートされてません)
const healthyNode: NodeStatistics | undefined = await nodeTracker.checkHealth(nodeUrl);
ノードURLで指定したノードのヘルスチェックを実施します。
結果は戻り値の latency
プロパティに格納されます(エラーは latest_error
)
予め本メソッドの実行前に
discover()
メソッドでノード一覧を取得しておく必要があります。
引数
nodeUrl: string
- ノードの REST Gateway URL
戻り値
NodeStatistics | undefined
- ヘルスが良好な場合は値が返り、不良の場合はundefined
が返ります。
const nodes: NodeStatistics = nodeTracker.availableNodes;
Symbol Statistics Service から取得したノードリストにアクセス出来ます。
負荷集中を避けるために、ノードリストはローカルキャッシュすることを推奨します。
ローカルキャッシュを NodeTrackerService
のコンストラクタに渡すことで disvoery()
の呼び出しを省略することが可能です。
const observer: Subject<{ node: NodeStatistics, index: number, total: number }> = nodeTracker.pingObserver;
ヘルスチェックの進捗を購読できる Subject
(rxjs) です。
コールバックの引数
{ node: NodeStatistics, index: number, total: number }
node: NodeStatistics
- ヘルスチェックが完了、または失敗したノードindex: number
- 進捗インデックス(availableNodes
のインデックスではありません)total: number
-availableNodes.length
が入ります。
使用例
const subscription = observer.subscribe(({node, index, total}: {node: NodeStatistics, index: number, total: number }) => {
if (node.latest_error) {
console.debug(`${index + 1} of ${total}: ${node.apiStatus.restGatewayUrl} [${node.latest_error}]`);
} else {
console.debug(`${index + 1} of ${total}: ${node.apiStatus.restGatewayUrl} [${node.latency} msecs]`);
}
});
// ...中略...
subscription.unsubscribe();
ヘルスチェックが完了またはエラー終了した NodeStatistics
がリアルタイムにプッシュされます。
エラー終了した場合は node.latest_error
にエラーが入ります。
リスト UI の更新トリガーなどに使用可能です。
const abortingCondition: boolean = nodeTracker.isAborting;
初期値は false
で、abortPinging()
メソッドが呼ばれると true
になります。
また、pingAll()
, healthCheck()
メソッドを呼び出すと一旦 false
に強制されます。
const count: number = nodeTracker.numActiveWebSockets;
今現在アクティブな WebSocket の数を返します。
pingAll
メソッドが終了した後は 0
になります。
4.2. NodeStatistics インターフェース
interface NodeStatistics {
peerStatus: {
isAvailable: boolean;
lastStatusCheck: number;
};
apiStatus: {
restGatewayUrl: string;
isAvailable: boolean;
lastStatusCheck: number;
nodeStatus: {
apiNode: string;
db: string;
};
isHttpsEnabled: boolean;
finalization: {
height: number;
epoch: number;
point: number;
hash: string;
};
restVersion: string;
webSocket: {
isAvailable: boolean;
wss: boolean;
url: string;
};
};
version: string;
publicKey: string;
networkGenerationHashSeed: string;
roles: number;
port: number;
networkIdentifier: number;
host: string;
friendlyName: string;
// Filled by NodeTrackerService
latency?: number;
latest_error?: string;
}
詳細は Open API の Responses
を参照してください。
主要なフィールド
networkIdentifier: number
-NetworkType
が格納されます(Testnet:152
, Mainnet:104
)apiStatus.isAvaiable: boolean
- ノードの有効性が格納されますapiStatus.restGatewayUrl: string
- REST Gateway URL が格納されますapiStatus.nodeStatus.apiNode: string
- API ノードの状態がup
(稼働中)またはdown
(障害発生)で格納されますapiStatus.nodeStatus.db: string
- MongoDB の状態がup
(稼働中)またはdown
(障害発生)で格納されますapiStatus.isHttpsEnabled: boolean
- HTTPS の有無が格納されますapiStatus.webSocket.isAvailable: boolean
- WebSocket の有無が格納されますapiStatus.webSocket.wss: boolean
- WebSocket over SSL/TLS の有無が格納されますapiStatus.webSocket.url: string
- WebSocket URL が格納されます
以下の二つのフィールドは NodeTrackerService
のヘルスチェックによって追加されます。
latest_error: string | undefined
- ヘルス不良のノードはエラーメッセージが格納されます。良好であればundefined
になります。latency: number | undefined
- ヘルスが良好な場合、レイテンシーの値(ミリ秒)が格納されます。
本ライブラリは他プロジェクトで使用する為、 近日中に @opensphere-inc/symbol-service へ統合される予定です。
本ライブラリに関しての質問等ありましたら Discussion へ、不具合等は Issue へお寄せいただければと思います。
本リポジトリは MIT ライセンスといたします。