Transazioni e istruzioni

Su Solana, gli utenti inviano transazioni per interagire con la rete. Le transazioni contengono una o più istruzioni che specificano le operazioni da elaborare. La logica di esecuzione per le istruzioni è memorizzata nei programmi distribuiti sulla rete Solana, dove ogni programma definisce il proprio set di istruzioni.

Di seguito sono riportati i dettagli chiave sull'elaborazione delle transazioni Solana:

  • Se una transazione include più istruzioni, queste vengono eseguite nell'ordine in cui sono state aggiunte alla transazione.
  • Le transazioni sono "atomiche" - tutte le istruzioni devono essere elaborate con successo, altrimenti l'intera transazione fallisce e non avviene alcuna modifica.

Una transazione è essenzialmente una richiesta per elaborare una o più istruzioni. Puoi pensare a una transazione come a una busta contenente moduli. Ogni modulo è un' istruzione che indica alla rete cosa fare. Inviare la transazione è come spedire la busta per far elaborare i moduli.

Transazione semplificataTransazione semplificata

Punti chiave

  • Le transazioni Solana includono istruzioni che invocano programmi sulla rete.
  • Le transazioni sono atomiche - se qualsiasi istruzione fallisce, l'intera transazione fallisce e non avviene alcuna modifica.
  • Le istruzioni in una transazione vengono eseguite in ordine sequenziale.
  • Il limite di dimensione della transazione è di 1232 byte.
  • Ogni istruzione richiede tre elementi informativi:
    1. L'indirizzo del programma da invocare
    2. Gli account da cui l'istruzione legge o su cui scrive
    3. Eventuali dati aggiuntivi richiesti dall'istruzione (es. argomenti della funzione)

Esempio di trasferimento SOL

Il diagramma seguente rappresenta una transazione con una singola istruzione per trasferire SOL da un mittente a un destinatario.

Su Solana, i "wallet" sono account posseduti dal System Program. Solo il programma proprietario può modificare i dati di un account, quindi trasferire SOL richiede l'invio di una transazione per invocare il System Program.

Trasferimento SOLTrasferimento SOL

L'account del mittente deve firmare (is_signer) la transazione per consentire al System Program di detrarre il suo saldo in lamport. Gli account del mittente e del destinatario devono essere scrivibili (is_writable) poiché i loro saldi in lamport cambiano.

Dopo l'invio della transazione, il System Program elabora l'istruzione di trasferimento. Il System Program aggiorna quindi i saldi in lamport di entrambi gli account del mittente e del destinatario.

Processo di trasferimento SOLProcesso di trasferimento SOL

Gli esempi seguenti mostrano come inviare una transazione che trasferisce SOL da un account a un altro. Vedi il codice sorgente dell'istruzione di trasferimento del System Program qui.

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.

Le librerie client spesso astraggono i dettagli per la costruzione delle istruzioni del programma. Se una libreria non è disponibile, puoi costruire manualmente l'istruzione. Questo richiede di conoscere i dettagli implementativi dell'istruzione.

Gli esempi seguenti mostrano come costruire manualmente l'istruzione di trasferimento. La scheda Expanded Instruction è funzionalmente equivalente alla scheda Instruction.

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

Nelle sezioni seguenti, esamineremo in dettaglio le transazioni e le istruzioni.

Istruzioni

Un instruction su un programma Solana può essere considerata come una funzione pubblica che può essere chiamata da chiunque utilizzi la rete Solana.

Puoi pensare a un programma Solana come a un server web ospitato sulla rete Solana, dove ogni istruzione è come un endpoint API pubblico che gli utenti possono chiamare per eseguire azioni specifiche. Invocare un'istruzione è simile all'invio di una richiesta POST a un endpoint API, permettendo agli utenti di eseguire la logica di business del programma.

Per chiamare un'istruzione di un programma su Solana, devi costruire una Instruction con tre elementi informativi:

  • Program ID: L'indirizzo del programma con la logica di business per l'istruzione che viene invocata.
  • Accounts: L'elenco di tutti gli account da cui l'istruzione legge o su cui scrive.
  • Instruction Data: Un array di byte che specifica quale istruzione invocare sul programma e qualsiasi argomento richiesto dall'istruzione.
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

Quando crei una Instruction, devi fornire ogni account richiesto come un AccountMeta. L'AccountMeta specifica quanto segue:

  • pubkey: L'indirizzo dell'account
  • is_signer: Se l'account deve firmare la transazione
  • is_writable: Se l'istruzione modifica i dati dell'account
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,
}

Specificando in anticipo quali account un'istruzione legge o scrive, le transazioni che non modificano gli stessi account possono essere eseguite in parallelo.

Per sapere quali account richiede un'istruzione, inclusi quelli che devono essere scrivibili, di sola lettura o che devono firmare la transazione, devi fare riferimento all'implementazione dell'istruzione come definita dal programma.

In pratica, solitamente non è necessario costruire un Instruction manualmente. La maggior parte degli sviluppatori di programmi fornisce librerie client con funzioni di supporto che creano le istruzioni per te.

AccountMetaAccountMeta

Esempio di struttura di un'istruzione

Esegui gli esempi seguenti per vedere la struttura di un'istruzione di trasferimento di SOL.

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.

I seguenti esempi mostrano l'output degli snippet di codice precedenti. Il formato esatto varia a seconda dell'SDK, ma ogni istruzione Solana richiede le seguenti informazioni:

  • ID del programma: L'indirizzo del programma che eseguirà l'istruzione.
  • Account: Un elenco di account richiesti dall'istruzione. Per ciascun account, l'istruzione deve specificare il suo indirizzo, se deve firmare la transazione e se verrà scritto.
  • Dati: Un buffer di byte che indica al programma quale istruzione eseguire e include eventuali argomenti richiesti dall'istruzione.
{
"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
}
}

