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 Signaturenmessage: Transaktionsinformationen, einschließlich der Liste der zu verarbeitenden Anweisungen
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagramm, 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 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_feeabzudecken; andernfalls schlägt die Transaktion mitInsufficientFundsForFeefehl.
Nachricht
Das Feld message ist eine
Message
Struktur, die die Nutzlast der Transaktion enthält:
header: Der Nachrichten-Headeraccount_keys: Ein Array von Kontoadressen, die für die Anweisungen der Transaktion erforderlich sindrecent_blockhash: Ein Blockhash, der als Zeitstempel für die Transaktion dientinstructions: Ein Array von Anweisungen
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
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.
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 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:
- Signer + beschreibbar
- Signer + schreibgeschützt
- Nicht-Signer + beschreibbar
- 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 zeigt
Aktueller Blockhash
Das Feld recent_blockhash ist ein 32-Byte-Hash, der zwei Zwecke erfüllt:
- Zeitstempel: beweist, dass die Transaktion kürzlich erstellt wurde.
- 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:
program_id_index: Index inaccount_keys, der das aufzurufende Programm identifiziert.accounts: Array von Indizes inaccount_keys, das die an das Programm zu übergebenden Konten angibt.data: Byte-Array, das den Anweisungsdiskriminator und serialisierte Argumente enthält.
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 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):
| Feld | Größe | Beschreibung |
|---|---|---|
num_signatures | 1-3 Bytes (compact-u16) | Anzahl der Signaturen |
signatures | num_signatures x 64 Bytes | Ed25519-Signaturen |
num_required_signatures | 1 Byte | MessageHeader Feld 1 |
num_readonly_signed | 1 Byte | MessageHeader Feld 2 |
num_readonly_unsigned | 1 Byte | MessageHeader Feld 3 |
num_account_keys | 1-3 Bytes (compact-u16) | Anzahl der statischen Kontoschlüssel |
account_keys | num_account_keys x 32 Bytes | Öffentliche Schlüssel |
recent_blockhash | 32 Bytes | Blockhash |
num_instructions | 1-3 Bytes (compact-u16) | Anzahl der Anweisungen |
instructions | variabel | Array von kompilierten Anweisungen |
Jede kompilierte Anweisung wird wie folgt serialisiert:
| Feld | Größe | Beschreibung |
|---|---|---|
program_id_index | 1 Byte | Index in Kontenschlüssel |
num_accounts | 1-3 Bytes (compact-u16) | Anzahl der Kontenindizes |
account_indices | num_accounts x 1 Byte | Kontenschlüsselindizes |
data_len | 1-3 Bytes (compact-u16) | Länge der instruction data |
data | data_len Bytes | Opake 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-Übertragungsdiagramm
Nachdem die Transaktion gesendet wurde, verarbeitet das System Program die Übertragungsanweisung und aktualisiert das Lamport-Guthaben beider Konten.
SOL-Ü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 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);
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 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));
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.
{"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?