Transaktionen und Anweisungen
Auf Solana senden Benutzer Transaktionen, um mit dem Netzwerk zu interagieren. Transaktionen enthalten eine oder mehrere Anweisungen, die zu verarbeitende Operationen spezifizieren. Die Ausführungslogik für Anweisungen ist in Programmen gespeichert, die im Solana-Netzwerk bereitgestellt werden, wobei jedes Programm seinen eigenen Satz von Anweisungen definiert.
Nachfolgend sind wichtige Details zur Verarbeitung von Solana-Transaktionen aufgeführt:
- Wenn eine Transaktion mehrere Anweisungen enthält, werden diese in der Reihenfolge ausgeführt, in der sie zur Transaktion hinzugefügt wurden.
- Transaktionen sind "atomar" - alle Anweisungen müssen erfolgreich verarbeitet werden, oder die gesamte Transaktion schlägt fehl und es werden keine Änderungen vorgenommen.
Eine Transaktion ist im Wesentlichen eine Anfrage zur Verarbeitung einer oder mehrerer Anweisungen.
Transaktion vereinfacht
Eine Transaktion ist wie ein Umschlag, der Formulare enthält. Jedes Formular ist eine Anweisung, die dem Netzwerk mitteilt, was zu tun ist. Das Senden der Transaktion ist wie das Versenden des Umschlags, um die Formulare bearbeiten zu lassen.
Wichtige Punkte
- Solana-Transaktionen enthalten Anweisungen, die Programme im Netzwerk aufrufen.
- Transaktionen sind atomar - wenn eine Anweisung fehlschlägt, schlägt die gesamte Transaktion fehl und es werden keine Änderungen vorgenommen.
- Anweisungen in einer Transaktion werden in sequentieller Reihenfolge ausgeführt.
- Die Größenbeschränkung für Transaktionen beträgt 1232 Bytes.
- Jede Anweisung erfordert drei Informationen:
- Die Adresse des aufzurufenden Programms
- Die Konten, von denen die Anweisung liest oder in die sie schreibt
- Alle zusätzlichen Daten, die von der Anweisung benötigt werden (z.B. Funktionsargumente)
SOL-Überweisungsbeispiel
Das folgende Diagramm stellt eine Transaktion mit einer einzelnen Anweisung dar, um SOL von einem Sender zu einem Empfänger zu übertragen.
Auf Solana sind "Wallets" Konten, die dem System Program gehören. Nur der Programmeigentümer kann die Daten eines Kontos ändern, daher erfordert die Übertragung von SOL das Senden einer Transaktion zur Aufrufung des System Programs.
SOL Transfer
Das Senderkonto muss die Transaktion signieren (is_signer
), damit das System
Program seinen Lamport-Saldo reduzieren kann. Die Sender- und Empfängerkonten
müssen beschreibbar sein (is_writable
), da sich ihre Lamport-Salden ändern.
Nach dem Senden der Transaktion verarbeitet das System Program die Transfer- Anweisung. Das System Program aktualisiert dann die Lamport-Salden beider Konten, des Senders und des Empfängers.
SOL Transfer Process
Die folgenden Beispiele zeigen, wie man eine Transaktion sendet, die SOL von einem Konto zu einem anderen überträgt.
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);
Client-Bibliotheken abstrahieren oft die Details für das Erstellen von Programmanweisungen. Wenn keine Bibliothek verfügbar ist, können Sie die Anweisung manuell erstellen. Dies erfordert, dass Sie die Implementierungsdetails der Anweisung kennen.
Die folgenden Beispiele zeigen, wie man die Transfer-Anweisung manuell erstellt.
Der Expanded Instruction
Tab ist funktional äquivalent zum Instruction
Tab.
- Kit
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
- Legacy
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = SystemProgram.transfer({fromPubkey: sender.publicKey,toPubkey: receiver.publicKey,lamports: transferAmount * LAMPORTS_PER_SOL});
- Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOLlet transfer_instruction =system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);
In den folgenden Abschnitten werden wir die Details von Transaktionen und Instructions erläutern.
Instructions
Eine Instruction auf einem Solana Program kann als öffentliche Funktion betrachtet werden, die von jedem über das Solana-Netzwerk aufgerufen werden kann.
Zum Aufrufen einer Instruction eines Programs werden drei wesentliche Informationen benötigt:
- Program ID: Das Program mit der Ausführungslogik für die Instruction
- Accounts: Liste der Accounts, die die Instruction benötigt
- Instruction Data: Byte-Array, das die auf dem Program auszuführende Instruction und alle von der Instruction benötigten Argumente spezifiziert
pub struct Instruction {/// Pubkey of the program that executes this instruction.pub program_id: Pubkey,/// Metadata describing accounts that should be passed to the program.pub accounts: Vec<AccountMeta>,/// Opaque data passed to the program for its own interpretation.pub data: Vec<u8>,}
Transaction Instruction
AccountMeta
Jeder von einer Instruction benötigte Account muss als AccountMeta bereitgestellt werden, das Folgendes enthält:
pubkey
: Adresse des Accountsis_signer
: Ob der Account die Transaktion signieren mussis_writable
: Ob die Instruction die Daten des Accounts modifiziert
pub struct AccountMeta {/// An account's public key.pub pubkey: Pubkey,/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.pub is_signer: bool,/// True if the account data or metadata may be mutated during program execution.pub is_writable: bool,}
AccountMeta
Durch die vorherige Angabe, welche Accounts eine Instruction liest oder schreibt, können Transaktionen, die nicht dieselben Accounts modifizieren, parallel ausgeführt werden.
Beispiel einer Instruction-Struktur
Führen Sie die folgenden Beispiele aus, um die Struktur einer SOL-Überweisungs-Instruction zu sehen.
import { generateKeyPairSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// 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});console.log(JSON.stringify(transferInstruction, null, 2));
Die folgenden Beispiele zeigen die Ausgabe der vorherigen Code-Snippets. Das genaue Format unterscheidet sich je nach SDK, aber jede Solana-Instruction erfordert die folgenden Informationen:
- Program ID: Die Adresse des Programms, das die Anweisung ausführen wird.
- Accounts: Eine Liste der von der Anweisung benötigten Konten. Für jedes Konto muss die Anweisung seine Adresse angeben, ob es die Transaktion signieren muss und ob es beschrieben wird.
- Data: Ein Byte-Puffer, der dem Programm mitteilt, welche Anweisung ausgeführt werden soll, und alle von der Anweisung benötigten Argumente enthält.
{"accounts": [{"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","role": 3,"signer": {"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","keyPair": {"privateKey": {},"publicKey": {}}}},{"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p","role": 1}],"programAddress": "11111111111111111111111111111111","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}}
Transaktionen
Eine Solana Transaktion besteht aus:
- Signaturen: Ein Array von Signaturen, die in der Transaktion enthalten sind.
- Message: Liste von Anweisungen, die atomar verarbeitet werden sollen.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Transaktionsformat
Die Struktur einer Transaktionsnachricht besteht aus:
- Message Header: Gibt die Anzahl der Signatur- und schreibgeschützten Konten an.
- Account-Adressen: Ein Array von Kontoadressen, die von den Anweisungen in der Transaktion benötigt werden.
- Recent Blockhash: Fungiert als Zeitstempel für die Transaktion.
- Anweisungen: Ein Array von auszuführenden 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>,}
Transaktionsnachricht
Transaktionsgröße
Solana-Transaktionen haben ein Größenlimit von 1232 Bytes. Dieses Limit ergibt sich aus der IPv6 Maximum Transmission Unit (MTU) Größe von 1280 Bytes, abzüglich 48 Bytes für Netzwerk-Header (40 Bytes IPv6 + 8 Bytes Fragment- Header).
Die Gesamtgröße einer Transaktion (Signaturen und Nachricht) muss unter diesem Limit bleiben und umfasst:
- Signaturen: jeweils 64 Bytes
- Nachricht: Header (3 Bytes), Account-Schlüssel (jeweils 32 Bytes), aktueller Blockhash (32 Bytes) und Anweisungen
Transaktionsformat
Message Header
Der Message Header verwendet drei Bytes, um Kontoberechtigungen zu definieren.
- Erforderliche Signaturen
- Anzahl der schreibgeschützten signierten Konten
- Anzahl der schreibgeschützten unsignierten Konten
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,}
Nachrichtenkopf
Kompakt-Array-Format
Ein kompaktes Array in einer Transaktionsnachricht ist ein Array, das im folgenden Format serialisiert wird:
- Die Array-Länge (kodiert als compact-u16)
- Die Array-Elemente nacheinander aufgelistet
Kompakt-Array-Format
Dieses Format wird verwendet, um die Längen der Arrays für Kontoadressen und Instructions in Transaktionsnachrichten zu kodieren.
Array von Kontoadressen
Eine Transaktionsnachricht enthält ein Array von Kontoadressen, die von ihren Instructions benötigt werden. Das Array beginnt mit einer compact-u16-Zahl, die angibt, wie viele Adressen es enthält. Die Adressen werden dann nach ihren Berechtigungen geordnet, wie im Nachrichtenkopf festgelegt.
- Konten, die beschreibbar sind und Signierer sind
- Konten, die schreibgeschützt sind und Signierer sind
- Konten, die beschreibbar sind und keine Signierer sind
- Konten, die schreibgeschützt sind und keine Signierer sind
Kompaktes Array von Kontoadressen
Recent Blockhash
Jede Transaktion erfordert einen recent blockhash, der zwei Zwecken dient:
- Fungiert als Zeitstempel
- Verhindert doppelte Transaktionen
Ein Blockhash läuft nach 150 Blöcken ab (etwa 1 Minute bei angenommenen 400ms Blockzeiten), danach kann die Transaktion nicht mehr verarbeitet werden.
Du kannst die getLatestBlockhash
RPC-Methode verwenden, um den aktuellen Blockhash und die letzte Blockhöhe zu
erhalten, bei der der Blockhash noch gültig sein wird. Hier ist ein Beispiel auf
Solana Playground.
Array von Instructions
Eine Transaktionsnachricht enthält ein Array von instructions im Typ CompiledInstruction. Instructions werden in diesen Typ umgewandelt, wenn sie einer Transaktion hinzugefügt werden.
Wie das Kontoadressen-Array in der Nachricht beginnt es mit einer compact-u16-Länge, gefolgt von den instruction data. Jede instruction enthält:
- Program-ID-Index: Ein u8-Index, der auf die Adresse des Programms im Array der Kontoadressen verweist. Dies gibt das Programm an, das die Anweisung verarbeiten wird.
- Konto-Indizes: Ein Array von u8-Indizes, die auf die für diese Anweisung erforderlichen Kontoadressen verweisen.
- Instruction Data: Ein Byte-Array, das angibt, welche Anweisung im Programm aufgerufen werden soll, sowie alle zusätzlichen Daten, die für die Anweisung erforderlich sind (z.B. Funktionsargumente).
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 Instructions
Beispiel einer Transaktionsstruktur
Führen Sie die folgenden Beispiele aus, um die Struktur einer Transaktion mit einer einzigen SOL-Überweisungsanweisung zu sehen.
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));
Die folgenden Beispiele zeigen die Transaktionsnachricht aus den vorherigen Code-Snippets. Das genaue Format unterscheidet sich je nach SDK, enthält aber dieselben Informationen.
{"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}}]}
Wenn Sie eine Transaktion anhand ihrer Signatur abrufen, nachdem Sie sie an das Netzwerk gesendet haben, erhalten Sie eine Antwort mit der folgenden Struktur.
Das Feld message
enthält die folgenden Felder:
-
header
: Gibt Lese-/Schreib- und Signaturberechtigungen für Adressen imaccountKeys
Array an -
accountKeys
: Array aller Kontoadressen, die in den Anweisungen der Transaktion verwendet werden -
recentBlockhash
: Blockhash, der zur Zeitstempelung der Transaktion verwendet wird -
instructions
: Array von auszuführenden Anweisungen. Jedeaccount
undprogramIdIndex
in einer Anweisung verweist per Index auf dasaccountKeys
Array. -
signatures
: Array mit Signaturen für alle Konten, die von den Anweisungen der Transaktion als Unterzeichner benötigt werden. Eine Signatur wird erstellt, indem die Transaktionsnachricht mit dem entsprechenden privaten Schlüssel für ein Konto signiert wird.
{"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?