コンテンツにスキップ
初級

チェーン高とファイナライズ高の照会⚓︎

/chain/info GET エンドポイントは、現在のチェーン高と最新の ファイナライズ 高を返します。これら2つの値を比較することで、ファイナライズされた状態がチェーンの先端からどれくらい遅れているかを確認できます。これは、トランザクションの不可逆性を確認する必要があるアプリケーションにとって非常に有用です。

このチュートリアルでは、ループ内でチェーンの状態をポーリングし、各高さが最後に変更されてからどれくらい時間が経過したかを表示する方法を説明します。

前提条件⚓︎

このチュートリアルでは、SDKを必要とせずに Symbol REST API を使用します。HTTPリクエストを行う方法さえあれば実行可能です。

完全なコード⚓︎

このチュートリアルの完全なコード一覧を以下に示します。 詳細な手順ごとの説明は次のセクションで行います。

import json
import os
import time
import urllib.request

NODE_URL = os.getenv(
    'NODE_URL', 'https://reference.symboltest.net:3001')
print(f'Using node {NODE_URL}')

prev_height = None
prev_finalized_height = None
height_changed_at = None
finalized_changed_at = None

try:
    while True:
        with urllib.request.urlopen(f'{NODE_URL}/chain/info') as response:
            chain_info = json.loads(response.read().decode())

        height = int(chain_info['height'])
        finalized = chain_info['latestFinalizedBlock']
        finalized_height = int(finalized['height'])

        now = time.time()

        if prev_height is not None and height != prev_height:
            height_changed_at = now
        if (
            prev_finalized_height is not None
            and finalized_height != prev_finalized_height
        ):
            finalized_changed_at = now

        if height_changed_at is not None:
            h_ago = f"{int(now - height_changed_at)}s ago"
        else:
            h_ago = "-"
        if finalized_changed_at is not None:
            f_ago = f"{int(now - finalized_changed_at)}s ago"
        else:
            f_ago = "-"

        print(
            f"Height: {height:>10,}"
            f"  (changed {h_ago})"
            f"  |  Finalized: {finalized_height:>10,}"
            f"  (changed {f_ago})"
        )

        prev_height = height
        prev_finalized_height = finalized_height
        time.sleep(1)

except KeyboardInterrupt:
    pass
except Exception as error:
    print(error)

Download source

const NODE_URL = process.env.NODE_URL ||
    'https://reference.symboltest.net:3001';
console.log(`Using node ${NODE_URL}`);

let prevHeight = null;
let prevFinalizedHeight = null;
let heightChangedAt = null;
let finalizedChangedAt = null;

try {
    while (true) {
        const response = await fetch(`${NODE_URL}/chain/info`);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const chainInfo = await response.json();

        const height = parseInt(chainInfo.height, 10);
        const finalized = chainInfo.latestFinalizedBlock;
        const finalizedHeight = parseInt(finalized.height, 10);

        const now = Date.now();

        if (prevHeight !== null && height !== prevHeight) {
            heightChangedAt = now;
        }
        if (prevFinalizedHeight !== null
            && finalizedHeight !== prevFinalizedHeight) {
            finalizedChangedAt = now;
        }

        const hAgo = heightChangedAt !== null
            ? `${Math.floor((now - heightChangedAt) / 1000)}s ago`
            : '-';
        const fAgo = finalizedChangedAt !== null
            ? `${Math.floor((now - finalizedChangedAt) / 1000)}s ago`
            : '-';

        const h = height.toLocaleString().padStart(10);
        const fh = finalizedHeight.toLocaleString().padStart(10);
        console.log(
            `Height: ${h}  (changed ${hAgo})`
            + `  |  Finalized: ${fh}`
            + `  (changed ${fAgo})`
        );

        prevHeight = height;
        prevFinalizedHeight = finalizedHeight;
        await new Promise((resolve) => setTimeout(resolve, 1000));
    }
} catch (error) {
    throw error;
}

Download source

このスニペットでは、NODE_URL 環境変数を使用して Symbol API ノード を設定します。値が指定されない場合は、デフォルト値が使用されます。

プログラムは無限ループで実行され、1秒ごとにステータス行を出力します。キーボード割り込み(Ctrl+C)でループを停止できます。

コード解説⚓︎

チェーン情報の取得⚓︎

        with urllib.request.urlopen(f'{NODE_URL}/chain/info') as response:
            chain_info = json.loads(response.read().decode())

        height = int(chain_info['height'])
        finalized = chain_info['latestFinalizedBlock']
        finalized_height = int(finalized['height'])
        const response = await fetch(`${NODE_URL}/chain/info`);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const chainInfo = await response.json();

        const height = parseInt(chainInfo.height, 10);
        const finalized = chainInfo.latestFinalizedBlock;
        const finalizedHeight = parseInt(finalized.height, 10);

各イテレーションで、コードは /chain/info GET エンドポイントに GET リクエストを送信します。レスポンスには以下が含まれます:

  • height: 現在のチェーン高(ノードが認識している最新の ブロック)。
  • latestFinalizedBlock: 直近でファイナライズされたブロックの詳細を含むオブジェクト:
    • height: ファイナライズされたブロックの高さ。
    • finalizationEpoch: ファイナライズエポック番号。
    • finalizationPoint: エポック内のファイナライズポイント。
    • hash: ファイナライズされたブロックのハッシュ。

