Transacciones e instrucciones

En Solana, los usuarios envían transacciones para interactuar con la red. Las transacciones contienen una o más instrucciones que especifican operaciones a procesar. La lógica de ejecución para las instrucciones se almacena en programas desplegados en la red de Solana, donde cada programa define su propio conjunto de instrucciones.

A continuación se presentan detalles clave sobre el procesamiento de transacciones en Solana:

  • Si una transacción incluye múltiples instrucciones, estas se ejecutan en el orden en que fueron añadidas a la transacción.
  • Las transacciones son "atómicas" - todas las instrucciones deben procesarse correctamente, o la transacción completa falla y no se produce ningún cambio.

Una transacción es esencialmente una solicitud para procesar una o más instrucciones.

Transacción simplificadaTransacción simplificada

Una transacción es como un sobre que contiene formularios. Cada formulario es una instrucción que le dice a la red qué hacer. Enviar la transacción es como enviar por correo el sobre para que se procesen los formularios.

Puntos clave

  • Las transacciones de Solana incluyen instrucciones que invocan programas en la red.
  • Las transacciones son atómicas - si alguna instrucción falla, toda la transacción falla y no se produce ningún cambio.
  • Las instrucciones en una transacción se ejecutan en orden secuencial.
  • El límite de tamaño de una transacción es de 1232 bytes.
  • Cada instrucción requiere tres piezas de información:
    1. La dirección del programa a invocar
    2. Las cuentas de las que la instrucción lee o en las que escribe
    3. Cualquier dato adicional requerido por la instrucción (p. ej., argumentos de función)

Ejemplo de transferencia de SOL

El diagrama a continuación representa una transacción con una única instrucción para transferir SOL desde un remitente a un receptor.

En Solana, las "carteras" son cuentas que pertenecen al Programa del Sistema. Solo el propietario del programa puede cambiar los datos de una cuenta, por lo que transferir SOL requiere enviar una transacción para invocar al Programa del Sistema.

Transferencia de SOLTransferencia de SOL

La cuenta del remitente debe firmar (is_signer) la transacción para permitir que el Programa del Sistema deduzca su saldo de lamports. Las cuentas del remitente y del destinatario deben ser modificables (is_writable) ya que sus saldos de lamports cambiarán.

Después de enviar la transacción, el Programa del Sistema procesa la instrucción de transferencia. El Programa del Sistema actualiza entonces los saldos de lamports de ambas cuentas, la del remitente y la del destinatario.

Proceso de transferencia de SOLProceso de transferencia de SOL

Los ejemplos a continuación muestran cómo enviar una transacción que transfiere SOL de una cuenta a otra.

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);
Click to execute the code.

Las bibliotecas cliente a menudo abstraen los detalles para construir instrucciones de programa. Si no hay una biblioteca disponible, puedes construir la instrucción manualmente. Esto requiere que conozcas los detalles de implementación de la instrucción.

Los ejemplos a continuación muestran cómo construir manualmente la instrucción de transferencia. La pestaña Expanded Instruction es funcionalmente equivalente a la pestaña Instruction.

  • Kit
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});
  • Legacy
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = SystemProgram.transfer({
fromPubkey: sender.publicKey,
toPubkey: receiver.publicKey,
lamports: transferAmount * LAMPORTS_PER_SOL
});
  • Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOL
let transfer_instruction =
system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);

En las secciones siguientes, explicaremos en detalle las transacciones y las instrucciones.

Instrucciones

Una instrucción en un programa de Solana puede considerarse como una función pública que cualquier persona puede llamar utilizando la red Solana.

Invocar la instrucción de un programa requiere tres elementos clave de información:

  • ID del programa: El programa con la lógica de ejecución para la instrucción
  • Cuentas: Lista de cuentas que la instrucción necesita
  • Instruction data: Array de bytes que especifica la instrucción a invocar en el programa y cualquier argumento requerido por la instrucción
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>,
}

Instrucción de transacciónInstrucción de transacción

AccountMeta

Cada cuenta requerida por una instrucción debe proporcionarse como un AccountMeta que contiene:

  • pubkey: Dirección de la cuenta
  • is_signer: Si la cuenta debe firmar la transacción
  • is_writable: Si la instrucción modifica los datos de la cuenta
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,
}

AccountMetaAccountMeta

Al especificar de antemano qué cuentas lee o escribe una instrucción, las transacciones que no modifican las mismas cuentas pueden ejecutarse en paralelo.

Estructura de ejemplo de instrucción

Ejecuta los ejemplos a continuación para ver la estructura de una instrucción de transferencia de 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));
Click to execute the code.

Los siguientes ejemplos muestran la salida de los fragmentos de código anteriores. El formato exacto difiere según el SDK, pero cada instrucción de Solana requiere la siguiente información:

  • ID del programa: La dirección del programa que ejecutará la instrucción.
  • Cuentas: Una lista de cuentas requeridas por la instrucción. Para cada cuenta, la instrucción debe especificar su dirección, si debe firmar la transacción y si se escribirá en ella.
  • Datos: Un búfer de bytes que indica al programa qué instrucción ejecutar e incluye cualquier argumento requerido por la instrucción.
{
"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
}
}

