Riepilogo
Una transazione contiene firme + un messaggio. Il messaggio contiene un'intestazione, indirizzi degli account, blockhash recente e istruzioni compilate. Dimensione massima serializzata: 1.232 byte.
Una
Transaction
ha due campi di primo livello:
signatures: un array di firmemessage: informazioni sulla transazione, inclusa la lista delle istruzioni da elaborare
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagramma che mostra le due parti di una transazione
La dimensione totale serializzata di una transazione non deve superare
PACKET_DATA_SIZE
(1.232 byte). Questo limite equivale a 1.280 byte (MTU minimo IPv6) meno 48 byte
per le intestazioni di rete (40 byte IPv6 + 8 byte intestazione frammento). I
1.232 byte includono sia l'array signatures che lo struct
message.
Diagramma che mostra il formato della transazione e i limiti di dimensione
Firme
Il campo signatures è un array con codifica compatta di valori
Signature.
Ogni Signature è una firma Ed25519 da 64 byte del Message serializzato,
firmata con la chiave privata dell'account firmatario. È richiesta una firma per
ogni account firmatario referenziato dalle istruzioni
della transazione.
La prima firma nell'array appartiene al fee payer, l'account che paga la commissione base e la commissione di prioritizzazione della transazione base fee and prioritization fee. Questa prima firma funge anche da ID transazione, utilizzato per cercare la transazione sulla rete. L'ID transazione è comunemente chiamato firma della transazione.
Requisiti del pagatore delle commissioni:
- Deve essere il primo account nel messaggio (indice 0) e un firmatario.
- Deve essere un account di proprietà del System Program o un account nonce
(validato da
validate_fee_payer). - Deve contenere abbastanza lamport per coprire
rent_exempt_minimum + total_fee; altrimenti la transazione fallisce conInsufficientFundsForFee.
Messaggio
Il campo message è una struct
Message
che contiene il payload della transazione:
header: L'header del messaggioaccount_keys: Un array di indirizzi account richiesti dalle istruzioni della transazionerecent_blockhash: Un blockhash che funge da timestamp per la transazioneinstructions: Un array di istruzioni
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>,}
Header
Il campo header è una struct
MessageHeader
con tre campi u8 che suddividono l'array account_keys in gruppi di permessi:
num_required_signatures: Numero totale di firme richieste dalla transazione.num_readonly_signed_accounts: Numero di account firmati che sono in sola lettura.num_readonly_unsigned_accounts: Numero di account non firmati che sono in 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,}
Diagramma che mostra le tre parti dell'header del messaggio
Indirizzi account
Il campo
account_keys
è un array con codifica compatta di chiavi pubbliche. Ogni voce identifica un
account utilizzato da almeno una delle istruzioni della transazione. L'array
deve includere ogni account e deve seguire questo ordinamento rigoroso:
- Firmatario + Scrivibile
- Firmatario + Sola lettura
- Non firmatario + Scrivibile
- Non firmatario + Sola lettura
Questo ordinamento rigoroso consente di combinare l'array account_keys con i
tre conteggi nell'header del messaggio per determinare i permessi
per ogni account senza memorizzare flag di metadati per singolo account. I
conteggi dell'header suddividono l'array nei quattro gruppi di permessi
elencati sopra.
Diagramma che mostra l'ordine dell'array degli indirizzi degli account
Blockhash recente
Il campo recent_blockhash è un hash a 32 byte che serve a due scopi:
- Timestamp: dimostra che la transazione è stata creata di recente.
- Deduplicazione: impedisce che la stessa transazione venga elaborata due volte.
Un blockhash scade dopo 150 slot. Se il blockhash non è più valido quando la
transazione arriva, viene rifiutata con BlockhashNotFound, a meno che non
sia una transazione con nonce durevole
valida.
Il metodo RPC getLatestBlockhash
consente di ottenere il blockhash corrente e l'ultima altezza del blocco alla
quale il blockhash sarà valido.
Istruzioni
Il campo
instructions
è un array con codifica compatta di struct
CompiledInstruction.
Ogni CompiledInstruction fa riferimento agli account tramite indice
nell'array account_keys anziché tramite chiave pubblica completa. Contiene:
program_id_index: indice inaccount_keysche identifica il programma da invocare.accounts: array di indici inaccount_keysche specifica gli account da passare al programma.data: array di byte contenente il discriminatore dell'istruzione e gli argomenti serializzati.
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
Formato binario della transazione
Le transazioni sono serializzate utilizzando uno schema di codifica compatta. Tutti gli array a lunghezza variabile (firme, chiavi degli account, istruzioni) sono preceduti da una codifica di lunghezza compact-u16. Questo formato utilizza 1 byte per valori 0-127 e 2-3 byte per valori più grandi.
Layout della transazione legacy (on the wire):
| Campo | Dimensione | Descrizione |
|---|---|---|
num_signatures | 1-3 byte (compact-u16) | Numero di firme |
signatures | num_signatures x 64 byte | Firme Ed25519 |
num_required_signatures | 1 byte | Campo 1 di MessageHeader |
num_readonly_signed | 1 byte | Campo 2 di MessageHeader |
num_readonly_unsigned | 1 byte | Campo 3 di MessageHeader |
num_account_keys | 1-3 byte (compact-u16) | Numero di chiavi di account statiche |
account_keys | num_account_keys x 32 byte | Chiavi pubbliche |
recent_blockhash | 32 byte | Blockhash |
num_instructions | 1-3 byte (compact-u16) | Numero di istruzioni |
instructions | variabile | Array di istruzioni compilate |
Ogni istruzione compilata è serializzata come:
| Campo | Dimensione | Descrizione |
|---|---|---|
program_id_index | 1 byte | Indice nelle chiavi account |
num_accounts | 1-3 byte (compact-u16) | Numero di indici account |
account_indices | num_accounts x 1 byte | Indici delle chiavi account |
data_len | 1-3 byte (compact-u16) | Lunghezza dei dati dell'istruzione |
data | data_len byte | Dati dell'istruzione opachi |
Calcolo della dimensione
Dato PACKET_DATA_SIZE = 1.232 byte, lo spazio disponibile può essere
calcolato:
Total = 1232 bytes- compact-u16(num_sigs) # 1 byte- num_sigs * 64 # signature bytes- 3 # message header- compact-u16(num_keys) # 1 byte- num_keys * 32 # account key bytes- 32 # recent blockhash- compact-u16(num_ixs) # 1 byte- sum(instruction_sizes) # per-instruction overhead + data
Esempio: transazione di trasferimento SOL
Il diagramma seguente mostra come transazioni e istruzioni lavorano insieme per consentire agli utenti di interagire con la rete. In questo esempio, SOL viene trasferito da un account a un altro.
I metadati dell'account mittente indicano che deve firmare la transazione. Questo consente al System Program di dedurre i lamport. Sia l'account mittente che quello destinatario devono essere scrivibili, affinché il loro saldo in lamport possa cambiare. Per eseguire questa istruzione, il wallet del mittente invia la transazione contenente la sua firma e il messaggio contenente l'istruzione di trasferimento SOL.
Diagramma trasferimento SOL
Dopo l'invio della transazione, il System Program elabora l'istruzione di trasferimento e aggiorna il saldo in lamport di entrambi gli account.
Diagramma processo trasferimento SOL
L'esempio seguente mostra il codice rilevante per i diagrammi sopra. Vedi la
funzione transfer
del System Program.
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);
L'esempio seguente mostra la struttura di una transazione che contiene 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));
Il codice seguente mostra l'output dei frammenti di codice precedenti. Il formato varia tra gli SDK, ma nota che ogni istruzione contiene le stesse informazioni richieste.
{"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}}]}
Recupero dei dettagli della transazione
Dopo l'invio, recupera i dettagli della transazione utilizzando la firma della transazione e il metodo RPC getTransaction.
Puoi anche trovare la transazione utilizzando Solana Explorer.
{"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?