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

トランザクションステータスの監視⚓︎

Symbol ネットワークに トランザクション をアナウンスした後、それが ブロック に含まれるまでは未承認の状態が続きます。

ステータスの変化を監視することは、トランザクションの承認や失敗に反応できる、レスポンスの高いアプリケーションを構築する上で不可欠です。

このチュートリアルでは、トランザクションが未承認から承認済みへと移行する際のステータスを監視する方法を説明します。

承認されたトランザクションでも覆る可能性があります

承認されたトランザクションはブロックに含まれていますが、まだ不可逆ではありません。 最終的な状態は ファイナライズ であり、これはブロックがネットワークによってファイナライズされた後にのみ発生します。 それまでは、 ロールバック が発生する可能性がまだあります。

前提条件⚓︎

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

完全なコード⚓︎

このチュートリアルでは、トランザクションのステータスを確認するためにポーリングを使用します。 ここでは説明の目的でポーリングを使用していますが、本番環境のアプリケーションには推奨されるアプローチではありません。 WebSocket を使用すると、API 呼び出しを繰り返すオーバーヘッドなしに、よりレスポンスの高いソリューションを提供できます。

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

import json
import os
import time
import urllib.request

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

# Transaction hash to monitor
transaction_hash = os.getenv(
    "TRANSACTION_HASH",
    "2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA",
)

print(f"Monitoring transaction: {transaction_hash}")


def wait_for_transaction_confirmation(
    transaction_hash, max_attempts=60, wait_seconds=2
):
    """
    Poll the transaction status endpoint until the transaction
    is confirmed.

    Args:
        transaction_hash: The hash of the transaction to monitor
        max_attempts: Maximum number of polling attempts for confirmation
        wait_seconds: Seconds to wait between attempts

    Returns:
        True if transaction was confirmed
    """
    status_path = f"/transactionStatus/{transaction_hash}"
    print(f"\nWaiting for transaction confirmation")
    print(f"Polling {status_path}")

    for attempt in range(1, max_attempts + 1):
        try:
            # Query the transaction status endpoint
            url = f"{NODE_URL}{status_path}"
            with urllib.request.urlopen(url) as response:
                response_json = json.loads(response.read().decode())

                # Parse the response
                status_group = response_json["group"]
                status_code = response_json["code"]
                status_hash = response_json["hash"]
                status_deadline = response_json["deadline"]

                print(f"  Attempt {attempt}:")
                print(f"    Status: {status_group}")
                print(f"    Code: {status_code}")
                print(f"    Hash: {status_hash}")
                print(f"    Deadline: {status_deadline}")

                # Check if the transaction has been confirmed
                if status_group == "confirmed":
                    print(f"\nTransaction confirmed!")
                    return True

                # Check if the transaction failed
                if status_group == "failed":
                    print(
                        f"\nTransaction failed with code: {status_code}"
                    )
                    raise RuntimeError(
                        f"Transaction failed: {status_code}"
                    )

        except Exception as e:
            if hasattr(e, 'code') and e.code == 404:
                print(
                    f"  Attempt {attempt}: Transaction status not "
                    "yet available"
                )
            else:
                raise

        # Wait before next attempt (except on last attempt)
        if attempt < max_attempts:
            time.sleep(wait_seconds)

    print(f"\nTransaction not confirmed after {max_attempts} attempts")
    raise RuntimeError(
        f"Transaction {transaction_hash} not confirmed in time"
    )


# Monitor the transaction until it's confirmed
wait_for_transaction_confirmation(transaction_hash)

Download source

// Configuration
const NODE_URL = process.env.NODE_URL||
    'https://reference.symboltest.net:3001';
console.log('Using node', NODE_URL);

// Transaction hash to monitor.
const transactionHash = process.env.TRANSACTION_HASH ||
    '2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA';

console.log(`Monitoring transaction: ${transactionHash}`);

/**
 * Poll the transaction status endpoint until the transaction is confirmed.
 *
 * @param {string} transactionHash - The hash of the transaction to monitor
 * @param {number} maxAttempts - Maximum number of polling attempts
 *   for confirmation
 * @param {number} waitSeconds - Seconds to wait between attempts
 * @returns {boolean} True if transaction was confirmed
 */
async function waitForTransactionConfirmation(
    transactionHash,
    maxAttempts = 60,
    waitSeconds = 2
) {
    const statusPath = `/transactionStatus/${transactionHash}`;
    console.log(`\nWaiting for transaction confirmation`);
    console.log(`Polling ${statusPath}`);

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            // Query the transaction status endpoint
            const statusResponse = await fetch(`${NODE_URL}${statusPath}`);

            if (!statusResponse.ok) {
                const status = statusResponse.status;
                const statusText = statusResponse.statusText;
                const error = new Error(`HTTP ${status}: ${statusText}`);
                error.status = statusResponse.status;
                throw error;
            }

            const statusJSON = await statusResponse.json();

            // Parse the response
            const statusGroup = statusJSON.group;
            const statusCode = statusJSON.code;
            const statusHash = statusJSON.hash;
            const statusDeadline = statusJSON.deadline;

            console.log(`  Attempt ${attempt}:`);
            console.log(`    Status: ${statusGroup}`);
            console.log(`    Code: ${statusCode}`);
            console.log(`    Hash: ${statusHash}`);
            console.log(`    Deadline: ${statusDeadline}`);

            // Check if the transaction has been confirmed
            if (statusGroup === 'confirmed') {
                console.log(`\nTransaction confirmed!`);
                return true;
            }

            // Check if the transaction failed
            if (statusGroup === 'failed') {
                console.log(
                    `\nTransaction failed with code: ${statusCode}`
                );
                throw new Error(`Transaction failed: ${statusCode}`);
            }

        } catch (error) {
            if (error.status === 404) {
                console.log(
                    `  Attempt ${attempt}: Transaction status not yet ` +
                    `available`
                );
            } else {
                throw error;
            }
        }

        // Wait before next attempt (except on last attempt)
        if (attempt < maxAttempts) {
            await new Promise((resolve) =>
                setTimeout(resolve, waitSeconds * 1000)
            );
        }
    }

    console.log(
        `\nTransaction not confirmed after ${maxAttempts} attempts`
    );
    throw new Error(
        `Transaction ${transactionHash} not confirmed in time`
    );
}

// Monitor the transaction until it's confirmed
await waitForTransactionConfirmation(transactionHash);

Download source

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

コード解説⚓︎

トランザクションハッシュの特定⚓︎

transaction_hash = os.getenv(
    "TRANSACTION_HASH",
    "2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA",
)
const transactionHash = process.env.TRANSACTION_HASH ||
    '2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA';

トランザクションを監視するには、署名後に生成されるハッシュが必要です。 ハッシュは、Symbol ネットワーク上でトランザクションを一意に識別します。

このチュートリアルでは、監視を実演するためにサンプルのトランザクションハッシュを使用します。 コードの実行時に TRANSACTION_HASH 環境変数を設定することで、独自のハッシュを提供できます。

実際には、トランザクションに署名した直後にこのハッシュを取得し(例については 転送チュートリアル を参照)、そのステータスを追跡するために使用します。

ステータスエンドポイントへの照会⚓︎

def wait_for_transaction_confirmation(
    transaction_hash, max_attempts=60, wait_seconds=2
):
    """
    Poll the transaction status endpoint until the transaction
    is confirmed.

    Args:
        transaction_hash: The hash of the transaction to monitor
        max_attempts: Maximum number of polling attempts for confirmation
        wait_seconds: Seconds to wait between attempts

    Returns:
        True if transaction was confirmed
    """
    status_path = f"/transactionStatus/{transaction_hash}"
    print(f"\nWaiting for transaction confirmation")
    print(f"Polling {status_path}")

    for attempt in range(1, max_attempts + 1):
        try:
            # Query the transaction status endpoint
            url = f"{NODE_URL}{status_path}"
            with urllib.request.urlopen(url) as response:
                response_json = json.loads(response.read().decode())

                # Parse the response
                status_group = response_json["group"]
                status_code = response_json["code"]
                status_hash = response_json["hash"]
                status_deadline = response_json["deadline"]

                print(f"  Attempt {attempt}:")
                print(f"    Status: {status_group}")
                print(f"    Code: {status_code}")
                print(f"    Hash: {status_hash}")
                print(f"    Deadline: {status_deadline}")
/**
 * Poll the transaction status endpoint until the transaction is confirmed.
 *
 * @param {string} transactionHash - The hash of the transaction to monitor
 * @param {number} maxAttempts - Maximum number of polling attempts
 *   for confirmation
 * @param {number} waitSeconds - Seconds to wait between attempts
 * @returns {boolean} True if transaction was confirmed
 */