Transacciones

Una transacción de Solana consiste en:

  1. Firmas: Un array de firmas incluidas en la transacción.
  2. Mensaje: Lista de instrucciones que se procesarán de forma atómica.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Formato de transacciónFormato de transacción

La estructura de un mensaje de transacción consiste en:

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

Mensaje de transacciónMensaje de transacción

Tamaño de transacción

Las transacciones de Solana tienen un límite de tamaño de 1232 bytes. Este límite proviene del tamaño de la Unidad Máxima de Transmisión (MTU) IPv6 de 1280 bytes, menos 48 bytes para encabezados de red (40 bytes IPv6 + 8 bytes de encabezado de fragmento).

El tamaño total de una transacción (firmas y mensaje) debe mantenerse por debajo de este límite e incluye:

  • Firmas: 64 bytes cada una
  • Mensaje: Encabezado (3 bytes), claves de cuenta (32 bytes cada una), blockhash reciente (32 bytes) e instrucciones

Formato de transacciónFormato de transacción

Encabezado del mensaje

El encabezado del mensaje utiliza tres bytes para definir los privilegios de la cuenta.

  1. Firmas requeridas
  2. Número de cuentas firmadas de solo lectura
  3. Número de cuentas no firmadas de solo lectura
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,
}

Encabezado del mensajeEncabezado del mensaje

Formato de matriz compacta

Una matriz compacta en un mensaje de transacción es una matriz serializada en el siguiente formato:

  1. La longitud de la matriz (codificada como compact-u16)
  2. Los elementos de la matriz listados uno tras otro

Formato de matriz compactaFormato de matriz compacta

Este formato se utiliza para codificar las longitudes de las matrices de Direcciones de cuenta e Instrucciones en los mensajes de transacción.

Matriz de direcciones de cuenta

Un mensaje de transacción contiene una matriz de direcciones de cuenta requeridas por sus instrucciones. La matriz comienza con un número compact-u16 que indica cuántas direcciones contiene. Las direcciones se ordenan según sus privilegios, según lo determina el encabezado del mensaje.

  • Cuentas que son escribibles y firmantes
  • Cuentas que son de solo lectura y firmantes
  • Cuentas que son escribibles y no firmantes
  • Cuentas que son de solo lectura y no firmantes

Matriz compacta de direcciones de cuentaMatriz compacta de direcciones de cuenta

Blockhash reciente

Cada transacción requiere un blockhash reciente que sirve para dos propósitos:

  1. Actúa como una marca de tiempo
  2. Previene transacciones duplicadas

Un blockhash expira después de 150 bloques (aproximadamente 1 minuto asumiendo tiempos de bloque de 400ms), después de lo cual la transacción no puede ser procesada.

Puedes usar el método RPC getLatestBlockhash para obtener el blockhash actual y la última altura de bloque en la que el blockhash será válido. Aquí hay un ejemplo en Solana Playground.

Matriz de instrucciones

Un mensaje de transacción contiene una matriz de instrucciones en el tipo CompiledInstruction. Las instrucciones se convierten a este tipo cuando se añaden a una transacción.

Al igual que la matriz de direcciones de cuenta en el mensaje, comienza con una longitud compact-u16 seguida por los datos de instrucción. Cada instrucción contiene:

  1. Índice de ID del programa: Un índice u8 que apunta a la dirección del programa en el array de direcciones de cuentas. Esto especifica el programa que procesará la instrucción.
  2. Índices de cuentas: Un array de índices u8 que apuntan a las direcciones de cuentas requeridas para esta instrucción.
  3. Instruction Data: Un array de bytes que especifica qué instrucción invocar en el programa y cualquier dato adicional requerido por la instrucción (por ejemplo, argumentos de función).
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 compacto de instruccionesArray compacto de instrucciones

Ejemplo de estructura de transacción

Ejecuta los ejemplos a continuación para ver la estructura de una transacción con 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 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));
Click to execute the code.

Los siguientes ejemplos muestran el mensaje de transacción resultante de los fragmentos de código anteriores. El formato exacto difiere según el SDK, pero incluye la misma información.

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

Cuando recuperas una transacción usando su firma después de enviarla a la red, recibirás una respuesta con la siguiente estructura.

El campo message contiene los siguientes campos:

  • header: Especifica privilegios de lectura/escritura y firmante para las direcciones en el array accountKeys

  • accountKeys: Array de todas las direcciones de cuentas utilizadas en las instrucciones de la transacción

  • recentBlockhash: Blockhash usado para marcar la fecha y hora de la transacción

  • instructions: Array de instrucciones a ejecutar. Cada account y programIdIndex en una instrucción hace referencia al array accountKeys por índice.

  • signatures: Array que incluye firmas para todas las cuentas requeridas como firmantes por las instrucciones en la transacción. Una firma se crea firmando el mensaje de la transacción usando la clave privada correspondiente para una cuenta.

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?