Transações e instruções
Na Solana, os usuários enviam transações para interagir com a rede. As transações contêm uma ou mais instruções que especificam operações a serem processadas. A lógica de execução para instruções é armazenada em programas implantados na rede Solana, onde cada programa define seu próprio conjunto de instruções.
Abaixo estão detalhes importantes sobre o processamento de transações na Solana:
- Se uma transação inclui múltiplas instruções, as instruções são executadas na ordem em que foram adicionadas à transação.
- As transações são "atômicas" - todas as instruções devem ser processadas com sucesso, ou a transação inteira falha e nenhuma mudança ocorre.
Uma transação é essencialmente uma solicitação para processar uma ou mais instruções. Você pode pensar em uma transação como um envelope contendo formulários. Cada formulário é uma instrução que diz à rede o que fazer. Enviar a transação é como enviar o envelope pelo correio para que os formulários sejam processados.
Transação Simplificada
Pontos-chave
- As transações da Solana incluem instruções que invocam programas na rede.
- As transações são atômicas - se qualquer instrução falhar, toda a transação falha e nenhuma mudança ocorre.
- As instruções em uma transação são executadas em ordem sequencial.
- O limite de tamanho da transação é 1232 bytes.
- Cada instrução requer três informações:
- O endereço do programa a ser invocado
- As contas que a instrução lê ou escreve
- Quaisquer dados extras exigidos pela instrução (por exemplo, argumentos de função)
Exemplo de transferência de SOL
O diagrama abaixo representa uma transação com uma única instrução para transferir SOL de um remetente para um destinatário.
Na Solana, as "carteiras" são contas pertencentes ao System Program. Apenas o programa proprietário pode alterar os dados de uma conta, portanto, transferir SOL requer o envio de uma transação para invocar o System Program.
Transferência de SOL
A conta do remetente deve assinar (is_signer
) a transação para permitir que o
System Program deduza seu saldo em lamports. As contas do remetente e do
destinatário devem ser graváveis (is_writable
) já que seus saldos em lamports
serão alterados.
Após o envio da transação, o System Program processa a instrução de transferência. O System Program então atualiza os saldos em lamports de ambas as contas do remetente e do destinatário.
Processo de transferência de SOL
Os exemplos abaixo mostram como enviar uma transação que transfere SOL de uma conta para outra. Veja o código-fonte da instrução de transferência do System Program aqui.
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);
As bibliotecas de cliente frequentemente abstraem os detalhes para construir instruções de programa. Se uma biblioteca não estiver disponível, você pode construir a instrução manualmente. Isso requer que você conheça os detalhes de implementação da instrução.
Os exemplos abaixo mostram como construir manualmente a instrução de
transferência. A aba Expanded Instruction
é funcionalmente equivalente à aba
Instruction
.
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
Nas seções abaixo, vamos percorrer os detalhes das transações e instruções.
Instruções
Uma instrução na Solana programa pode ser entendida como uma função pública que pode ser chamada por qualquer pessoa usando a rede Solana.
Você pode pensar em um programa Solana como um servidor web hospedado na rede
Solana, onde cada instrução é como um endpoint de API pública que os usuários
podem chamar para realizar ações específicas. Invocar uma instrução é semelhante
a enviar uma solicitação POST
para um endpoint de API, permitindo que os
usuários executem a lógica de negócios do programa.
Para chamar uma instrução de programa na Solana, você precisa construir uma
Instruction
com três informações:
- Program ID: O endereço do programa com a lógica de negócios para a instrução sendo invocada.
- Accounts: A lista de todas as contas que a instrução lê ou escreve.
- Instruction Data: Um array de bytes especificando qual instrução invocar no programa e quaisquer argumentos necessários para a instrução.
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>,}
Instrução de Transação
AccountMeta
Ao criar uma Instruction
, você deve fornecer cada conta necessária como um
AccountMeta
.
O AccountMeta
especifica o seguinte:
- pubkey: O endereço da conta
- is_signer: Se a conta deve assinar a transação
- is_writable: Se a instrução modifica os dados da conta
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,}
Ao especificar antecipadamente quais contas uma instrução lê ou escreve, transações que não modificam as mesmas contas podem ser executadas em paralelo.
Para saber quais contas uma instrução requer, incluindo quais devem ser graváveis, somente leitura ou assinar a transação, você deve consultar a implementação da instrução conforme definida pelo programa.
Na prática, geralmente você não precisa construir um Instruction
manualmente.
A maioria dos desenvolvedores de programas fornecem bibliotecas cliente com
funções auxiliares que criam as instruções para você.
AccountMeta
Exemplo de estrutura de instrução
Execute os exemplos abaixo para ver a estrutura de uma instrução de transferência 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));
Os exemplos a seguir mostram a saída dos trechos de código anteriores. O formato exato difere dependendo do SDK, mas toda instrução Solana requer as seguintes informações:
- ID do programa: O endereço do programa que executará a instrução.
- Contas: Uma lista de contas exigidas pela instrução. Para cada conta, a instrução deve especificar seu endereço, se ela deve assinar a transação e se será gravada.
- Dados: Um buffer de bytes que informa ao programa qual instrução executar e inclui quaisquer argumentos exigidos pela instrução.
{"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}}
Transações
Depois de criar as instruções que deseja invocar, o próximo passo é criar uma
Transaction
e adicionar as instruções à transação. Uma
transação
Solana é composta por:
- Assinaturas: Um array de
assinaturas
de todas as contas necessárias como signatárias para as instruções na
transação. Uma assinatura é criada assinando a transação
Message
com a chave privada da conta. - Mensagem: A mensagem da transação inclui a lista de instruções a serem processadas atomicamente.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Formato da Transação
A estrutura de uma mensagem de transação consiste em:
- Cabeçalho da Mensagem: Especifica o número de contas signatárias e somente leitura.
- Endereços de Contas: Um array de endereços de contas necessários pelas instruções na transação.
- Blockhash Recente: Atua como um timestamp para a transação.
- Instruções: Um array de instruções a serem executadas.
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>,}
Tamanho da Transação
As transações da Solana têm um limite de tamanho de 1232 bytes. Este limite vem do tamanho da Unidade Máxima de Transmissão (MTU) IPv6 de 1280 bytes, menos 48 bytes para cabeçalhos de rede (40 bytes IPv6 + 8 bytes de cabeçalho).
O tamanho total de uma transação (assinaturas e mensagem) deve permanecer abaixo deste limite e inclui:
- Assinaturas: 64 bytes cada
- Mensagem: Cabeçalho (3 bytes), chaves de conta (32 bytes cada), blockhash recente (32 bytes) e instruções
Formato da Transação
Cabeçalho da Mensagem
O cabeçalho da mensagem especifica as permissões para a conta na transação. Ele funciona em combinação com os endereços de contas ordenados rigorosamente para determinar quais contas são signatárias e quais são graváveis.
- O número de assinaturas necessárias para todas as instruções na transação.
- O número de contas assinadas que são somente leitura.
- O número de contas não assinadas que são somente leitura.
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,}
Cabeçalho da Mensagem
Formato de Array Compacto
Um array compacto em uma mensagem de transação é um array serializado no seguinte formato:
- O comprimento do array (codificado como compact-u16)
- Os itens do array listados um após o outro
Formato de array compacto
Este formato é usado para codificar os comprimentos dos arrays de Endereços de Conta e Instruções nas mensagens de transação.
Array de Endereços de Conta
Uma mensagem de transação contém uma única lista de todos os endereços de conta exigidos por suas instruções. O array começa com um número compact-u16 indicando quantos endereços ele contém.
Para economizar espaço, a transação não armazena permissões para cada conta
individualmente. Em vez disso, ela depende de uma combinação do MessageHeader
e de uma ordenação estrita dos endereços de conta para determinar as permissões.
Os endereços são sempre ordenados da seguinte maneira:
- Contas que são graváveis e signatárias
- Contas que são somente leitura e signatárias
- Contas que são graváveis e não signatárias
- Contas que são somente leitura e não signatárias
O MessageHeader
fornece os valores usados para determinar o número de contas
para cada grupo de permissão.
Array compacto de endereços de conta
Blockhash Recente
Cada transação requer um blockhash recente que serve para dois propósitos:
- Atua como um timestamp para quando a transação foi criada
- Previne transações duplicadas
Um blockhash expira após 150 blocos (aproximadamente 1 minuto assumindo tempos de bloco de 400ms), após o qual a transação é considerada expirada e não pode ser processada.
Você pode usar o método RPC
getLatestBlockhash
para obter o blockhash
atual e a altura do último bloco no qual o blockhash será válido.
Array de Instruções
Uma mensagem de transação contém um array de instruções no tipo CompiledInstruction. As instruções são convertidas para este tipo quando adicionadas a uma transação.
Assim como o array de endereços de contas na mensagem, ele começa com um compact-u16 de comprimento seguido pelos dados da instrução. Cada instrução contém:
- Índice do ID do Programa: Um índice que aponta para o endereço do programa no array de endereços de contas. Isso especifica o programa que processa a instrução.
- Índices de Contas: Um array de índices que apontam para os endereços de contas necessários para esta instrução.
- Instruction Data: Um array de bytes que especifica qual instrução invocar no programa e quaisquer dados adicionais necessários pela instrução (ex: argumentos de função).
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 Instruções
Exemplo de Estrutura de Transação
Execute os exemplos abaixo para ver a estrutura de uma transação com uma única instrução de transferência 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));
Os exemplos a seguir mostram a saída da mensagem de transação dos snippets de código anteriores. O formato exato difere dependendo do SDK, mas inclui as mesmas informações.
{"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}}]}
Após enviar uma transação, você pode recuperar seus detalhes usando o método RPC getTransaction. A resposta terá uma estrutura semelhante ao snippet a seguir. Alternativamente, você pode inspecionar a transação usando o Solana Explorer.
Uma "assinatura de transação" identifica exclusivamente uma transação na Solana. Você usa esta assinatura para procurar os detalhes da transação na rede. A assinatura da transação é simplesmente a primeira assinatura na transação. Observe que a primeira assinatura também é a assinatura do pagador da taxa da transação.
{"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?