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 program distribuiti sulla rete Solana, dove ogni program 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 di elaborazione di una o più istruzioni.
Transazione semplificata
Una transazione è come una busta contenente moduli. Ogni modulo è un'istruzione che dice alla rete cosa fare. Inviare la transazione è come spedire la busta per far elaborare i moduli.
Punti chiave
- Le transazioni Solana includono istruzioni che invocano program 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 informazioni:
- L'indirizzo del program da invocare
- Gli account da cui l'istruzione legge o su cui scrive
- Eventuali dati aggiuntivi richiesti dall'istruzione (ad es., argomenti della funzione)
Esempio di trasferimento di 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 SOL
L'account del mittente deve firmare (is_signer
) la transazione per permettere
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 SOL
Gli esempi seguenti mostrano come inviare una transazione che trasferisce SOL da un account a un altro.
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);
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
.
- 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);
Nelle sezioni seguenti, esamineremo in dettaglio le transazioni e le istruzioni.
Istruzioni
Un'istruzione su un programma Solana può essere considerata come una funzione pubblica che può essere chiamata da chiunque utilizzi la rete Solana.
L'invocazione di un'istruzione di un programma richiede tre elementi chiave di informazione:
- ID del programma: Il programma con la logica di esecuzione per l'istruzione
- Account: Elenco degli account di cui l'istruzione ha bisogno
- Instruction data: Array di byte che specifica l'istruzione da invocare sul programma e qualsiasi argomento richiesto dall'istruzione
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>,}
Istruzione di transazione
AccountMeta
Ogni account richiesto da un'istruzione deve essere fornito come un AccountMeta che contiene:
pubkey
: Indirizzo dell'accountis_signer
: Se l'account deve firmare la transazioneis_writable
: Se l'istruzione modifica i dati dell'account
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
Specificando in anticipo quali account un'istruzione legge o scrive, le transazioni che non modificano gli stessi account possono essere eseguite in parallelo.
Esempio di struttura di istruzione
Esegui gli esempi seguenti per vedere la struttura di un'istruzione di trasferimento SOL.
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));
I seguenti esempi mostrano l'output degli snippet di codice precedenti. Il formato esatto differisce a seconda dell'SDK, ma ogni istruzione Solana richiede le seguenti informazioni:
- Program ID: L'indirizzo del programma che eseguirà l'istruzione.
- Accounts: 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à modificato.
- Data: 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}}
Transactions
Una transaction di Solana consiste in:
- Signatures: Un array di firme incluse nella transazione.
- Message: Elenco di istruzioni da elaborare in modo atomico.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Formato della Transaction
La struttura di un messaggio di transazione consiste in:
- Message Header: Specifica il numero di account firmatari e di sola lettura.
- Account Addresses: Un array di indirizzi di account richiesti dalle istruzioni nella transazione.
- Recent Blockhash: Funge da timestamp per la transazione.
- Instructions: Un array di istruzioni da eseguire.
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>,}
Messaggio della Transaction
Dimensione della Transaction
Le transazioni di 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 gli header di rete (40 byte IPv6 + 8 byte header di frammento).
La dimensione totale di una transazione (firme e messaggio) deve rimanere sotto questo limite e include:
- Signatures: 64 byte ciascuna
- Message: Header (3 byte), chiavi degli account (32 byte ciascuna), recent blockhash (32 byte) e istruzioni
Formato della Transaction
Message Header
L' header del messaggio utilizza tre byte per definire i privilegi degli account.
- Firme richieste
- Numero di account firmati di sola lettura
- Numero di account non firmati di sola lettura
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 messaggio
Formato Compact-Array
Un array compatto in un messaggio di transazione è un array serializzato nel seguente formato:
- La lunghezza dell'array (codificata come compact-u16)
- Gli elementi dell'array elencati uno dopo l'altro
Formato array compatto
Questo formato viene utilizzato per codificare le lunghezze degli array Indirizzi degli account e Istruzioni nei messaggi delle transazioni.
Array di indirizzi degli account
Un messaggio di transazione contiene un array di indirizzi di account richiesti dalle sue istruzioni. L'array inizia con un numero compact-u16 che indica quanti indirizzi contiene. Gli indirizzi sono quindi ordinati in base ai loro privilegi, come determinato dall'intestazione del messaggio.
- Account scrivibili e firmatari
- Account di sola lettura e firmatari
- Account scrivibili e non firmatari
- Account di sola lettura e non firmatari
Array compatto di indirizzi degli account
Recent Blockhash
Ogni transazione richiede un recent blockhash che serve a due scopi:
- Funge da timestamp
- Previene transazioni duplicate
Un blockhash scade dopo 150 blocchi (circa 1 minuto assumendo tempi di blocco di 400ms), dopo di che la transazione 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. Ecco un esempio su
Solana Playground.
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 degli indirizzi degli account nel messaggio, inizia con una lunghezza compact-u16 seguita dai dati dell'instruction data. Ogni istruzione contiene:
- Indice ID del programma: un indice u8 che punta all'indirizzo del programma nell'array degli indirizzi degli account. Questo specifica il programma che elaborerà l'istruzione.
- Indici degli account: un array di indici u8 che puntano agli indirizzi degli account richiesti per questa istruzione.
- Instruction Data: un array di byte che specifica quale istruzione invocare sul programma e qualsiasi dato aggiuntivo richiesto dall'istruzione (es. argomenti della funzione).
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 istruzioni
Esempio di struttura di una 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 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));
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}}]}
Quando recuperi una transazione utilizzando la sua firma dopo averla inviata alla rete, riceverai una risposta con la seguente struttura.
Il campo message
contiene i seguenti campi:
-
header
: specifica i privilegi di lettura/scrittura e firma per gli indirizzi nell'arrayaccountKeys
-
accountKeys
: array di tutti gli indirizzi degli account utilizzati nelle istruzioni della transazione -
recentBlockhash
: blockhash utilizzato per marcare temporalmente la transazione -
instructions
: array di istruzioni da eseguire. Ogniaccount
eprogramIdIndex
in un'istruzione fa riferimento all'arrayaccountKeys
per indice. -
signatures
: array che include le firme per tutti gli account richiesti come firmatari dalle istruzioni sulla transazione. Una firma viene creata firmando il messaggio della transazione utilizzando la chiave privata corrispondente per un 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?