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.
Transação Simplificada
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.
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.
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 geralmente 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
.
- 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);
Nas seções abaixo, vamos percorrer os detalhes das transações e instruções.
Instructions
Uma instruction em um programa Solana pode ser entendida como uma função pública que pode ser chamada por qualquer pessoa usando a rede Solana.
Invocar uma instruction de um programa requer três informações essenciais:
- Program ID: O programa com a lógica de execução para a instruction
- Accounts: Lista de contas que a instruction necessita
- Instruction Data: Array de bytes especificando a instruction a ser invocada no programa e quaisquer argumentos requeridos pela 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>,}
Transaction Instruction
AccountMeta
Cada conta requerida por uma instruction deve ser fornecida como um AccountMeta que contém:
pubkey
: Endereço da contais_signer
: Se a conta deve assinar a transaçãois_writable
: Se a instruction 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,}
AccountMeta
Ao especificar antecipadamente quais contas uma instruction lê ou escreve, transações que não modificam as mesmas contas podem ser executadas em paralelo.
Exemplo de estrutura de Instruction
Execute os exemplos abaixo para ver a estrutura de uma instruction 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 snippets de código anteriores. O formato exato difere dependendo do SDK, mas toda instruction Solana requer as seguintes informações:
- Program ID: O endereço do programa que executará a instrução.
- Accounts: Uma lista de contas necessárias para a instrução. Para cada conta, a instrução deve especificar seu endereço, se ela deve assinar a transação e se será escrita.
- Data: Um buffer de bytes que informa ao programa qual instrução executar e inclui quaisquer argumentos necessários para a 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
Uma transação da Solana consiste em:
- Assinaturas: Um array de assinaturas incluídas na transação.
- Mensagem: 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>,}
Mensagem da Transação
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 de fragmento).
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 contas (32 bytes cada), blockhash recente (32 bytes) e instruções
Formato da Transação
Cabeçalho da Mensagem
O cabeçalho da mensagem utiliza três bytes para definir privilégios de conta.
- Assinaturas necessárias
- Número de contas assinadas somente leitura
- Número de contas não assinadas 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 Instructions nas mensagens de transação.
Array de Endereços de Conta
Uma mensagem de transação contém um array de endereços de conta requeridos por suas instructions. O array começa com um número compact-u16 indicando quantos endereços ele contém. Os endereços são então ordenados por seus privilégios, conforme determinado pelo cabeçalho da mensagem.
- 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
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
- 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 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. Aqui está um
exemplo no Solana Playground.
Array de Instructions
Uma mensagem de transação contém um array de instructions no tipo CompiledInstruction. As instructions são convertidas para este tipo quando adicionadas a uma transação.
Como o array de endereços de conta na mensagem, ele começa com um comprimento compact-u16 seguido pelos dados da instruction. Cada instruction contém:
- Índice de ID do Programa: Um índice u8 que aponta para o endereço do programa no array de endereços de contas. Isso especifica o programa que processará a instrução.
- Índices de Contas: Um array de índices u8 que apontam para os endereços de contas necessários para esta instrução.
- Instruction Data: Um array de bytes especificando qual instrução invocar no programa e quaisquer dados adicionais exigidos 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}}]}
Quando você busca uma transação usando sua assinatura após enviá-la para a rede, você receberá uma resposta com a seguinte estrutura.
O campo message
contém os seguintes campos:
-
header
: Especifica privilégios de leitura/escrita e assinatura para endereços no arrayaccountKeys
-
accountKeys
: Array de todos os endereços de contas usados nas instruções da transação -
recentBlockhash
: Blockhash usado para marcar o timestamp da transação -
instructions
: Array de instruções a serem executadas. Cadaaccount
eprogramIdIndex
em uma instrução referencia o arrayaccountKeys
por índice. -
signatures
: Array incluindo assinaturas para todas as contas exigidas como signatárias pelas instruções na transação. Uma assinatura é criada assinando a mensagem da transação usando a chave privada correspondente para uma conta.
{"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?