Transacties en instructies
Op Solana sturen gebruikers transacties om te interacteren met het netwerk. Transacties bevatten een of meer instructies die te verwerken operaties specificeren. De uitvoeringslogica voor instructies is opgeslagen in programs die op het Solana-netwerk zijn geïmplementeerd, waarbij elk program zijn eigen set instructies definieert.
Hieronder staan belangrijke details over Solana transactieverwerking:
- Als een transactie meerdere instructies bevat, worden de instructies uitgevoerd in de volgorde waarin ze aan de transactie zijn toegevoegd.
- Transacties zijn "atomair" - alle instructies moeten succesvol worden verwerkt, anders mislukt de hele transactie en vinden er geen wijzigingen plaats.
Een transactie is in essentie een verzoek om een of meer instructies te verwerken.
Transactie vereenvoudigd
Een transactie is als een envelop met formulieren. Elk formulier is een instructie die het netwerk vertelt wat te doen. Het verzenden van de transactie is als het versturen van de envelop om de formulieren te laten verwerken.
Belangrijke punten
- Solana transacties bevatten instructies die programs op het netwerk aanroepen.
- Transacties zijn atomair - als een instructie mislukt, mislukt de hele transactie en vinden er geen wijzigingen plaats.
- Instructies in een transactie worden in sequentiële volgorde uitgevoerd.
- De limiet voor transactiegrootte is 1232 bytes.
- Elke instructie vereist drie stukken informatie:
- Het adres van het program dat moet worden aangeroepen
- De accounts waaruit de instructie leest of waarin deze schrijft
- Eventuele extra gegevens die de instructie nodig heeft (bijv. functieargumenten)
SOL-overdrachtsvoorbeeld
Het onderstaande diagram toont een transactie met een enkele instructie om SOL over te maken van een verzender naar een ontvanger.
Op Solana zijn "wallets" accounts die eigendom zijn van het System Program. Alleen de programma-eigenaar kan de gegevens van een account wijzigen, dus het overmaken van SOL vereist het verzenden van een transactie om het System Program aan te roepen.
SOL Overdracht
Het verzendende account moet de transactie ondertekenen (is_signer
) om het
System Program toestemming te geven om het lamport-saldo te verminderen. De
verzender en ontvanger accounts moeten beschrijfbaar zijn (is_writable
) omdat
hun lamport-saldi veranderen.
Na het verzenden van de transactie verwerkt het System Program de overdracht instructie. Het System Program werkt vervolgens de lamport-saldi bij van zowel de verzender als de ontvanger accounts.
SOL Overdracht Proces
De onderstaande voorbeelden laten zien hoe je een transactie verzendt die SOL overmaakt van het ene account naar het andere.
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 abstraheren vaak de details voor het bouwen van programma-instructies. Als er geen bibliotheek beschikbaar is, kun je de instructie handmatig opbouwen. Dit vereist dat je de implementatiedetails van de instructie kent.
De onderstaande voorbeelden tonen hoe je handmatig de overdracht-instructie kunt
opbouwen. De Expanded Instruction
tab is functioneel gelijk aan de
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 de onderstaande secties bespreken we de details van transacties en instructies.
Instructions
Een instruction op een Solana program kan worden gezien als een publieke functie die door iedereen op het Solana-netwerk kan worden aangeroepen.
Het aanroepen van een instruction van een program vereist drie belangrijke stukken informatie:
- Program ID: Het program met de uitvoeringslogica voor de instruction
- Accounts: Lijst met accounts die de instruction nodig heeft
- Instruction Data: Byte-array die de aan te roepen instruction op het program specificeert en eventuele argumenten die de instruction vereist
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
Elk account dat door een instruction vereist wordt, moet worden aangeleverd als een AccountMeta die het volgende bevat:
pubkey
: Het adres van het accountis_signer
: Of het account de transactie moet ondertekenenis_writable
: Of de instruction de gegevens van het account wijzigt
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
Door vooraf te specificeren welke accounts een instruction leest of schrijft, kunnen transacties die niet dezelfde accounts wijzigen parallel worden uitgevoerd.
Voorbeeld van een instruction-structuur
Voer de onderstaande voorbeelden uit om de structuur van een SOL-overdrachtsinstructie te zien.
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));
De volgende voorbeelden tonen de output van de voorgaande codefragmenten. Het exacte formaat verschilt afhankelijk van de SDK, maar elke Solana instruction vereist de volgende informatie:
- Program ID: Het adres van het programma dat de instructie zal uitvoeren.
- Accounts: Een lijst van accounts die vereist zijn voor de instructie. Voor elk account moet de instructie het adres specificeren, of het de transactie moet ondertekenen, en of er naar geschreven zal worden.
- Data: Een byte buffer die het programma vertelt welke instructie uitgevoerd moet worden en eventuele argumenten bevat die nodig zijn voor de instructie.
{"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}}
Transacties
Een Solana transactie bestaat uit:
- Handtekeningen: Een array van handtekeningen die bij de transactie zijn inbegrepen.
- Bericht: Lijst van instructies die atomisch verwerkt moeten worden.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Transactie Formaat
De structuur van een transactiebericht bestaat uit:
- Berichtheader: Specificeert het aantal ondertekenaars en alleen-lezen accounts.
- Account Adressen: Een array van accountadressen die vereist zijn voor de instructies in de transactie.
- Recente Blockhash: Fungeert als een tijdstempel voor de transactie.
- Instructies: Een array van instructies die uitgevoerd moeten worden.
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>,}
Transactie Bericht
Transactiegrootte
Solana transacties hebben een groottelimiet van 1232 bytes. Deze limiet komt voort uit de IPv6 Maximum Transmission Unit (MTU) grootte van 1280 bytes, minus 48 bytes voor netwerkheaders (40 bytes IPv6 + 8 bytes fragment header).
De totale grootte van een transactie (handtekeningen en bericht) moet onder deze limiet blijven en omvat:
- Handtekeningen: 64 bytes elk
- Bericht: Header (3 bytes), accountsleutels (32 bytes elk), recente blockhash (32 bytes), en instructies
Transactie Formaat
Berichtheader
De berichtheader gebruikt drie bytes om accountprivileges te definiëren.
- Vereiste handtekeningen
- Aantal alleen-lezen ondertekende accounts
- Aantal alleen-lezen niet-ondertekende accounts
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,}
Berichtkop
Compact-Array formaat
Een compact array in een transactiebericht is een array geserialiseerd in het volgende formaat:
- De array lengte (gecodeerd als compact-u16)
- De array-items op een rij
Compact array formaat
Dit formaat wordt gebruikt om de lengtes te coderen van de Accountadressen en Instructies arrays in transactieberichten.
Array van accountadressen
Een transactiebericht bevat een array van accountadressen die vereist zijn door de instructies. De array begint met een compact-u16 getal dat aangeeft hoeveel adressen het bevat. De adressen worden vervolgens geordend op hun privileges, zoals bepaald door de berichtkop.
- Accounts die schrijfbaar zijn en ondertekenaars
- Accounts die alleen-lezen zijn en ondertekenaars
- Accounts die schrijfbaar zijn en geen ondertekenaars
- Accounts die alleen-lezen zijn en geen ondertekenaars
Compact array van accountadressen
Recente blockhash
Elke transactie vereist een recente blockhash die twee doelen dient:
- Fungeert als tijdstempel
- Voorkomt dubbele transacties
Een blockhash verloopt na 150 blokken (ongeveer 1 minuut uitgaande van 400ms bloktijden), waarna de transactie niet meer kan worden verwerkt.
Je kunt de getLatestBlockhash
RPC methode
gebruiken om de huidige blockhash en laatste blokhoogte te krijgen waarop de
blockhash geldig zal zijn. Hier is een voorbeeld op
Solana Playground.
Array van instructies
Een transactiebericht bevat een array van instructies in het CompiledInstruction type. Instructies worden naar dit type geconverteerd wanneer ze aan een transactie worden toegevoegd.
Net als de accountadressen array in het bericht, begint het met een compact-u16 lengte gevolgd door de instruction data. Elke instructie bevat:
- Program ID Index: Een u8-index die verwijst naar het adres van het programma in de array met accountadressen. Dit specificeert het programma dat de instructie zal verwerken.
- Account Indexes: Een array van u8-indexen die verwijzen naar de accountadressen die vereist zijn voor deze instructie.
- Instruction Data: Een byte-array die specificeert welke instructie moet worden aangeroepen op het programma en eventuele aanvullende gegevens die nodig zijn voor de instructie (bijv. functie argumenten).
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>,}
Compacte array van Instructions
Voorbeeld transactiestructuur
Voer de onderstaande voorbeelden uit om de structuur van een transactie met een enkele SOL overboeking instructie te zien.
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));
De volgende voorbeelden tonen de transactiebericht-output van de vorige codesnippets. Het exacte formaat verschilt afhankelijk van de SDK, maar bevat dezelfde informatie.
{"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}}]}
Wanneer je een transactie ophaalt met behulp van de handtekening nadat je deze naar het netwerk hebt verzonden, ontvang je een antwoord met de volgende structuur.
Het message
veld bevat de volgende velden:
-
header
: Specificeert lees/schrijf- en ondertekenaarsprivileges voor adressen in deaccountKeys
array -
accountKeys
: Array van alle accountadressen die in de instructies van de transactie worden gebruikt -
recentBlockhash
: Blockhash gebruikt om de transactie van een tijdstempel te voorzien -
instructions
: Array van uit te voeren instructies. Elkeaccount
enprogramIdIndex
in een instructie verwijst naar deaccountKeys
array via index. -
signatures
: Array met handtekeningen voor alle accounts die als ondertekenaars vereist zijn door de instructies in de transactie. Een handtekening wordt gemaakt door het transactiebericht te ondertekenen met de bijbehorende privésleutel voor een account.
{"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?