コンテンツにスキップ

チェーン高と不可逆高を照会する⚓︎

初級

/chain/height GET エンドポイントは、現在のチェーン高を返します。

不可逆高 は、これ以上ロールバックできない最も高いブロックです。 NEM では、現在のチェーン高から 書き換え制限 を引いて計算します。

このチュートリアルでは、ループでチェーン高をポーリングし、不可逆高を計算して、チェーン高が最後に変化してからどれくらい時間が経過したかを追跡する方法を説明します。

前提条件⚓︎

このチュートリアルでは、SDK を必要とせずに NEM REST API を使用します。 HTTP リクエストを送信する方法だけが必要です。

完全なコード⚓︎

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

import json
import os
import time
import urllib.request

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
print(f'Using node {NODE_URL}')

prev_height = None
height_changed_at = None

REWRITE_LIMIT = 360

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

        height = int(chain_height['height'])

        irreversible_height = max(0, height - REWRITE_LIMIT)

        now = time.time()
        if prev_height is not None and height != prev_height:
            height_changed_at = now

        if height_changed_at is not None:
            height_ago = f'{int(now - height_changed_at)}s ago'
        else:
            height_ago = '-'

        print(
            f'Height: {height:>10,}  (changed {height_ago})'
            f'  |  Irreversible: {irreversible_height:>10,}'
        )

        prev_height = height
        time.sleep(1)

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

Download source

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
console.log(`Using node ${NODE_URL}`);

let prevHeight = null;
let heightChangedAt = null;

const REWRITE_LIMIT = 360;

for (;;) {
    const response = await fetch(`${NODE_URL}/chain/height`);
    if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);

    const chainHeight = await response.json();

    const height = parseInt(chainHeight.height, 10);

    const irreversibleHeight = Math.max(0, height - REWRITE_LIMIT);

    const now = Date.now();
    if (null !== prevHeight && height !== prevHeight)
        heightChangedAt = now;

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

    const heightLabel = height.toLocaleString().padStart(10);
    const irreversibleLabel =
        irreversibleHeight.toLocaleString().padStart(10);
    console.log(
        `Height: ${heightLabel}  (changed ${heightAgo})` +
        `  |  Irreversible: ${irreversibleLabel}`
    );

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

}

Download source

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

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 static final int REWRITE_LIMIT = 360;

    private final String nodeUrl = System.getenv().getOrDefault(
        "NODE_URL", "http://libertalia.nemtest.net:7890");

    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 heightChangedAt = null;

        while (true) {
            final HttpRequest request =
                HttpRequest.newBuilder(
                    URI.create(nodeUrl + "/chain/height")).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            if (200 != response.statusCode())
                throw new IOException("HTTP error! status: "
                    + response.statusCode());
            final JsonNode chainHeight = JSON_MAPPER.readTree(
                response.body());
            final long height = chainHeight.get("height").asLong();


            final long irreversibleHeight = Math.max(
                0, height - REWRITE_LIMIT);

            final long now = System.currentTimeMillis();
            if (null != prevHeight && height != prevHeight)
                heightChangedAt = now;
            final String heightAgo = null != heightChangedAt
                ? "%ds ago".formatted((now - heightChangedAt) / 1000)
                : "-";

            System.out.printf(
                "Height: %,10d  (changed %s)"
                    + "  |  Irreversible: %,10d%n",
                height, heightAgo, irreversibleHeight);
            prevHeight = height;
            Thread.sleep(1000);

        }
    }
}

Download source

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

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

コードの説明⚓︎

チェーン高を取得する⚓︎

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

        height = int(chain_height['height'])
    const response = await fetch(`${NODE_URL}/chain/height`);
    if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);

    const chainHeight = await response.json();

    const height = parseInt(chainHeight.height, 10);
            final HttpRequest request =
                HttpRequest.newBuilder(
                    URI.create(nodeUrl + "/chain/height")).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            if (200 != response.statusCode())
                throw new IOException("HTTP error! status: "
                    + response.statusCode());
            final JsonNode chainHeight = JSON_MAPPER.readTree(
                response.body());
            final long height = chainHeight.get("height").asLong();

