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 finden Sie wichtige Details zur Verarbeitung von Solana-Transaktionen:
- 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 mit Formularen. 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, aus 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 Sender- und Empfängerkonten.
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 Programm-Anweisungen. 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 Anweisungen durchgehen.
Anweisungen
Eine Anweisung an ein Solana Programm kann als öffentliche Funktion betrachtet werden, die von jedem über das Solana-Netzwerk aufgerufen werden kann.
Für den Aufruf einer Programmanweisung werden drei wichtige Informationen benötigt:
- Programm-ID: Das Programm mit der Ausführungslogik für die Anweisung
- Konten: Liste der Konten, die die Anweisung benötigt
- instruction data: Byte-Array, das die aufzurufende Anweisung im Programm und alle von der Anweisung 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>,}
Transaktionsanweisung
AccountMeta
Jedes von einer Anweisung benötigte Konto muss als AccountMeta bereitgestellt werden, das Folgendes enthält:
pubkey
: Adresse des Kontosis_signer
: Ob das Konto die Transaktion signieren mussis_writable
: Ob die Anweisung die Daten des Kontos 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 Konten eine Anweisung liest oder schreibt, können Transaktionen, die nicht dieselben Konten modifizieren, parallel ausgeführt werden.
Beispiel einer Anweisungsstruktur
Führen Sie die folgenden Beispiele aus, um die Struktur einer SOL-Überweisungsanweisung 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 Codeausschnitte. Das genaue Format unterscheidet sich je nach SDK, aber jede Solana-Anweisung erfordert folgende Informationen:
- Programm-ID: Die Adresse des Programms, das die Anweisung ausführen wird.
- Konten: 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.
- Daten: 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.
- Nachricht: 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:
- Nachrichtenkopf: Gibt die Anzahl der Signer und schreibgeschützten Konten an.
- Kontoadressen: Ein Array von Kontoadressen, die von den Anweisungen in der Transaktion benötigt werden.
- Aktueller 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 stammt von 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: 64 Bytes pro Signatur
- Nachricht: Header (3 Bytes), Kontoschlüssel (32 Bytes pro Schlüssel), aktueller Blockhash (32 Bytes) und Anweisungen
Transaktionsformat
Nachrichtenkopf
Der Nachrichtenkopf verwendet drei Bytes, um Kontoberechtigungen zu definieren.
- Erforderliche Signaturen
- Anzahl der schreibgeschützten signierten Konten
- Anzahl der schreibgeschützten nicht signierten 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, eines nach dem anderen aufgelistet
Kompakt-Array-Format
Dieses Format wird verwendet, um die Längen der Arrays Kontenadressen und Anweisungen in Transaktionsnachrichten zu kodieren.
Array von Kontenadressen
Eine Transaktionsnachricht enthält ein Array von Kontenadressen, die von ihren Anweisungen 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 Privilegien geordnet, wie im Nachrichtenkopf festgelegt.
- Konten, die beschreibbar und Signer sind
- Konten, die schreibgeschützt und Signer sind
- Konten, die beschreibbar und keine Signer sind
- Konten, die schreibgeschützt und keine Signer sind
Kompaktes Array von Kontenadressen
Aktueller Blockhash
Jede Transaktion erfordert einen aktuellen 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.
Sie können die getLatestBlockhash
RPC-Methode verwenden, um den aktuellen Blockhash und die letzte Blockhöhe zu
erhalten, bei der der Blockhash gültig sein wird. Hier ist ein Beispiel auf
Solana Playground.
Array von Anweisungen
Eine Transaktionsnachricht enthält ein Array von Anweisungen im Typ CompiledInstruction. Anweisungen werden in diesen Typ umgewandelt, wenn sie einer Transaktion hinzugefügt werden.
Wie das Array der Kontenadressen in der Nachricht beginnt es mit einer compact-u16-Länge, gefolgt von den Anweisungsdaten. Jede Anweisung enthält:
- Program-ID-Index: Ein u8-Index, der auf die Adresse des Programms im Array der Kontenadressen verweist. Dies gibt das Programm an, das die Anweisung verarbeiten wird.
- Konten-Indizes: Ein Array von u8-Indizes, die auf die für diese Anweisung erforderlichen Kontenadressen verweisen.
- Instruction Data: Ein Byte-Array, das angibt, welche Anweisung im Programm aufgerufen werden soll, sowie alle zusätzlichen Daten, die von der Anweisung benötigt werden (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 Anweisungen
Beispiel für eine 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-Ausgabe 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 Signer-Berechtigungen für Adressen imaccountKeys
Array an -
accountKeys
: Array aller Kontenadressen, 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 Signer 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?