Transazioni

Dopo aver creato le istruzioni che desideri invocare, il passo successivo è creare una Transaction e aggiungere le istruzioni alla transazione. Una transazione Solana è composta da:

  1. Firme: Un array di firme da tutti gli account richiesti come firmatari per le istruzioni nella transazione. Una firma viene creata firmando il Message della transazione con la chiave privata dell'account.
  2. Messaggio: Il messaggio della transazione include l'elenco delle istruzioni da elaborare atomicamente.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Formato della TransazioneFormato della Transazione

La struttura di un messaggio di transazione consiste in:

  • Intestazione del Messaggio: Specifica il numero di account firmatari e di sola lettura.
  • Indirizzi degli Account: Un array di indirizzi di account richiesti dalle istruzioni nella transazione.
  • Blockhash Recente: Funge da timestamp per la transazione.
  • Istruzioni: Un array di istruzioni da eseguire.
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>,
}

Dimensione della Transazione

Le transazioni Solana hanno un limite di dimensione di 1232 byte. Questo limite deriva dalla dimensione dell'Unità Massima di Trasmissione (MTU) IPv6 di 1280 byte, meno 48 byte per le intestazioni di rete (40 byte IPv6 + 8 byte di intestazione).

La dimensione totale di una transazione (firme e messaggio) deve rimanere sotto questo limite e include:

  • Firme: 64 byte ciascuna
  • Messaggio: Intestazione (3 byte), chiavi degli account (32 byte ciascuna), blockhash recente (32 byte) e istruzioni

Formato della TransazioneFormato della Transazione

Intestazione del Messaggio

L'intestazione del messaggio specifica i permessi per l'account nella transazione. Funziona in combinazione con gli indirizzi degli account ordinati rigorosamente per determinare quali account sono firmatari e quali sono scrivibili.

  1. Il numero di firme richieste per tutte le istruzioni sulla transazione.
  2. Il numero di account firmati che sono di sola lettura.
  3. Il numero di account non firmati che sono di sola lettura.
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,
}

Intestazione del MessaggioIntestazione del Messaggio

Formato Compact-Array

Un array compatto in un messaggio di transazione è un array serializzato nel seguente formato:

  1. La lunghezza dell'array (codificata come compact-u16)
  2. Gli elementi dell'array elencati uno dopo l'altro

Formato array compattoFormato array compatto

Questo formato viene utilizzato per codificare le lunghezze degli array Indirizzi degli Account e Istruzioni nei messaggi di transazione.

Array di indirizzi account

Un messaggio di transazione contiene un singolo elenco di tutti gli indirizzi account richiesti dalle sue istruzioni. L'array inizia con un numero compact-u16 che indica quanti indirizzi contiene.

Per risparmiare spazio, la transazione non memorizza i permessi per ogni account individualmente. Invece, si basa su una combinazione dell'MessageHeader e un ordinamento rigoroso degli indirizzi account per determinare i permessi.

Gli indirizzi sono sempre ordinati nel seguente modo:

  1. Account che sono scrivibili e firmatari
  2. Account che sono sola lettura e firmatari
  3. Account che sono scrivibili e non firmatari
  4. Account che sono sola lettura e non firmatari

L'MessageHeader fornisce i valori utilizzati per determinare il numero di account per ogni gruppo di permessi.

Array compatto di indirizzi accountArray compatto di indirizzi account

Blockhash recente

Ogni transazione richiede un blockhash recente che serve a due scopi:

  1. Funge da timestamp per quando la transazione è stata creata
  2. Previene transazioni duplicate

Un blockhash scade dopo 150 blocchi (circa 1 minuto assumendo tempi di blocco di 400ms), dopo di che la transazione è considerata scaduta e non può essere elaborata.

Puoi utilizzare il metodo RPC getLatestBlockhash per ottenere il blockhash corrente e l'altezza dell'ultimo blocco in cui il blockhash sarà valido.

Array di istruzioni

Un messaggio di transazione contiene un array di istruzioni nel tipo CompiledInstruction. Le istruzioni vengono convertite in questo tipo quando aggiunte a una transazione.

Come l'array di indirizzi account nel messaggio, inizia con una lunghezza compact-u16 seguita dai dati dell'istruzione. Ogni istruzione contiene:

  1. Indice ID programma: Un indice che punta all'indirizzo del programma nell'array degli indirizzi degli account. Questo specifica il programma che elabora l'istruzione.
  2. Indici degli account: Un array di indici che puntano agli indirizzi degli account richiesti per questa istruzione.
  3. Instruction data: Un array di byte che specifica quale istruzione invocare sul programma e qualsiasi dato aggiuntivo richiesto dall'istruzione (es. argomenti della funzione).
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>,
}

Array compatto di istruzioniArray compatto di istruzioni

Esempio di struttura di transazione

Esegui gli esempi seguenti per vedere la struttura di una transazione con una singola istruzione di trasferimento SOL.

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.

I seguenti esempi mostrano l'output del messaggio di transazione dagli snippet di codice precedenti. Il formato esatto differisce a seconda dell'SDK, ma include le stesse informazioni.

{
"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
}
}
]
}

Dopo aver inviato una transazione, puoi recuperare i suoi dettagli utilizzando il metodo RPC getTransaction. La risposta avrà una struttura simile allo snippet seguente. In alternativa, puoi ispezionare la transazione utilizzando Solana Explorer.

Una "firma di transazione" identifica in modo univoco una transazione su Solana. Usi questa firma per cercare i dettagli della transazione sulla rete. La firma della transazione è semplicemente la prima firma sulla transazione. Nota che la prima firma è anche la firma del pagatore delle commissioni della transazione.

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?