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 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:
- La dirección del programa a invocar
- Las cuentas de las que la instrucción lee o en las que escribe
- 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 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 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 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);
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 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);
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
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ón
AccountMeta
Cada cuenta requerida por una instrucción debe proporcionarse como un AccountMeta que contiene:
pubkey
: Dirección de la cuentais_signer
: Si la cuenta debe firmar la transacciónis_writable
: Si la instrucción modifica los datos de la cuenta
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
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 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));
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:
- Firmas: Un array de firmas incluidas en la transacción.
- Mensaje: Lista de instrucciones que se procesarán de forma atómica.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Formato de transacción
La estructura de un mensaje de transacción consiste en:
- Encabezado del mensaje: Especifica el número de firmantes y cuentas de solo lectura.
- Direcciones de cuentas: Un array de direcciones de cuentas requeridas por las instrucciones en la transacción.
- Blockhash reciente: Actúa como una marca de tiempo para la transacción.
- Instrucciones: Un array de instrucciones que se ejecutarán.
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ó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ón
Encabezado del mensaje
El encabezado del mensaje utiliza tres bytes para definir los privilegios de la cuenta.
- Firmas requeridas
- Número de cuentas firmadas de solo lectura
- Número de cuentas no firmadas 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,}
Encabezado del mensaje
Formato de matriz compacta
Una matriz compacta en un mensaje de transacción es una matriz serializada en el siguiente formato:
- La longitud de la matriz (codificada como compact-u16)
- Los elementos de la matriz listados uno tras otro
Formato 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 cuenta
Blockhash reciente
Cada transacción requiere un blockhash reciente que sirve para dos propósitos:
- Actúa como una marca de tiempo
- 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:
- Í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.
- Índices de cuentas: Un array de índices u8 que apuntan a las direcciones de cuentas requeridas para esta instrucción.
- 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).
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
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 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));
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 arrayaccountKeys
-
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. Cadaaccount
yprogramIdIndex
en una instrucción hace referencia al arrayaccountKeys
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.
{"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?