概要
トランザクションは署名とメッセージで構成されます。メッセージにはヘッダー、アカウントアドレス、最新のブロックハッシュ、コンパイル済みinstructionsが含まれます。シリアライズ後の最大サイズ:1,232バイト。
Transactionには、2つのトップレベルフィールドがあります:
signatures: 署名の配列message: 処理されるinstructionsのリストを含むトランザクション情報
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
トランザクションの2つの部分を示す図
トランザクションのシリアライズ後の合計サイズは、PACKET_DATA_SIZE(1,232バイト)を超えてはなりません。この制限は、1,280バイト(IPv6の最小MTU)からネットワークヘッダー用の48バイト(IPv6用40バイト + フラグメントヘッダー用8バイト)を引いた値です。1,232バイトには、signatures配列とmessage構造体の両方が含まれます。
トランザクション形式とサイズ制限を示す図
署名
signaturesフィールドは、Signature値のコンパクトエンコード配列です。各Signatureは、署名者アカウントの秘密鍵で署名された、シリアライズされたMessageの64バイトEd25519署名です。トランザクションのinstructionsによって参照される署名者アカウントごとに1つの署名が必要です。
配列の最初の署名は手数料支払者のものであり、トランザクションの基本手数料と優先手数料を支払うアカウントです。この最初の署名はトランザクションIDとしても機能し、ネットワーク上でトランザクションを検索するために使用されます。トランザクションIDは一般的にトランザクション署名と呼ばれます。
手数料支払者の要件:
- メッセージの最初のアカウント(インデックス0)であり、署名者である必要があります。
- システムプログラムが所有するアカウント、またはnonceアカウントである必要があります(
validate_fee_payerで検証されます)。 rent_exempt_minimum + total_feeをカバーするのに十分なlamportsを保持している必要があります。そうでない場合、トランザクションは*rsInsufficientFundsForFee*で失敗します。
メッセージ
messageフィールドは、トランザクションのペイロードを含むMessage構造体です:
header: メッセージヘッダーaccount_keys: トランザクションのinstructionsに必要なアカウントアドレスの配列recent_blockhash: トランザクションのタイムスタンプとして機能するブロックハッシュinstructions: instructionsの配列
pub struct Message {/// The message header, identifying signed and read-only `account_keys`.pub header: MessageHeader,/// All the account keys used by this transaction.#[serde(with = "short_vec")]pub account_keys: Vec<Pubkey>,/// The id of a recent ledger entry.pub recent_blockhash: Hash,/// Programs that will be executed in sequence and committed in/// one atomic transaction if all succeed.#[serde(with = "short_vec")]pub instructions: Vec<CompiledInstruction>,}
ヘッダー
headerフィールドは、account_keys配列を権限グループに分割する3つのu8フィールドを持つMessageHeader構造体です:
num_required_signatures: トランザクションに必要な署名の総数。num_readonly_signed_accounts: 読み取り専用の署名済みアカウントの数。num_readonly_unsigned_accounts: 読み取り専用の未署名アカウントの数。
pub struct MessageHeader {/// The number of signatures required for this message to be considered/// valid. The signers of those signatures must match the first/// `num_required_signatures` of [`Message::account_keys`].pub num_required_signatures: u8,/// The last `num_readonly_signed_accounts` of the signed keys are read-only/// accounts.pub num_readonly_signed_accounts: u8,/// The last `num_readonly_unsigned_accounts` of the unsigned keys are/// read-only accounts.pub num_readonly_unsigned_accounts: u8,}
メッセージヘッダーの3つの部分を示す図
アカウントアドレス
account_keysフィールドは、公開鍵のコンパクトエンコードされた配列です。各エントリは、トランザクションのinstructionsの少なくとも1つで使用されるアカウントを識別します。配列にはすべてのアカウントを含める必要があり、次の厳密な順序に従う必要があります:
- 署名者 + 書き込み可能
- 署名者 + 読み取り専用
- 非署名者 + 書き込み可能
- 非署名者 + 読み取り専用
この厳密な順序により、account_keys配列をメッセージのheader内の3つのカウントと組み合わせることで、アカウントごとのメタデータフラグを保存することなく、各アカウントの権限を決定できます。ヘッダーのカウントは、配列を上記の4つの権限グループに分割します。
アカウントアドレス配列の順序を示す図
最新のブロックハッシュ
recent_blockhashフィールドは、2つの目的を果たす32バイトのハッシュです。
- タイムスタンプ: トランザクションが最近作成されたことを証明します。
- 重複排除: 同じトランザクションが2回処理されることを防ぎます。
ブロックハッシュは150スロット後に期限切れになります。トランザクションが到着したときにブロックハッシュが有効でない場合、durable nonceトランザクションでない限り、*rsBlockhashNotFound*で拒否されます。
getLatestBlockhash
RPCメソッドを使用すると、現在のブロックハッシュと、そのブロックハッシュが有効な最後のブロック高を取得できます。
Instructions
instructionsフィールドは、CompiledInstruction構造体のコンパクトエンコードされた配列です。各*rsCompiledInstruction*は、完全な公開鍵ではなく、account_keys配列へのインデックスによってアカウントを参照します。これには以下が含まれます。
program_id_index: 呼び出すプログラムを識別するaccount_keysへのインデックス。accounts: プログラムに渡すアカウントを指定するaccount_keysへのインデックスの配列。data: instruction識別子とシリアライズされた引数を含むバイト配列。
pub struct CompiledInstruction {/// Index into the transaction keys array indicating the program account that executes this instruction.pub program_id_index: u8,/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.#[serde(with = "short_vec")]pub accounts: Vec<u8>,/// The program input data.#[serde(with = "short_vec")]pub data: Vec<u8>,}
Instructionsのコンパクト配列
トランザクションのバイナリ形式
トランザクションは、コンパクトエンコーディング方式を使用してシリアライズされます。すべての可変長配列(署名、アカウントキー、instructions)には、compact-u16長エンコーディングが接頭辞として付けられます。この形式は、0〜127の値には1バイト、より大きな値には2〜3バイトを使用します。
レガシートランザクションのレイアウト(ワイヤー上):
| フィールド | サイズ | 説明 |
|---|---|---|
num_signatures | 1〜3バイト(compact-u16) | 署名の数 |
signatures | num_signatures x 64バイト | Ed25519署名 |
num_required_signatures | 1バイト | MessageHeaderフィールド1 |
num_readonly_signed | 1バイト | MessageHeaderフィールド2 |
num_readonly_unsigned | 1バイト | MessageHeaderフィールド3 |
num_account_keys | 1〜3バイト(compact-u16) | 静的アカウントキーの数 |
account_keys | num_account_keys x 32バイト | 公開鍵 |
recent_blockhash | 32バイト | ブロックハッシュ |
num_instructions | 1〜3バイト(compact-u16) | instructionsの数 |
instructions | 可変 | コンパイル済みinstructionsの配列 |
コンパイルされた各instructionは次のようにシリアライズされます:
| フィールド | サイズ | 説明 |
|---|---|---|
program_id_index | 1バイト | アカウントキーへのインデックス |
num_accounts | 1-3バイト (compact-u16) | アカウントインデックスの数 |
account_indices | num_accounts x 1バイト | アカウントキーインデックス |
data_len | 1-3バイト (compact-u16) | instruction dataの長さ |
data | data_len バイト | 不透明なinstruction data |
サイズ計算
PACKET_DATA_SIZE =
1,232バイトの場合、利用可能なスペースは次のように計算できます:
Total = 1232 bytes- compact-u16(num_sigs) # 1 byte- num_sigs * 64 # signature bytes- 3 # message header- compact-u16(num_keys) # 1 byte- num_keys * 32 # account key bytes- 32 # recent blockhash- compact-u16(num_ixs) # 1 byte- sum(instruction_sizes) # per-instruction overhead + data
例: SOL転送トランザクション
以下の図は、トランザクションとinstructionsがどのように連携してユーザーがネットワークと対話できるようにするかを示しています。この例では、SOLがあるアカウントから別のアカウントに転送されます。
送信者アカウントのメタデータは、トランザクションに署名する必要があることを示しています。これにより、System Programはlamportを差し引くことができます。lamport残高を変更するには、送信者と受信者の両方のアカウントが書き込み可能である必要があります。このinstructionを実行するために、送信者のウォレットは署名とSOL転送instructionを含むメッセージを含むトランザクションを送信します。
SOL転送図
トランザクションが送信された後、System Programは転送instructionを処理し、両方のアカウントのlamport残高を更新します。
SOL転送プロセス図
以下の例は、上記の図に関連するコードを示しています。System
Programのtransfer関数を参照してください。
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Create a connection to clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Fund sender with airdropawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: sender.address,lamports: lamports(LAMPORTS_PER_SOL), // 1 SOLcommitment: "confirmed"});// Check balance before transferconst { value: preBalance1 } = await rpc.getBalance(sender.address).send();const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});// Add the transfer instruction to a new transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();const transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));// Send the transaction to the networkconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });const transactionSignature = getSignatureFromTransaction(signedTransaction);// Check balance after transferconst { value: postBalance1 } = await rpc.getBalance(sender.address).send();const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();console.log("Sender prebalance:",Number(preBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient prebalance:",Number(preBalance2) / Number(LAMPORTS_PER_SOL));console.log("Sender postbalance:",Number(postBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient postbalance:",Number(postBalance2) / Number(LAMPORTS_PER_SOL));console.log("Transaction Signature:", transactionSignature);
以下の例は、単一のSOL転送instructionを含むトランザクションの構造を示しています。
import {createSolanaRpc,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";const rpc = createSolanaRpc("http://localhost:8899");const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
以下のコードは、前のコードスニペットからの出力を示しています。 SDK間で形式は異なりますが、各インストラクションには同じ必須情報が含まれていることに注目してください。
{"version": 0,"header": {"numSignerAccounts": 1,"numReadonlySignerAccounts": 0,"numReadonlyNonSignerAccounts": 1},"staticAccounts": ["HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa","5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL","11111111111111111111111111111111"],"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1","instructions": [{"programAddressIndex": 2,"accountIndices": [0, 1],"data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}]}
トランザクション詳細の取得
送信後、トランザクション署名とgetTransaction RPCメソッドを使用してトランザクション詳細を取得します。
Solana Explorerを使用してトランザクションを見つけることもできます。
{"blockTime": 1745196488,"meta": {"computeUnitsConsumed": 150,"err": null,"fee": 5000,"innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [989995000, 10000000, 1],"postTokenBalances": [],"preBalances": [1000000000, 0, 1],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 13049,"transaction": {"message": {"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"accountKeys": ["8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ","7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma","11111111111111111111111111111111"],"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf","instructions": [{"accounts": [0, 1],"data": "3Bxs4NN8M2Yn4TLb","programIdIndex": 2,"stackHeight": null}],"indexToProgramIds": {}},"signatures": ["3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"]},"version": "legacy"}
Is this page helpful?