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 vereenvoudigdTransactie 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:
    1. Het adres van het program dat moet worden aangeroepen
    2. De accounts waaruit de instructie leest of waarin deze schrijft
    3. 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 OverdrachtSOL 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 ProcesSOL 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 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);
Click to execute the code.

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 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});
  • Legacy
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = SystemProgram.transfer({
fromPubkey: sender.publicKey,
toPubkey: receiver.publicKey,
lamports: transferAmount * LAMPORTS_PER_SOL
});
  • Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOL
let 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
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>,
}

Transaction InstructionTransaction 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 account
  • is_signer: Of het account de transactie moet ondertekenen
  • is_writable: Of de instruction de gegevens van het account wijzigt
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,
}

AccountMetaAccountMeta

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 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));
Click to execute the code.

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:

  1. Handtekeningen: Een array van handtekeningen die bij de transactie zijn inbegrepen.
  2. Bericht: Lijst van instructies die atomisch verwerkt moeten worden.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Transactie FormaatTransactie 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.
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>,
}

Transactie BerichtTransactie 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 FormaatTransactie Formaat

Berichtheader

De berichtheader gebruikt drie bytes om accountprivileges te definiëren.

  1. Vereiste handtekeningen
  2. Aantal alleen-lezen ondertekende accounts
  3. Aantal alleen-lezen niet-ondertekende accounts
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,
}

BerichtkopBerichtkop

Compact-Array formaat

Een compact array in een transactiebericht is een array geserialiseerd in het volgende formaat:

  1. De array lengte (gecodeerd als compact-u16)
  2. De array-items op een rij

Compact array formaatCompact 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 accountadressenCompact array van accountadressen

Recente blockhash

Elke transactie vereist een recente blockhash die twee doelen dient:

  1. Fungeert als tijdstempel
  2. 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:

  1. 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.
  2. Account Indexes: Een array van u8-indexen die verwijzen naar de accountadressen die vereist zijn voor deze instructie.
  3. 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).
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>,
}

Compacte array van InstructionsCompacte 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 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));
Click to execute the code.

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 de accountKeys 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. Elke account en programIdIndex in een instructie verwijst naar de accountKeys 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.

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?

Inhoudsopgave

Pagina Bewerken