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 vereenvoudigdTransactie 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:
    1. Het adres van het programma dat moet worden aangeroepen
    2. De accounts waaruit de instructie leest of waarnaartoe schrijft
    3. 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 TransferSOL 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 procesSOL 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 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 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 SOL
const 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.
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

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
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,
}

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.

AccountMetaAccountMeta

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 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.

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:

  1. 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.
  2. Bericht: Het transactie bericht bevat de lijst met instructies die atomisch moeten worden verwerkt.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

TransactieformaatTransactieformaat

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

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

TransactieformaatTransactieformaat

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.

  1. Het aantal handtekeningen vereist voor alle instructies in de transactie.
  2. Het aantal ondertekende accounts dat alleen-lezen is.
  3. Het aantal niet-ondertekende accounts dat alleen-lezen is.
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 die geserialiseerd is in het volgende formaat:

  1. De arraylengte (gecodeerd als compact-u16)
  2. De array-items op volgorde achter elkaar geplaatst

Compact array formaatCompact 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:

  1. Accounts die schrijfbaar en ondertekenaars zijn
  2. Accounts die alleen-lezen en ondertekenaars zijn
  3. Accounts die schrijfbaar en geen ondertekenaars zijn
  4. 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 accountadressenCompact array van accountadressen

Recente blockhash

Elke transactie vereist een recente blockhash die twee doelen dient:

  1. Fungeert als een tijdstempel voor wanneer de transactie is aangemaakt
  2. 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:

  1. Program ID Index: Een index die verwijst naar het programma-adres in de array met accountadressen. Dit specificeert het programma dat de instructie verwerkt.
  2. Account Indexes: Een array van 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. functieargumenten).
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 instructiesCompacte 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 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.

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.

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