チェーン高は、新しいブロックが生成されるたびに(約30秒ごと)増加します。

ブロックは通常、生成されてから10〜20分後にファイナライズされるため、ファイナライズ高はチェーン先端よりも遅れます。ファイナライズのラウンドが完了すると、ファイナライズ高は1ブロックずつ進むのではなく、一度に多くのブロック分ジャンプします。投票ノード がこのプロセスをどのように推進するかについては、テキストブックの コンセンサス のセクションを参照してください。

高さの変更の追跡⚓︎

        if prev_height is not None and height != prev_height:
            height_changed_at = now
        if (
            prev_finalized_height is not None
            and finalized_height != prev_finalized_height
        ):
            finalized_changed_at = now

        if height_changed_at is not None:
            h_ago = f"{int(now - height_changed_at)}s ago"
        else:
            h_ago = "-"
        if finalized_changed_at is not None:
            f_ago = f"{int(now - finalized_changed_at)}s ago"
        else:
            f_ago = "-"
        if (prevHeight !== null && height !== prevHeight) {
            heightChangedAt = now;
        }
        if (prevFinalizedHeight !== null
            && finalizedHeight !== prevFinalizedHeight) {
            finalizedChangedAt = now;
        }

        const hAgo = heightChangedAt !== null
            ? `${Math.floor((now - heightChangedAt) / 1000)}s ago`
            : '-';
        const fAgo = finalizedChangedAt !== null
            ? `${Math.floor((now - finalizedChangedAt) / 1000)}s ago`
            : '-';

各高さが最後に変更されてからの経過時間を表示するために、コードは以前の値とそのタイムスタンプを保存します。高さが以前の値と異なる場合、タイムスタンプは現在時刻に更新されます。

変更が観測されるまで、タイムスタンプは未設定のままであり、出力には数値の代わりに - が表示されます。変更が発生すると、カウンターは 0s ago から始まり、次の変更まで1秒ごとに増加します。

ポーリングループ⚓︎

        print(
            f"Height: {height:>10,}"
            f"  (changed {h_ago})"
            f"  |  Finalized: {finalized_height:>10,}"
            f"  (changed {f_ago})"
        )

        prev_height = height
        prev_finalized_height = finalized_height
        time.sleep(1)
        const h = height.toLocaleString().padStart(10);
        const fh = finalizedHeight.toLocaleString().padStart(10);
        console.log(
            `Height: ${h}  (changed ${hAgo})`
            + `  |  Finalized: ${fh}`
            + `  (changed ${fAgo})`
        );

        prevHeight = height;
        prevFinalizedHeight = finalizedHeight;
        await new Promise((resolve) => setTimeout(resolve, 1000));

各イテレーションでは、以下の内容を含む1行のステータスを出力します:

  • 現在のチェーン高と、最後に変更されてからの経過秒数。
  • ファイナライズ高と、最後に変更されてからの経過秒数。

その後、ループはイテレーションの間に1秒間スリープします。

出力⚓︎

以下の出力は、チェーン高とファイナライズ高を監視する典型的な実行例を示しています:

Using node https://reference.symboltest.net:3001
Height:  3,159,411  (changed -)  |  Finalized:  3,159,388  (changed -)
Height:  3,159,411  (changed -)  |  Finalized:  3,159,388  (changed -)
Height:  3,159,411  (changed -)  |  Finalized:  3,159,388  (changed -)
Height:  3,159,412  (changed 0s ago)  |  Finalized:  3,159,388  (changed -)
Height:  3,159,412  (changed 1s ago)  |  Finalized:  3,159,388  (changed -)
Height:  3,159,412  (changed 2s ago)  |  Finalized:  3,159,411  (changed 0s ago)
Height:  3,159,412  (changed 3s ago)  |  Finalized:  3,159,411  (changed 1s ago)

出力のポイント:

  1. チェーン高が 3,159,411 から 3,159,412 へ1ブロック進みます。その時点で、変更カウンターは 0s から始まります。
  2. その後、ファイナライズ高が追いつき、3,159,388 から 3,159,411 へ23ブロック進みます。その変更カウンターも 0s から始まります。
  3. 最初はどちらの高さも、まだ変更が観測されていないため - を表示しています。

チェーン高とファイナライズ高の間のギャップは正常な状態です。チェーン先端のブロックに含まれるトランザクションは承認(承認済み)されていますが、まだ不可逆(ファイナライズ済み)ではありません。ファイナライズ高がそのブロックに達するかそれを超えると、そのトランザクションはチェーン内に残ることが保証されます。

結論⚓︎

このチュートリアルでは、以下の方法を説明しました:

ステップ 関連ドキュメント
チェーン情報の取得 /chain/info GET

次のステップ⚓︎

新しいブロックやファイナライズを監視するためのイベント駆動型のアプローチについては、WebSocketのチュートリアル 新しいブロックのリスニング を参照してください。