Transaktionsstruktur

Zusammenfassung

Eine Transaktion besteht aus Signaturen + einer Nachricht. Die Nachricht enthält einen Header, Kontoadressen, einen aktuellen Blockhash und kompilierte Anweisungen. Maximale serialisierte Größe: 1.232 Bytes.

Eine Transaction hat zwei Felder auf oberster Ebene:

  • signatures: Ein Array von Signaturen
  • message: Transaktionsinformationen, einschließlich der Liste der zu verarbeitenden Anweisungen
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Diagramm, das die zwei Teile einer Transaktion zeigtDiagramm, das die zwei Teile einer Transaktion zeigt

Die gesamte serialisierte Größe einer Transaktion darf PACKET_DATA_SIZE (1.232 Bytes) nicht überschreiten. Dieses Limit entspricht 1.280 Bytes (die IPv6-Mindest-MTU) minus 48 Bytes für Netzwerk-Header (40 Bytes IPv6 + 8 Bytes Fragment-Header). Die 1.232 Bytes umfassen sowohl das signatures-Array als auch die message-Struktur.

Diagramm, das das Transaktionsformat und die Größenlimits zeigtDiagramm, das das Transaktionsformat und die Größenlimits zeigt

Signaturen

Das signatures-Feld ist ein kompakt kodiertes Array von Signature-Werten. Jede Signature ist eine 64-Byte-Ed25519-Signatur der serialisierten Message, signiert mit dem privaten Schlüssel des Signer-Kontos. Eine Signatur ist für jedes Signer-Konto erforderlich, auf das die Anweisungen der Transaktion verweisen.

Die erste Signatur im Array gehört zum Fee Payer, dem Konto, das die Basisgebühr und Priorisierungsgebühr der Transaktion bezahlt. Diese erste Signatur dient auch als Transaktions-ID, die verwendet wird, um die Transaktion im Netzwerk nachzuschlagen. Die Transaktions-ID wird üblicherweise als Transaktionssignatur bezeichnet.

Anforderungen an den Gebührenzahler:

  • Muss das erste Konto in der Nachricht sein (Index 0) und ein Signer.
  • Muss ein Konto im Besitz des System-Programms oder ein Nonce-Konto sein (validiert durch validate_fee_payer).
  • Muss genügend Lamports enthalten, um rent_exempt_minimum + total_fee abzudecken; andernfalls schlägt die Transaktion mit InsufficientFundsForFee fehl.

Nachricht

Das Feld message ist eine Message Struktur, die die Nutzlast der Transaktion enthält:

  • header: Der Nachrichten-Header
  • account_keys: Ein Array von Kontoadressen, die für die Anweisungen der Transaktion erforderlich sind
  • recent_blockhash: Ein Blockhash, der als Zeitstempel für die Transaktion dient
  • instructions: Ein Array von Anweisungen
Message
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>,
}

Das Feld header ist eine MessageHeader Struktur mit drei u8-Feldern, die das Array account_keys in Berechtigungsgruppen unterteilen:

  • num_required_signatures: Gesamtanzahl der von der Transaktion benötigten Signaturen.
  • num_readonly_signed_accounts: Anzahl der signierten Konten, die schreibgeschützt sind.
  • num_readonly_unsigned_accounts: Anzahl der nicht signierten Konten, die schreibgeschützt sind.
MessageHeader
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,
}

Diagramm, das die drei Teile des Nachrichten-Headers zeigtDiagramm, das die drei Teile des Nachrichten-Headers zeigt

Kontoadressen

Das Feld account_keys ist ein kompakt codiertes Array von öffentlichen Schlüsseln. Jeder Eintrag identifiziert ein Konto, das von mindestens einer der Anweisungen der Transaktion verwendet wird. Das Array muss jedes Konto enthalten und muss dieser strikten Reihenfolge folgen:

  1. Signer + beschreibbar
  2. Signer + schreibgeschützt
  3. Nicht-Signer + beschreibbar
  4. Nicht-Signer + schreibgeschützt

Diese strikte Reihenfolge ermöglicht es, das Array account_keys mit den drei Zählwerten im header der Nachricht zu kombinieren, um die Berechtigungen für jedes Konto zu bestimmen, ohne Metadaten-Flags pro Konto zu speichern. Die Header-Zählwerte unterteilen das Array in die vier oben aufgeführten Berechtigungsgruppen.

Diagramm, das die Reihenfolge des Arrays der Kontoadressen zeigtDiagramm, das die Reihenfolge des Arrays der Kontoadressen zeigt

Aktueller Blockhash

Das Feld recent_blockhash ist ein 32-Byte-Hash, der zwei Zwecke erfüllt:

  1. Zeitstempel: beweist, dass die Transaktion kürzlich erstellt wurde.
  2. Deduplizierung: verhindert, dass dieselbe Transaktion zweimal verarbeitet wird.

Ein Blockhash läuft nach 150 Slots ab. Wenn der Blockhash nicht mehr gültig ist, wenn die Transaktion eintrifft, wird sie mit BlockhashNotFound abgelehnt, es sei denn, es handelt sich um eine gültige Durable-Nonce-Transaktion.

