Struttura delle transazioni

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 firme
  • message: informazioni sulla transazione, inclusa la lista delle istruzioni da elaborare
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Diagramma che mostra le due parti di una transazioneDiagramma 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 dimensioneDiagramma 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 con InsufficientFundsForFee.

Messaggio

Il campo message è una struct Message che contiene il payload della transazione:

  • header: L'header del messaggio
  • account_keys: Un array di indirizzi account richiesti dalle istruzioni della transazione
  • recent_blockhash: Un blockhash che funge da timestamp per la transazione
  • instructions: Un array di istruzioni
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>,
}

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

Diagramma che mostra le tre parti dell'header del messaggioDiagramma 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:

  1. Firmatario + Scrivibile
  2. Firmatario + Sola lettura
  3. Non firmatario + Scrivibile
  4. 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 accountDiagramma 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:

  1. Timestamp: dimostra che la transazione è stata creata di recente.
  2. 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:

  1. program_id_index: indice in account_keys che identifica il programma da invocare.
  2. accounts: array di indici in account_keys che specifica gli account da passare al programma.
  3. data: array di byte contenente il discriminatore dell'istruzione e gli argomenti serializzati.
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

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):

CampoDimensioneDescrizione
num_signatures1-3 byte (compact-u16)Numero di firme
signaturesnum_signatures x 64 byteFirme Ed25519
num_required_signatures1 byteCampo 1 di MessageHeader
num_readonly_signed1 byteCampo 2 di MessageHeader
num_readonly_unsigned1 byteCampo 3 di MessageHeader
num_account_keys1-3 byte (compact-u16)Numero di chiavi di account statiche
account_keysnum_account_keys x 32 byteChiavi pubbliche
recent_blockhash32 byteBlockhash
num_instructions1-3 byte (compact-u16)Numero di istruzioni
instructionsvariabileArray di istruzioni compilate

Ogni istruzione compilata è serializzata come:

CampoDimensioneDescrizione
program_id_index1 byteIndice nelle chiavi account
num_accounts1-3 byte (compact-u16)Numero di indici account
account_indicesnum_accounts x 1 byteIndici delle chiavi account
data_len1-3 byte (compact-u16)Lunghezza dei dati dell'istruzione
datadata_len byteDati 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 SOLDiagramma 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 SOLDiagramma 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 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.

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

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.

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?

Indice dei contenuti

Modifica pagina

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso