Resumen
Una transacción tiene firmas + un mensaje. El mensaje contiene un encabezado, direcciones de cuenta, blockhash reciente e instrucciones compiladas. Tamaño serializado máximo: 1.232 bytes.
Una
Transaction
tiene dos campos de nivel superior:
signatures: Un array de firmasmessage: Información de la transacción, incluyendo la lista de instrucciones a procesar
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagrama mostrando las dos partes de una transacción
El tamaño serializado total de una transacción no debe exceder
PACKET_DATA_SIZE
(1.232 bytes). Este límite equivale a 1.280 bytes (el MTU mínimo de IPv6) menos
48 bytes para encabezados de red (40 bytes IPv6 + 8 bytes de encabezado de
fragmento). Los 1.232 bytes incluyen tanto el array signatures
como la estructura message.
Diagrama mostrando el formato de transacción y límites de tamaño
Firmas
El campo signatures es un array codificado de forma compacta de valores
Signature.
Cada Signature es una firma Ed25519 de 64 bytes del Message serializado,
firmado con la clave privada de la cuenta firmante. Se requiere una firma por
cada cuenta firmante referenciada por las instrucciones de
la transacción.
La primera firma en el array pertenece al pagador de comisiones, la cuenta que paga la comisión base y comisión de priorización de la transacción. Esta primera firma también sirve como el ID de transacción, usado para buscar la transacción en la red. El ID de transacción se conoce comúnmente como la firma de transacción.
Requisitos del pagador de comisiones:
- Debe ser la primera cuenta en el mensaje (índice 0) y un firmante.
- Debe ser una cuenta propiedad del System Program o una cuenta nonce (validada
por
validate_fee_payer). - Debe contener suficientes lamports para cubrir
rent_exempt_minimum + total_fee; de lo contrario, la transacción falla conInsufficientFundsForFee.
Mensaje
El campo message es una estructura
Message
que contiene la carga útil de la transacción:
header: El encabezado del mensajeaccount_keys: Un array de direcciones de cuentas requeridas por las instrucciones de la transacciónrecent_blockhash: Un blockhash que actúa como marca de tiempo para la transaccióninstructions: Un array de instrucciones
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>,}
Encabezado
El campo header es una estructura
MessageHeader
con tres campos u8 que dividen el array account_keys en grupos de permisos:
num_required_signatures: Número total de firmas requeridas por la transacción.num_readonly_signed_accounts: Número de cuentas firmadas que son de solo lectura.num_readonly_unsigned_accounts: Número de cuentas sin firmar que son de solo lectura.
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,}
Diagrama que muestra las tres partes del encabezado del mensaje
Direcciones de cuentas
El campo
account_keys
es un array codificado de forma compacta de claves públicas. Cada entrada
identifica una cuenta utilizada por al menos una de las instrucciones de la
transacción. El array debe incluir todas las cuentas y debe seguir este orden
estricto:
- Firmante + Escribible
- Firmante + Solo lectura
- No firmante + Escribible
- No firmante + Solo lectura
Este orden estricto permite que el array account_keys se combine con los
tres contadores en el header del mensaje para determinar los
permisos de cada cuenta sin almacenar flags de metadatos por cuenta. Los
contadores del encabezado dividen el array en los cuatro grupos de permisos
listados arriba.
Diagrama que muestra el orden del array de direcciones de cuenta
Blockhash reciente
El campo recent_blockhash es un hash de 32 bytes que cumple dos propósitos:
- Marca de tiempo: demuestra que la transacción se creó recientemente.
- Deduplicación: evita que la misma transacción se procese dos veces.
Un blockhash expira después de 150 slots. Si el blockhash ya no es válido cuando
llega la transacción, se rechaza con BlockhashNotFound, a menos que sea
una transacción de nonce durable
válida.
El método RPC getLatestBlockhash te
permite obtener el blockhash actual y la última altura de bloque en la que el
blockhash será válido.
Instrucciones
El campo
instructions
es un array codificado de forma compacta de estructuras
CompiledInstruction.
Cada CompiledInstruction hace referencia a las cuentas por índice en el
array account_keys en lugar de por clave pública completa. Contiene:
program_id_index: Índice enaccount_keysque identifica el programa a invocar.accounts: Array de índices enaccount_keysque especifica las cuentas a pasar al programa.data: Array de bytes que contiene el discriminador de instrucción y los argumentos serializados.
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 compacto de instrucciones
Formato binario de transacción
Las transacciones se serializan usando un esquema de codificación compacta. Todos los arrays de longitud variable (firmas, claves de cuenta, instrucciones) tienen como prefijo una codificación de longitud compact-u16. Este formato usa 1 byte para valores 0-127 y 2-3 bytes para valores mayores.
Diseño de transacción legacy (en la red):
| Campo | Tamaño | Descripción |
|---|---|---|
num_signatures | 1-3 bytes (compact-u16) | Número de firmas |
signatures | num_signatures x 64 bytes | Firmas Ed25519 |
num_required_signatures | 1 byte | Campo 1 de MessageHeader |
num_readonly_signed | 1 byte | Campo 2 de MessageHeader |
num_readonly_unsigned | 1 byte | Campo 3 de MessageHeader |
num_account_keys | 1-3 bytes (compact-u16) | Número de claves de cuenta estáticas |
account_keys | num_account_keys x 32 bytes | Claves públicas |
recent_blockhash | 32 bytes | Blockhash |
num_instructions | 1-3 bytes (compact-u16) | Número de instrucciones |
instructions | variable | Array de instrucciones compiladas |
Cada instrucción compilada se serializa como:
| Campo | Tamaño | Descripción |
|---|---|---|
program_id_index | 1 byte | Índice en las claves de cuenta |
num_accounts | 1-3 bytes (compact-u16) | Número de índices de cuenta |
account_indices | num_accounts x 1 byte | Índices de claves de cuenta |
data_len | 1-3 bytes (compact-u16) | Longitud de los datos de instrucción |
data | data_len bytes | Datos de instrucción opacos |
Cálculo de tamaño
Dado PACKET_DATA_SIZE = 1.232 bytes, el espacio disponible se puede
calcular:
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
Ejemplo: transacción de transferencia de SOL
El diagrama a continuación muestra cómo las transacciones e instrucciones trabajan juntas para permitir a los usuarios interactuar con la red. En este ejemplo, se transfiere SOL de una cuenta a otra.
Los metadatos de la cuenta del remitente indican que debe firmar la transacción. Esto permite al System Program deducir lamports. Tanto la cuenta del remitente como la del destinatario deben ser escribibles, para que su saldo de lamports pueda cambiar. Para ejecutar esta instrucción, la cartera del remitente envía la transacción que contiene su firma y el mensaje que contiene la instrucción de transferencia de SOL.
Diagrama de transferencia de SOL
Después de que se envía la transacción, el System Program procesa la instrucción de transferencia y actualiza el saldo de lamports de ambas cuentas.
Diagrama del proceso de transferencia de SOL
El ejemplo a continuación muestra el código relevante para los diagramas
anteriores. Consulta la
función 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);
El siguiente ejemplo muestra la estructura de una transacción que contiene una sola instrucción de transferencia de 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));
El código a continuación muestra la salida de los fragmentos de código anteriores. El formato difiere entre SDKs, pero observa que cada instrucción contiene la misma información requerida.
{"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}}]}
Obtener detalles de la transacción
Después del envío, recupera los detalles de la transacción usando la firma de la transacción y el método RPC getTransaction.
También puedes encontrar la transacción usando 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?