async function waitForTransactionConfirmation(
    transactionHash,
    maxAttempts = 60,
    waitSeconds = 2
) {
    const statusPath = `/transactionStatus/${transactionHash}`;
    console.log(`\nWaiting for transaction confirmation`);
    console.log(`Polling ${statusPath}`);

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            // Query the transaction status endpoint
            const statusResponse = await fetch(`${NODE_URL}${statusPath}`);

            if (!statusResponse.ok) {
                const status = statusResponse.status;
                const statusText = statusResponse.statusText;
                const error = new Error(`HTTP ${status}: ${statusText}`);
                error.status = statusResponse.status;
                throw error;
            }

            const statusJSON = await statusResponse.json();

            // Parse the response
            const statusGroup = statusJSON.group;
            const statusCode = statusJSON.code;
            const statusHash = statusJSON.hash;
            const statusDeadline = statusJSON.deadline;

            console.log(`  Attempt ${attempt}:`);
            console.log(`    Status: ${statusGroup}`);
            console.log(`    Code: ${statusCode}`);
            console.log(`    Hash: ${statusHash}`);
            console.log(`    Deadline: ${statusDeadline}`);

wait_for_transaction_confirmation 関数は、このチュートリアルの中核です。 トランザクションが承認されるか失敗するまで監視します。

for ループを使用して、デフォルトで最大60回(2秒間隔で2分間)トランザクションのステータスを確認します。 このループ構造により、トランザクションが承認されなかった場合でも、最終的には監視が停止することが保証されます。

各試行で、関数は /transactionStatus/{hash} GET エンドポイントに照会し、トランザクションの現在の状態に関する情報を返します。

レスポンスには以下が含まれます。

  • Group (グループ): トランザクションの現在のステータスグループ。可能な値は以下の通りです。

    グループ 意味
    unconfirmed トランザクションは 未承認トランザクションプール にあり、ブロックに含まれるのを待っている。
    confirmed トランザクションがブロックに取り込まれた。
    failed トランザクションが検証に失敗し、拒否された。
    partial アグリゲートボンデッドトランザクション連署待ち。
  • Code (コード): より詳細な情報を提供するステータスコード(例: Success や特定のエラーコード)。 すべての可能な値については、 TransactionStatusEnum スキーマを参照してください。

  • Hash (ハッシュ): 監視されているトランザクションハッシュ。
  • Deadline (有効期限): ネットワーク時間でのトランザクションの有効期限。

関数はポーリングを試行するたびにこれらのフィールドすべてを表示するため、トランザクションがどのように状態を移行していくかを確認できます。

承認の確認⚓︎

                # Check if the transaction has been confirmed
                if status_group == "confirmed":
                    print(f"\nTransaction confirmed!")
                    return True
            // Check if the transaction has been confirmed
            if (statusGroup === 'confirmed') {
                console.log(`\nTransaction confirmed!`);
                return true;
            }

レスポンスを解析した後、関数は group フィールドを確認します。 それが confirmed の場合、トランザクションは ハーベスティング を通じて正常にブロックに含まれており、関数は正常に終了します。

失敗の確認⚓︎

                # Check if the transaction failed
                if status_group == "failed":
                    print(
                        f"\nTransaction failed with code: {status_code}"
                    )
                    raise RuntimeError(
                        f"Transaction failed: {status_code}"
                    )
            // Check if the transaction failed
            if (statusGroup === 'failed') {
                console.log(
                    `\nTransaction failed with code: ${statusCode}`
                );
                throw new Error(`Transaction failed: ${statusCode}`);
            }

トランザクションのステータスグループが failed の場合、関数はステータスコードとともにエラーを発生させます。

一般的な理由には、残高不足、無効な 署名、または有効期限切れが含まれます。 失敗したトランザクションは検証中に拒否され、ブロックには含まれません。

すべての可能なコードについては、 TransactionStatusEnum を参照してください。

不明なステータスの処理⚓︎

        except Exception as e:
            if hasattr(e, 'code') and e.code == 404:
                print(
                    f"  Attempt {attempt}: Transaction status not "
                    "yet available"
                )
            else:
                raise
        } catch (error) {
            if (error.status === 404) {
                console.log(
                    `  Attempt ${attempt}: Transaction status not yet ` +
                    `available`
                );
            } else {
                throw error;
            }
        }