Die RPC-Methode getLatestBlockhash ermöglicht es Ihnen, den aktuellen Blockhash und die letzte Blockhöhe abzurufen, bei der der Blockhash gültig sein wird.

Anweisungen

Das Feld instructions ist ein kompakt codiertes Array von CompiledInstruction-Strukturen. Jede CompiledInstruction referenziert Konten über einen Index im Array account_keys und nicht über den vollständigen öffentlichen Schlüssel. Sie enthält:

  1. program_id_index: Index in account_keys, der das aufzurufende Programm identifiziert.
  2. accounts: Array von Indizes in account_keys, das die an das Programm zu übergebenden Konten angibt.
  3. data: Byte-Array, das den Anweisungsdiskriminator und serialisierte Argumente enthält.
CompiledInstruction
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>,
}

Kompaktes Array von AnweisungenKompaktes Array von Anweisungen

Binärformat von Transaktionen

Transaktionen werden mit einem kompakten Codierungsschema serialisiert. Alle Arrays mit variabler Länge (Signaturen, Kontoschlüssel, Anweisungen) werden mit einer Compact-u16-Längencodierung vorangestellt. Dieses Format verwendet 1 Byte für Werte von 0-127 und 2-3 Bytes für größere Werte.

Legacy-Transaktionslayout (auf der Leitung):

FeldGrößeBeschreibung
num_signatures1-3 Bytes (compact-u16)Anzahl der Signaturen
signaturesnum_signatures x 64 BytesEd25519-Signaturen
num_required_signatures1 ByteMessageHeader Feld 1
num_readonly_signed1 ByteMessageHeader Feld 2
num_readonly_unsigned1 ByteMessageHeader Feld 3
num_account_keys1-3 Bytes (compact-u16)Anzahl der statischen Kontoschlüssel
account_keysnum_account_keys x 32 BytesÖffentliche Schlüssel
recent_blockhash32 BytesBlockhash
num_instructions1-3 Bytes (compact-u16)Anzahl der Anweisungen
instructionsvariabelArray von kompilierten Anweisungen

Jede kompilierte Anweisung wird wie folgt serialisiert:

FeldGrößeBeschreibung
program_id_index1 ByteIndex in Kontenschlüssel
num_accounts1-3 Bytes (compact-u16)Anzahl der Kontenindizes
account_indicesnum_accounts x 1 ByteKontenschlüsselindizes
data_len1-3 Bytes (compact-u16)Länge der instruction data
datadata_len BytesOpake instruction data

Größenberechnung

Bei PACKET_DATA_SIZE = 1.232 Bytes kann der verfügbare Platz wie folgt berechnet werden:

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

Beispiel: SOL-Übertragungstransaktion

Das folgende Diagramm zeigt, wie Transaktionen und Anweisungen zusammenarbeiten, um Benutzern die Interaktion mit dem Netzwerk zu ermöglichen. In diesem Beispiel wird SOL von einem Konto auf ein anderes übertragen.

Die Metadaten des Absenderkontos geben an, dass es die Transaktion signieren muss. Dies ermöglicht es dem System Program, Lamports abzuziehen. Sowohl das Absender- als auch das Empfängerkonto müssen beschreibbar sein, damit sich ihr Lamport-Guthaben ändern kann. Um diese Anweisung auszuführen, sendet die Wallet des Absenders die Transaktion mit ihrer Signatur und der Nachricht, die die SOL-Übertragungsanweisung enthält.

SOL-ÜbertragungsdiagrammSOL-Übertragungsdiagramm

Nachdem die Transaktion gesendet wurde, verarbeitet das System Program die Übertragungsanweisung und aktualisiert das Lamport-Guthaben beider Konten.

SOL-ÜbertragungsprozessdiagrammSOL-Übertragungsprozessdiagramm

Das folgende Beispiel zeigt den Code, der für die obigen Diagramme relevant ist. Siehe die transfer-Funktion des System Programs.

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 cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const 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 airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { 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 network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { 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);
Console
Click to execute the code.

Das folgende Beispiel zeigt die Struktur einer Transaktion, die eine einzelne SOL-Übertragungsanweisung enthält.

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 keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Console
Click to execute the code.

Der folgende Code zeigt die Ausgabe der vorherigen Code-Snippets. Das Format unterscheidet sich zwischen den SDKs, aber beachten Sie, dass jede Instruktion dieselben erforderlichen Informationen enthält.

{
"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
}
}
]
}

Transaktionsdetails abrufen

Nach der Übermittlung können Sie die Transaktionsdetails mithilfe der Transaktionssignatur und der RPC-Methode getTransaction abrufen.

Sie können die Transaktion auch über den Solana Explorer finden.

Transaction Data
{
"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?

Inhaltsverzeichnis

Seite bearbeiten

Verwaltet von

© 2026 Solana Foundation.
Alle Rechte vorbehalten.
Verbinden Sie sich