コンテンツにスキップ

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

初級

/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:
            height_ago = f'{int(now - height_changed_at)}s ago'
        else:
            height_ago = '-'
        if finalized_changed_at is not None:
            finalized_ago = f'{int(now - finalized_changed_at)}s ago'
        else:
            finalized_ago = '-'

        print(
            f'Height: {height:>10,}'
            f'  (changed {height_ago})'
            f'  |  Finalized: {finalized_height:>10,}'
            f'  (changed {finalized_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;

for (;;) {
    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 = BigInt(chainInfo.height);
    const finalized = chainInfo.latestFinalizedBlock;
    const finalizedHeight = BigInt(finalized.height);

    const now = Date.now();

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

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

    const heightLabel = height.toLocaleString().padStart(10);
    const finalizedHeightLabel =
        finalizedHeight.toLocaleString().padStart(10);
    console.log(
        `Height: ${heightLabel}  (changed ${heightAgo})` +
        `  |  Finalized: ${finalizedHeightLabel}` +
        `  (changed ${finalizedAgo})`
    );

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

Download source

//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.1

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public final class ChainHeights {
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

    private final String nodeUrl = System.getenv().getOrDefault(
        "NODE_URL", "https://reference.symboltest.net:3001");

    public static void main(final String[] args) {
        try {
            new ChainHeights().run();
        } catch (final Exception ex) {
            System.out.println(null == ex.getMessage()
                ? ex.toString()
                : ex.getMessage());
        }
    }

    private void run() throws IOException, InterruptedException {
        System.out.printf("Using node %s%n", nodeUrl);

        Long prevHeight = null;
        Long prevFinalizedHeight = null;
        Long heightChangedAt = null;
        Long finalizedChangedAt = null;

        while (true) {

            final HttpRequest request = HttpRequest.newBuilder(
                URI.create(nodeUrl + "/chain/info")).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            final JsonNode chainInfo = JSON_MAPPER.readTree(
                response.body());

            final long height = chainInfo.get("height").asLong();
            final JsonNode finalized =
                chainInfo.get("latestFinalizedBlock");
            final long finalizedHeight =
                finalized.get("height").asLong();

            final long now = System.currentTimeMillis();

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

            final String heightAgo = null != heightChangedAt
                ? "%ds ago".formatted((now - heightChangedAt) / 1000)
                : "-";
            final String finalizedAgo = null != finalizedChangedAt
                ? "%ds ago".formatted((now - finalizedChangedAt) / 1000)
                : "-";

            System.out.printf(
                "Height: %,10d  (changed %s)"
                    + "  |  Finalized: %,10d  (changed %s)%n",
                height, heightAgo, finalizedHeight, finalizedAgo);

            prevHeight = height;
            prevFinalizedHeight = finalizedHeight;
            Thread.sleep(1000);

        }
    }
}

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 = BigInt(chainInfo.height);
    const finalized = chainInfo.latestFinalizedBlock;
    const finalizedHeight = BigInt(finalized.height);
            final HttpRequest request = HttpRequest.newBuilder(
                URI.create(nodeUrl + "/chain/info")).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            final JsonNode chainInfo = JSON_MAPPER.readTree(
                response.body());

            final long height = chainInfo.get("height").asLong();
            final JsonNode finalized =
                chainInfo.get("latestFinalizedBlock");
            final long finalizedHeight =
                finalized.get("height").asLong();

各イテレーションで、コードは /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:
            height_ago = f'{int(now - height_changed_at)}s ago'
        else:
            height_ago = '-'
        if finalized_changed_at is not None:
            finalized_ago = f'{int(now - finalized_changed_at)}s ago'
        else:
            finalized_ago = '-'
    if (null !== prevHeight && height !== prevHeight)
        heightChangedAt = now;
    if (null !== prevFinalizedHeight &&
        finalizedHeight !== prevFinalizedHeight)
        finalizedChangedAt = now;

    const heightAgo = null !== heightChangedAt ?
        `${Math.floor((now - heightChangedAt) / 1000)}s ago` :
        '-';
    const finalizedAgo = null !== finalizedChangedAt ?
        `${Math.floor((now - finalizedChangedAt) / 1000)}s ago` :
        '-';
            if (null != prevHeight && height != prevHeight)
                heightChangedAt = now;
            if (null != prevFinalizedHeight
                && finalizedHeight != prevFinalizedHeight)
                finalizedChangedAt = now;

            final String heightAgo = null != heightChangedAt
                ? "%ds ago".formatted((now - heightChangedAt) / 1000)
                : "-";
            final String finalizedAgo = null != finalizedChangedAt
                ? "%ds ago".formatted((now - finalizedChangedAt) / 1000)
                : "-";

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

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

ポーリングループ⚓︎

        print(
            f'Height: {height:>10,}'
            f'  (changed {height_ago})'
            f'  |  Finalized: {finalized_height:>10,}'
            f'  (changed {finalized_ago})'
        )

        prev_height = height
        prev_finalized_height = finalized_height
        time.sleep(1)
    const heightLabel = height.toLocaleString().padStart(10);
    const finalizedHeightLabel =
        finalizedHeight.toLocaleString().padStart(10);
    console.log(
        `Height: ${heightLabel}  (changed ${heightAgo})` +
        `  |  Finalized: ${finalizedHeightLabel}` +
        `  (changed ${finalizedAgo})`
    );

    prevHeight = height;
    prevFinalizedHeight = finalizedHeight;
    await new Promise(resolve => { setTimeout(resolve, 1000); });
            System.out.printf(
                "Height: %,10d  (changed %s)"
                    + "  |  Finalized: %,10d  (changed %s)%n",
                height, heightAgo, finalizedHeight, finalizedAgo);

            prevHeight = height;
            prevFinalizedHeight = finalizedHeight;
            Thread.sleep(1000);

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

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

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

出力⚓︎

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

1
2
3
4
5
6
7
8
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)

出力のポイント:

  • 変更前 (2〜4行目): まだ変更が観測されていないため、どちらのカウンターも - を表示しています。

  • 新しいブロックの承認 (5行目): チェーン高が 3,159,411 から 3,159,412 へ進み、変更カウンターが 0s から始まります。

  • ファイナライズラウンドの完了 (7行目): ファイナライズ高が 3,159,388 から 3,159,411 へ 23 ブロック進み、変更カウンターが 0s から始まります。

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

結論⚓︎

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

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

次のステップ⚓︎

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