エンドポイントが HTTP 404 を返した場合、トランザクションのステータスはまだ利用できません。 これは、トランザクションをアナウンスした直後で ノード が処理する前、またはハッシュが無効な場合に発生する可能性があります。 関数はこのケースを処理し、試行をログに記録してポーリングを続行します。

その他のエラー(接続の問題や失敗したトランザクションなど)については、関数は直ちに例外を再発生させます。

試行間の待機⚓︎

        # Wait before next attempt (except on last attempt)
        if attempt < max_attempts:
            time.sleep(wait_seconds)
        // Wait before next attempt (except on last attempt)
        if (attempt < maxAttempts) {
            await new Promise((resolve) =>
                setTimeout(resolve, waitSeconds * 1000)
            );
        }

ポーリングの試行間に、関数は設定可能な遅延時間(デフォルト:2秒)だけ待機します。 これにより、ノードへのリクエストの過負荷を防ぎ、ネットワーク処理のための時間を確保します。

タイムアウトの処理⚓︎

    print(f"\nTransaction not confirmed after {max_attempts} attempts")
    raise RuntimeError(
        f"Transaction {transaction_hash} not confirmed in time"
    )
    console.log(
        `\nTransaction not confirmed after ${maxAttempts} attempts`
    );
    throw new Error(
        `Transaction ${transactionHash} not confirmed in time`
    );

指定された回数試行してもトランザクションが承認されない場合、関数は問題を説明する RuntimeError を発生させます。

これにより、呼び出し側のコードはトランザクションが予想された時間枠内に完了しなかったことを認識し、以下のような適切なアクションを取ることができます。

  • トランザクションのアナウンスの再試行
  • ユーザーへの警告
  • 調査のための問題のログ記録

出力⚓︎

以下の出力は、トランザクションが未承認から承認済みへと移行する様子を監視する典型的な実行例を示しています。

Using node https://reference.symboltest.net:3001
Monitoring transaction: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA

Waiting for transaction confirmation
Polling /transactionStatus/2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
  Attempt 1: Transaction status not yet available
  Attempt 2: Transaction status not yet available
  Attempt 3:
    Status: unconfirmed
    Code: Success
    Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
    Deadline: 47578965854
  Attempt 4:
    Status: unconfirmed
    Code: Success
    Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
    Deadline: 47578965854
  Attempt 5:
    Status: confirmed
    Code: Success
    Hash: 2B6D3B5232E06B9D32682F518C765301FCF9716BFA1EEEF9523653406E04C7EA
    Deadline: 47578965854

Transaction confirmed!

出力のポイント:

  1. 監視されているトランザクションハッシュ。
  2. トランザクションのステータス、コード、ハッシュ、および有効期限を示す複数回のポーリング試行。
  3. 試行の間にステータスが unconfirmed から confirmed に変化しています。
  4. トランザクションが承認されたときの成功メッセージ。

試行回数とタイミングは、ネットワークの状態とブロックの生成速度によって異なります。 Symbol ネットワークでは、ブロックは通常30秒ごとに生成されるため、トランザクションが承認されるまでに数回の unconfirmed ステータスレスポンスが表示される場合があります。

承認されると、トランザクションハッシュを使用して /transactions/confirmed/{transactionId} GET に照会することで、トランザクションが含まれたブロック高などの追加の詳細を取得できます。

ネットワークの観点からトランザクションを確認するには、 Symbol Testnet Explorer にアクセスし、トランザクションハッシュを検索してください。

結論⚓︎

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

ステップ 関連ドキュメント
ステータスエンドポイントへの照会 /transactionStatus/{hash} GET
承認の確認 TransactionStatusEnum
失敗の確認 TransactionStatusEnum

次のステップ⚓︎

本番環境のアプリケーションでは、以下の改善を検討してください。

  • ファイナライズの待機: トランザクションが含まれるブロックがファイナライズされたことを検証し、それが真に不可逆であることを保証します。 チェーンとファイナライズの最新高の照会 を参照してください。
  • トランザクション混入の証明: マークル証明を使用して、トランザクションがブロックの一部であることを暗号学的に検証します。 ブロックへのトランザクション混入の証明 を参照してください。
  • 複数のノードへの照会: より高い信頼性と単一ノードの問題からの保護のために、複数のノードにわたってステータスとファイナライズを確認します。
  • WebSocket の使用: ポーリングを WebSocket サブスクリプションに置き換え、API 呼び出しを繰り返すことなくリアルタイムの更新を受け取ります。 WebSocket チュートリアルの トランザクションフローのリスニング を参照してください。