メインコンテンツへスキップ
Laserstreamからトランザクションデータを受け取ったとき、重要なことが2つあります。
  • Message → ユーザーがやりたかったこと(署名済みの提案)
  • Meta → 実際に起きたこと(実行結果)
課題: 生のトランザクションデータは、読みやすいアドレスや署名の代わりにバイナリのバイト配列(<Buffer 00 bf a0 e8...>のような形式)で提供されます。 このガイドは、そのバイナリデータを人間が読みやすい形式にデコードし、意味のある情報を抽出し、提案から実行までの完全なトランザクションストーリーを理解する方法を示します。

ライブストリーム、デコードなし

以下の最小クライアントを実行します。フィルターフラグは投票および失敗したトランザクションをドロップし、accountInclude配列はJupiterプログラムIDに触れる活動の結果を制限します。
コンソールには、ラッパー(filters, createdAt)と、2つの子を隠すtransactionブランチが表示されます。
  • transaction.transaction.transaction → 署名済みのメッセージ
  • transaction.transaction.meta → 実行メタ
Uint8Arrayのように見えるものは、その時点では不透明なままです。 デコード関数を使ってスクリプトを実行すると、読みやすいアドレスを持つ実際のネスト構造が見られます。

バイナリデータのデコード

デコードの理由: 生のLaserstreamデータには、署名、アカウントキー、ハッシュがバイナリUint8Arrayオブジェクトとして含まれており、読み取りができません。これらをbase58文字列に変換する必要があります。 解決策: Laserstreamは、Yellowstone gRPCを使用しており、組み込みのデコードユーティリティを提供しています。各フィールドタイプのデコーダーを書く代わりに、再帰関数を使ってすべてのバイナリデータを人間が読みやすい形式に変換します。
このアプローチは、組み込みのデコードを活用しながら、手動変換が必要なバイナリフィールドを処理します。トランザクション構造はすでに解析されているので、バイナリフィールドを人間が読みやすい形式に変換するだけです。

トランザクション構造の理解

デコードされたデータが見えるようになったので、Laserstreamトランザクションアップデートの主な2つの部分を探ります。最初の例から、各トランザクションには2つのキーオブジェクトが含まれていることを思い出してください。
  • Message (Proposal)transaction.transaction.transaction→ 署名されたメッセージ(ユーザーの提案)
  • Meta (Execution)transaction.transaction.meta→ 実行メタデータ(バリデーターの応答)
この2部構成は、ユーザーの要求と実際に起こったことの完全なストーリーを伝えます。各部分を詳細に検討していきましょう。

提案: メッセージ内部のすべて

ユーザーは何を誰が、そしていつまでを指定するメッセージを作成します。以下に各部分をデコードする方法を示します。

トランザクションヘッダー

numRequiredSignaturesは、バリデーターにどれだけの署名を確認するかを伝え、2つのnumReadonly*の値は、ランタイムが読み取り専用として扱えるアカウントをラベル付けし、並列実行を可能にします。

アカウントキー辞書

accountKeysは公開鍵の単純なリストで、検索テーブルとして機能します。トランザクション内の後の整数(programIdIndexや命令のaccounts配列内の各要素)がこのリストにインデックスを使用して戻ります。これによりメッセージあたり1キロバイト以上を節約できます。

リプレイ防止

recentBlockhashは、最後の150のブロックハッシュをスクロールすると期限切れになります。メインネットで約90秒です。

命令: 実際のコマンド

