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. Man kann sich eine Transaktion wie einen Umschlag mit Formularen vorstellen. 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.

Transaktion vereinfachtTransaktion vereinfacht

Kernpunkte

  • 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:
    1. Die Adresse des aufzurufenden Programms
    2. Die Konten, aus denen die Anweisung liest oder in die sie schreibt
    3. 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 an einen Empfänger zu überweisen.

Bei Solana sind "Wallets" Konten, die dem System Program gehören. Nur der Programmeigentümer kann die Daten eines Kontos ändern, daher erfordert die Überweisung von SOL das Senden einer Transaktion, um das System Program aufzurufen.

SOL-ÜberweisungSOL-Überweisung

Das Senderkonto muss die Transaktion signieren (is_signer), damit das System Program seinen Lamport-Saldo reduzieren kann. Die Sender- und Empfängerkonten müssen schreibbar sein (is_writable), da sich ihre Lamport-Salden ändern.

Nach dem Senden der Transaktion verarbeitet das System Program die Überweisungs- Anweisung. Das System Program aktualisiert dann die Lamport-Salden sowohl des Sender- als auch des Empfängerkontos.

SOL-ÜberweisungsprozessSOL-Überweisungsprozess

Die folgenden Beispiele zeigen, wie man eine Transaktion sendet, die SOL von einem Konto auf ein anderes überweist. Den Quellcode der Überweisungsanweisung des System Programs finden Sie hier.

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.

Client-Bibliotheken abstrahieren oft die Details für die Erstellung 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.

const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});

In den folgenden Abschnitten werden wir die Details von Transaktionen und Anweisungen durchgehen.

Anweisungen

Eine Anweisung auf einem Solana Programm kann als eine öffentliche Funktion betrachtet werden, die von jedem über das Solana-Netzwerk aufgerufen werden kann.

Man kann sich ein Solana-Programm wie einen Webserver vorstellen, der im Solana-Netzwerk gehostet wird, wobei jede Anweisung wie ein öffentlicher API-Endpunkt ist, den Benutzer aufrufen können, um bestimmte Aktionen auszuführen. Das Aufrufen einer Anweisung ähnelt dem Senden einer POST Anfrage an einen API-Endpunkt, wodurch Benutzer die Geschäftslogik des Programms ausführen können.

Um eine Programm-Anweisung auf Solana aufzurufen, müssen Sie eine Instruction mit drei Informationselementen erstellen:

  • Programm-ID: Die Adresse des Programms mit der Geschäftslogik für die aufzurufende Anweisung.
  • Konten: Die Liste aller Konten, aus denen die Anweisung liest oder in die sie schreibt.
  • instruction data: Ein Byte-Array, das angibt, welche Anweisung im Programm aufgerufen werden soll und welche Argumente für die Anweisung erforderlich sind.
Instruction
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>,
}

Transaktions-AnweisungTransaktions-Anweisung

AccountMeta

Bei der Erstellung einer Instruction müssen Sie jedes erforderliche Konto als AccountMeta angeben. Das AccountMeta spezifiziert Folgendes:

  • pubkey: Die Adresse des Kontos
  • is_signer: Ob das Konto die Transaktion signieren muss
  • is_writable: Ob die Anweisung die Daten des Kontos modifiziert
AccountMeta
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,
}

Durch die vorherige Angabe, welche Konten eine Anweisung liest oder beschreibt, können Transaktionen, die nicht dieselben Konten modifizieren, parallel ausgeführt werden.

Um zu wissen, welche Konten eine Anweisung benötigt, einschließlich welche beschreibbar, nur lesbar sein müssen oder die Transaktion signieren müssen, müssen Sie die Implementierung der Anweisung, wie sie vom Programm definiert wurde, konsultieren.

In der Praxis müssen Sie normalerweise keine Instruction manuell erstellen. Die meisten Programmentwickler stellen Client-Bibliotheken mit Hilfsfunktionen bereit, die die Anweisungen für Sie erstellen.

AccountMetaAccountMeta

Beispiel für eine 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 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
});
console.log(JSON.stringify(transferInstruction, null, 2));
Console
Click to execute the code.

Die folgenden Beispiele zeigen die Ausgabe der vorherigen Codeausschnitte. Das genaue Format unterscheidet sich je nach SDK, aber jede Solana-Anweisung erfordert die folgenden 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

Nachdem Sie die Anweisungen erstellt haben, die Sie aufrufen möchten, besteht der nächste Schritt darin, eine Transaction zu erstellen und die Anweisungen zur Transaktion hinzuzufügen. Eine Solana Transaktion besteht aus:

  1. Signaturen: Ein Array von Signaturen von allen Konten, die als Signer für die Anweisungen in der Transaktion erforderlich sind. Eine Signatur wird erstellt, indem die Transaktion Message mit dem privaten Schlüssel des Kontos signiert wird.
  2. Nachricht: Die Transaktions Nachricht enthält die Liste der Anweisungen, die atomar verarbeitet werden sollen.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

TransaktionsformatTransaktionsformat

