Transacties en instructies
Op Solana sturen gebruikers transacties om te interacteren met het netwerk. Transacties bevatten een of meer instructies die te verwerken bewerkingen specificeren. De uitvoeringslogica voor instructies is opgeslagen op programma's die op het Solana-netwerk zijn geïmplementeerd, waarbij elk programma 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 wezen een verzoek om een of meer instructies te verwerken. Je kunt een transactie zien 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.
Transactie vereenvoudigd
Belangrijke punten
- Solana transacties bevatten instructies die programma's 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 programma dat moet worden aangeroepen
- De accounts waaruit de instructie leest of waarnaartoe schrijft
- Eventuele extra gegevens die nodig zijn voor de instructie (bijv. functieargumenten)
SOL Transfer voorbeeld
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 voor het overmaken van SOL moet een transactie worden verzonden om het System Program aan te roepen.
SOL Transfer
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 transfer instructie. Het System Program werkt vervolgens de lamport-saldi bij van zowel het verzendende als het ontvangende account.
SOL Transfer proces
De onderstaande voorbeelden laten zien hoe je een transactie verzendt die SOL overmaakt van het ene account naar het andere. Bekijk de broncode van de transfer-instructie van het System Program 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 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 transfer-instructie kunt
opbouwen. De Expanded Instruction
tab is functioneel gelijk aan de
Instruction
tab.
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
In de onderstaande secties behandelen we de details van transacties en instructies.
Instructies
Een instruction op Solana program kan worden gezien als een publieke functie die door iedereen via het Solana-netwerk kan worden aangeroepen.
Je kunt een Solana-programma zien als een webserver die op het Solana-netwerk
wordt gehost, waarbij elke instructie vergelijkbaar is met een openbaar
API-eindpunt dat gebruikers kunnen aanroepen om specifieke acties uit te voeren.
Het aanroepen van een instructie is vergelijkbaar met het verzenden van een
POST
verzoek naar een API-eindpunt, waardoor gebruikers de bedrijfslogica van
het programma kunnen uitvoeren.
Om een programma-instructie op Solana aan te roepen, moet je een Instruction
samenstellen met drie stukken informatie:
- Program ID: Het adres van het programma met de bedrijfslogica voor de instructie die wordt aangeroepen.
- Accounts: De lijst van alle accounts waaruit de instructie leest of waarin deze schrijft.
- Instruction Data: Een byte-array dat specificeert welke instructie op het programma moet worden aangeroepen en eventuele argumenten die de instructie 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
Bij het maken van een Instruction
moet je elk vereist account opgeven als een
AccountMeta
.
De AccountMeta
specificeert het volgende:
- pubkey: Het adres van het account
- is_signer: Of het account de transactie moet ondertekenen
- is_writable: Of de instructie 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,}
Door vooraf te specificeren welke accounts een instructie leest of schrijft, kunnen transacties die niet dezelfde accounts wijzigen parallel worden uitgevoerd.
Om te weten welke accounts een instructie vereist, inclusief welke beschrijfbaar, alleen-lezen of de transactie moeten ondertekenen, moet je de implementatie van de instructie raadplegen zoals gedefinieerd door het programma.
In de praktijk hoef je meestal niet handmatig een Instruction
te maken. De
meeste programma-ontwikkelaars bieden client-bibliotheken met hulpfuncties die
de instructies voor je aanmaken.
AccountMeta
Voorbeeld instructiestructuur
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 uitvoer van de vorige codefragmenten. Het exacte formaat verschilt afhankelijk van de SDK, maar elke Solana-instructie vereist de volgende informatie:
- Program ID: Het adres van het programma dat de instructie zal uitvoeren.
- Accounts: Een lijst met 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 bytebuffer die het programma vertelt welke instructie uitgevoerd moet worden en eventuele argumenten bevat die de instructie nodig heeft.
{"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
Nadat je de instructies hebt gemaakt die je wilt aanroepen, is de volgende stap
het creëren van een Transaction
en het toevoegen van de instructies aan de
transactie. Een Solana
transactie
bestaat uit:
- Handtekeningen: Een array van
handtekeningen
van alle accounts die vereist zijn als ondertekenaars voor de instructies in
de transactie. Een handtekening wordt gemaakt door de transactie
Message
te ondertekenen met de privésleutel van het account. - Bericht: Het transactie bericht bevat de lijst met instructies die atomisch moeten worden verwerkt.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Transactieformaat
De structuur van een transactiebericht bestaat uit:
- Berichtkop: Specificeert het aantal ondertekenaars en alleen-lezen accounts.
- Accountadressen: 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 moeten worden uitgevoerd.
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>,}
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 header).
De totale grootte van een transactie (handtekeningen en bericht) moet onder deze limiet blijven en omvat:
- Handtekeningen: 64 bytes elk
- Bericht: Kop (3 bytes), accountsleutels (32 bytes elk), recente blockhash (32 bytes), en instructies
Transactieformaat
Berichtkop
De berichtkop specificeert de rechten voor het account in de transactie. Het werkt in combinatie met de strikt geordende accountadressen om te bepalen welke accounts ondertekenaars zijn en welke beschrijfbaar zijn.
- Het aantal handtekeningen vereist voor alle instructies in de transactie.
- Het aantal ondertekende accounts dat alleen-lezen is.
- Het aantal niet-ondertekende accounts dat alleen-lezen is.
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 die geserialiseerd is in het volgende formaat:
- De arraylengte (gecodeerd als compact-u16)
- De array-items op volgorde achter elkaar geplaatst
Compact array formaat
Dit formaat wordt gebruikt om de lengtes van de Accountadressen en Instructies arrays in transactieberichten te coderen.
Array van accountadressen
Een transactiebericht bevat een enkele lijst van alle accountadressen die vereist zijn voor de instructies. De array begint met een compact-u16 getal dat aangeeft hoeveel adressen deze bevat.
Om ruimte te besparen, slaat de transactie niet voor elk account afzonderlijk
permissies op. In plaats daarvan vertrouwt het op een combinatie van de
MessageHeader
en een strikte ordening van de accountadressen om permissies te
bepalen.
De adressen worden altijd in de volgende volgorde gerangschikt:
- Accounts die schrijfbaar en ondertekenaars zijn
- Accounts die alleen-lezen en ondertekenaars zijn
- Accounts die schrijfbaar en geen ondertekenaars zijn
- Accounts die alleen-lezen en geen ondertekenaars zijn
De MessageHeader
levert de waarden die worden gebruikt om het aantal accounts
voor elke permissiegroep te bepalen.
Compact array van accountadressen
Recente blockhash
Elke transactie vereist een recente blockhash die twee doelen dient:
- Fungeert als een tijdstempel voor wanneer de transactie is aangemaakt
- Voorkomt dubbele transacties
Een blockhash verloopt na 150 blokken (ongeveer 1 minuut uitgaande van 400ms bloktijden), waarna de transactie als verlopen wordt beschouwd en 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.
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 array met accountadressen in het bericht, begint het met een compact-u16 lengte gevolgd door de instructiegegevens. Elke instructie bevat:
- Program ID Index: Een index die verwijst naar het programma-adres in de array met accountadressen. Dit specificeert het programma dat de instructie verwerkt.
- Account Indexes: Een array van 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. functieargumenten).
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 instructies
Voorbeeld transactiestructuur
Voer de onderstaande voorbeelden uit om de structuur van een transactie met een enkele SOL overdrachtsinstuctie 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 uitvoer van het transactiebericht uit 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}}]}
Na het indienen van een transactie kun je de details ervan ophalen met de getTransaction RPC-methode. De respons zal een structuur hebben die vergelijkbaar is met het volgende fragment. Als alternatief kun je de transactie inspecteren met Solana Explorer.
Een "transactiehandtekening" identificeert een transactie op Solana uniek. Je gebruikt deze handtekening om de details van de transactie op het netwerk op te zoeken. De transactiehandtekening is simpelweg de eerste handtekening op de transactie. Merk op dat de eerste handtekening ook de handtekening is van de betaler van de transactiekosten.
{"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?