各命令には3つの主要な部分があります。
  • Program ID (programIdIndex): accountKeys配列内のアドレスにポイントします(例: インデックス10 = ComputeBudget111111111111111111111111111111
  • Accounts (accounts): この命令がどのアカウントインデックスに触れるのかを表すbase58エンコードされた文字列
  • Data (data): base58でエンコードされた実際の命令データ
convertBuffers関数により、アカウントはbase58として表示されますが、実際にはアカウントインデックスを含んでいます(例: "3vtmrQMafzDoG2CBz1iqgXPTnC"はインデックス[21, 19, 12, 17, 2, 6, 1, 22]にデコードされます) この設計により、フルの32バイトのアドレスを繰り返す代わりに、各命令はルックアップテーブル内の位置を参照するだけで済みます。

署名: 認証の証明

signaturesには必要なアカウントがこのトランザクションを承認したことを証明する暗号署名が含まれています。署名の数はheader.numRequiredSignaturesと一致する必要があります。

アドレスタブルックアップ

versionedtrueの場合、addressTableLookupsはオンチェーンテーブルと2つのインデックスリストと共に表示されます。ルックアップテーブルにより、パケットを1,232バイトのMTU以下に保ちながら、アドレスカウントのハードキャップを数十に引き上げます。

すべてがどのように接続するか: フロー

基本原理からの流れ:
  1. ルックアップテーブルを作成: accountKeysはこのトランザクションが触れるすべてのアドレスをリストします
  2. ルールを設定: headerは署名が必要な数と、読み取り専用のアカウントを指定します
  3. コマンドを作成: 各instructionは以下を指します:
    • プログラム (programIdIndexaccountKeys[index])
    • 必要なアカウント (accounts→ 複数のaccountKeys[index]位置)
    • 命令データ (dataでエンコード)
  4. 認証を追加: signaturesは、必要なアカウントがこのトランザクションを承認したことを証明します
  5. 期限を設定: recentBlockhashは、このトランザクションが後で再利用されないようにします

実行: メタ内部のすべて

メッセージはユーザーが何をしたかったのかを示し、メタはバリデーターがトランザクションを実行したときに実際に起こったことを示します。

基本的な実行情報

成功/失敗
  • err: null = 成功
  • err: {...} = エラー詳細付きの失敗
  • fee = このトランザクションのために課金されたlamports
残高変動
バランス配列は、インデックスでaccountKeys配列に対応します。
  • アカウント0: 15000 lamportsの喪失(手数料支払い)
  • アカウント1: 1461600 lamportsの獲得(新しいアカウント作成)
  • アカウント3: 2001231920 lamportsの獲得(プログラムアカウント)
計算使用量
要求された量からどれだけの計算予算が使用されたかを示します。

高度な実行詳細

内部命令
内部命令は、実行中にプログラムが呼び出した追加命令です。元のトランザクションの一部ではなく、主命令によって引き起こされました。 ログメッセージ
ログメッセージは、プログラム実行の時系列のトレースを提供し、どのプログラムが呼び出され、どのようなカスタムログメッセージを出力したかを示します。 トークン残高の変動
トークン残高の変動は、SPLトークンアカウントの前後の状態を示し、適切な小数処理を備えた人間が読みやすい金額を含んでいます。

実用的なデコードパターン

デコードされたトランザクションから有用な情報を抽出するための一般的なパターンを紹介します。

完全な例: Jupiterスワップデコーダ

Jupiterスワップトランザクションをデコードし、意味のある情報を抽出する完全な例を示します。
この例は、メッセージデコードとメタ分析を組み合わせて、複雑なDeFiトランザクションからビジネス関連の情報を抽出する方法を示しています。

主なポイント

  • 二部構成: すべてのトランザクションは、メッセージ(要求されたこと)とメタ(実際に起こったこと)を持っています
  • バイナリデコード: bs58.encode()を使用してバイナリフィールドを読みやすいbase58文字列に変換します
  • アカウントキーのルックアップ: 命令はaccountKeys配列のインデックスでアカウントを参照します
  • 残高追跡: preBalancespostBalancesを比較して何が変わったかを確認します
Solanaトランザクションを理解する鍵は、それが効率性を重視して設計されていることを認識することです。アドレスを繰り返す代わりに、ルックアップテーブルとインデックスを使用してトランザクションサイズを最小限に保ちながら、情報の密度を最大化します。