Die Struktur einer Transaktionsnachricht besteht aus:

  • Nachrichtenheader: 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.
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>,
}

Transaktionsgröße

Solana-Transaktionen haben eine Größenbeschränkung von 1232 Bytes. Diese Begrenzung 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 Header).

Die Gesamtgröße einer Transaktion (Signaturen und Nachricht) muss unter dieser Grenze bleiben und umfasst:

  • Signaturen: jeweils 64 Bytes
  • Nachricht: Header (3 Bytes), Kontoschlüssel (jeweils 32 Bytes), aktueller Blockhash (32 Bytes) und Anweisungen

TransaktionsformatTransaktionsformat

Nachrichtenheader

Der Nachrichtenheader gibt die Berechtigungen für das Konto in der Transaktion an. Er funktioniert in Kombination mit den streng geordneten Kontoadressen, um zu bestimmen, welche Konten Signer sind und welche beschreibbar sind.

  1. Die Anzahl der Signaturen, die für alle Anweisungen in der Transaktion erforderlich sind.
  2. Die Anzahl der signierten Konten, die schreibgeschützt sind.
  3. Die Anzahl der unsignierten 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,
}

NachrichtenheaderNachrichtenheader

Kompakt-Array-Format

Ein kompaktes Array in einer Transaktionsnachricht ist ein Array, das im folgenden Format serialisiert wird:

  1. Die Array-Länge (kodiert als compact-u16)
  2. Die Array-Elemente, eines nach dem anderen aufgelistet

Kompaktes Array-FormatKompaktes Array-Format

Dieses Format wird verwendet, um die Längen der Kontoadressen und Anweisungen Arrays in Transaktionsnachrichten zu kodieren.

Array von Kontenadressen

Eine Transaktionsnachricht enthält eine einzelne Liste aller Kontenadressen, die von ihren Anweisungen benötigt werden. Das Array beginnt mit einer compact-u16 Zahl, die angibt, wie viele Adressen es enthält.

Um Speicherplatz zu sparen, speichert die Transaktion nicht für jedes Konto einzeln Berechtigungen. Stattdessen verlässt sie sich auf eine Kombination aus dem MessageHeader und einer strikten Reihenfolge der Kontenadressen, um Berechtigungen zu bestimmen.

Die Adressen sind immer in folgender Weise angeordnet:

  1. Konten, die beschreibbar und Signer sind
  2. Konten, die schreibgeschützt und Signer sind
  3. Konten, die beschreibbar und keine Signer sind
  4. Konten, die schreibgeschützt und keine Signer sind

Der MessageHeader liefert die Werte, die verwendet werden, um die Anzahl der Konten für jede Berechtigungsgruppe zu bestimmen.

Kompaktes Array von KontenadressenKompaktes Array von Kontenadressen

Aktueller Blockhash

Jede Transaktion erfordert einen aktuellen Blockhash, der zwei Zwecken dient:

  1. Fungiert als Zeitstempel für den Zeitpunkt der Erstellung der Transaktion
  2. Verhindert doppelte Transaktionen

Ein Blockhash läuft nach 150 Blöcken ab (etwa 1 Minute bei angenommenen 400ms Blockzeiten), danach gilt die Transaktion als abgelaufen und kann 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 gültig sein wird.

Array von Anweisungen

Eine Transaktionsnachricht enthält ein Array von Anweisungen im CompiledInstruction Typ. Anweisungen werden in diesen Typ umgewandelt, wenn sie einer Transaktion hinzugefügt werden.

Wie das Kontenadressen-Array in der Nachricht beginnt es mit einer compact-u16 Länge, gefolgt von den Anweisungsdaten. Jede Anweisung enthält:

  1. Program-ID-Index: Ein Index, der auf die Adresse des Programms im Array der Kontoadressen verweist. Dies gibt das Programm an, das die Anweisung verarbeitet.
  2. Kontoindizes: Ein Array von Indizes, die auf die für diese Anweisung erforderlichen Kontoadressen verweisen.
  3. 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).
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

Beispiel einer Transaktionsstruktur

Führen Sie die folgenden Beispiele aus, um die Struktur einer Transaktion mit einer einzelnen 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 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.

Die folgenden Beispiele zeigen die Transaktionsnachrichtenausgabe aus den vorherigen Code-Snippets. Das genaue Format unterscheidet sich je nach SDK, enthält aber die gleichen 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
}
}
]
}

Nach dem Absenden einer Transaktion können Sie ihre Details mit der RPC-Methode getTransaction abrufen. Die Antwort wird eine Struktur ähnlich dem folgenden Snippet haben. Alternativ können Sie die Transaktion mit dem Solana Explorer untersuchen.

Eine "Transaktionssignatur" identifiziert eine Transaktion auf Solana eindeutig. Sie verwenden diese Signatur, um die Details der Transaktion im Netzwerk nachzuschlagen. Die Transaktionssignatur ist einfach die erste Signatur auf der Transaktion. Beachten Sie, dass die erste Signatur auch die Signatur des Zahlers der Transaktions-Fee ist.

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