ループのたびに、コードは /chain/height GET エンドポイントへ GET リクエストを送信します。 レスポンスには height フィールドが 1 つ含まれ、ノードが認識している最新のブロックである現在のチェーン高を返します。

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

不可逆高を計算する⚓︎

        irreversible_height = max(0, height - REWRITE_LIMIT)
    const irreversibleHeight = Math.max(0, height - REWRITE_LIMIT);
            final long irreversibleHeight = Math.max(
                0, height - REWRITE_LIMIT);

書き換え制限 は、NEM でロールバックによって取り消せるブロック数の上限で、360 ブロック(およそ 6 時間)に設定されています。

現在のチェーン高から書き換え制限を引くと、不可逆高 が得られます。

不可逆高以下のブロックは、これ以上ロールバックできません。

ロールバックと書き換え制限の詳細については、テキストブックの コンセンサス セクションを参照してください。

高さの変化を追跡する⚓︎

        now = time.time()
        if prev_height is not None and height != prev_height:
            height_changed_at = now

        if height_changed_at is not None:
            height_ago = f'{int(now - height_changed_at)}s ago'
        else:
            height_ago = '-'
    const now = Date.now();
    if (null !== prevHeight && height !== prevHeight)
        heightChangedAt = now;

    const heightAgo = null !== heightChangedAt ?
        `${Math.floor((now - heightChangedAt) / 1000)}s ago` :
        '-';
            final long now = System.currentTimeMillis();
            if (null != prevHeight && height != prevHeight)
                heightChangedAt = now;
            final String heightAgo = null != heightChangedAt
                ? "%ds ago".formatted((now - heightChangedAt) / 1000)
                : "-";

チェーン高が最後に変化してからの経過時間を表示するため、コードは前回の高さと、最後に更新された時刻を保存します。

新しいブロックが到着して高さが変化するたびに、タイムスタンプを更新します。 その後、経過時間を現在のチェーン高とともに表示します。

ポーリングループ⚓︎

        print(
            f'Height: {height:>10,}  (changed {height_ago})'
            f'  |  Irreversible: {irreversible_height:>10,}'
        )

        prev_height = height
        time.sleep(1)
    const heightLabel = height.toLocaleString().padStart(10);
    const irreversibleLabel =
        irreversibleHeight.toLocaleString().padStart(10);
    console.log(
        `Height: ${heightLabel}  (changed ${heightAgo})` +
        `  |  Irreversible: ${irreversibleLabel}`
    );

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

各ループでは、次の情報を示すステータス行を 1 行表示します。

  • 現在のチェーン高と、最後に変化してからの経過秒数。
  • 不可逆高。

その後、ノードをもう一度照会する前に 1 秒間スリープします。

出力⚓︎

以下の出力は、チェーン高と不可逆高を監視した場合の実行例です。

1
2
3
4
5
6
7
8
Using node http://libertalia.nemtest.net:7890
Height:    659,471  (changed -)  |  Irreversible:    659,111
Height:    659,471  (changed -)  |  Irreversible:    659,111
Height:    659,471  (changed -)  |  Irreversible:    659,111
Height:    659,472  (changed 0s ago)  |  Irreversible:    659,112
Height:    659,472  (changed 1s ago)  |  Irreversible:    659,112
Height:    659,472  (changed 2s ago)  |  Irreversible:    659,112
Height:    659,472  (changed 3s ago)  |  Irreversible:    659,112

出力の要点は次のとおりです。

  • 新しいブロックの前(2~4 行目): プログラムがポーリングを続ける間、チェーン高は変わりません。
  • 新しいブロックが到着(5 行目): チェーン高が 659,471 から 659,472 に進みます。 不可逆高も進みます。

チェーン高と不可逆高の違い

不可逆高は常に 書き換え制限 だけチェーン高より遅れます。 チェーンの先端付近にあるトランザクションは、まだロールバックされる可能性があります。 そのブロックの高さが不可逆高以下になると、不可逆になります。

まとめ⚓︎

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

手順 関連ドキュメント
チェーン高を取得する /chain/height GET

次のステップ⚓︎

新しいブロックをイベント駆動で監視する方法については、WebSocket チュートリアルの 新しいブロックをリッスンする を参照してください。