Resumo
Uma transação tem assinaturas + uma mensagem. A mensagem contém um cabeçalho, endereços de conta, blockhash recente e instruções compiladas. Tamanho máximo serializado: 1.232 bytes.
Uma
Transaction
tem dois campos de nível superior:
signatures: Um array de assinaturasmessage: Informações da transação, incluindo a lista de instruções a serem processadas
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Diagrama mostrando as duas partes de uma transação
O tamanho total serializado de uma transação não deve exceder
PACKET_DATA_SIZE
(1.232 bytes). Este limite equivale a 1.280 bytes (o MTU mínimo IPv6) menos 48
bytes para cabeçalhos de rede (40 bytes IPv6 + 8 bytes de cabeçalho de
fragmento). Os 1.232 bytes incluem tanto o array signatures
quanto a struct message.
Diagrama mostrando o formato de transação e limites de tamanho
Assinaturas
O campo signatures é um array codificado de forma compacta de valores
Signature.
Cada Signature é uma assinatura Ed25519 de 64 bytes da Message serializada,
assinada com a chave privada da conta do signatário. Uma assinatura é necessária
para cada conta signatária referenciada pelas instruções
da transação.
A primeira assinatura no array pertence ao pagador de taxa, a conta que paga a taxa base da transação e a taxa de priorização base fee and prioritization fee. Esta primeira assinatura também serve como o ID da transação, usado para procurar a transação na rede. O ID da transação é comumente referido como a assinatura da transação.
Requisitos do pagador de taxas:
- Deve ser a primeira conta na mensagem (índice 0) e um signatário.
- Deve ser uma conta pertencente ao System Program ou uma conta nonce (validada
por
validate_fee_payer). - Deve possuir lamports suficientes para cobrir
rent_exempt_minimum + total_fee; caso contrário, a transação falha comInsufficientFundsForFee.
Mensagem
O campo message é uma struct
Message
que contém a carga útil da transação:
header: O cabeçalho da mensagemaccount_keys: Um array de endereços de contas exigidos pelas instruções da transaçãorecent_blockhash: Um blockhash que atua como timestamp para a transaçãoinstructions: Um array de instruções
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>,}
Cabeçalho
O campo header é uma struct
MessageHeader
com três campos u8 que particionam o array account_keys em grupos de
permissões:
num_required_signatures: Número total de assinaturas exigidas pela transação.num_readonly_signed_accounts: Número de contas assinadas que são somente leitura.num_readonly_unsigned_accounts: 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,}
Diagrama mostrando as três partes do cabeçalho da mensagem
Endereços de contas
O campo
account_keys
é um array compactado de chaves públicas. Cada entrada identifica uma conta
usada por pelo menos uma das instruções da transação. O array deve incluir todas
as contas e deve seguir esta ordenação estrita:
- Signatário + Gravável
- Signatário + Somente leitura
- Não signatário + Gravável
- Não signatário + Somente leitura
Esta ordenação estrita permite que o array account_keys seja combinado com
as três contagens no header da mensagem para determinar as
permissões de cada conta sem armazenar flags de metadados por conta. As
contagens do cabeçalho particionam o array nos quatro grupos de permissões
listados acima.
Diagrama mostrando a ordem do array de endereços de contas
Blockhash recente
O campo recent_blockhash é um hash de 32 bytes que serve dois propósitos:
- Timestamp: prova que a transação foi criada recentemente.
- Deduplicação: impede que a mesma transação seja processada duas vezes.
Um blockhash expira após 150 slots. Se o blockhash não for mais válido quando a
transação chegar, ela é rejeitada com BlockhashNotFound, a menos que seja
uma transação de nonce durável válida.
O método RPC getLatestBlockhash permite
obter o blockhash atual e a última altura de bloco na qual o blockhash será
válido.
Instruções
O campo
instructions
é um array compacto codificado de estruturas
CompiledInstruction.
Cada CompiledInstruction referencia contas por índice no array
account_keys em vez de por chave pública completa. Contém:
program_id_index: Índice noaccount_keysidentificando o programa a invocar.accounts: Array de índices noaccount_keysespecificando as contas a passar para o programa.data: Array de bytes contendo o discriminador de instrução e 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 instruções
Formato binário de transação
As transações são serializadas usando um esquema de codificação compacta. Todos os arrays de comprimento variável (assinaturas, chaves de conta, instruções) são prefixados com uma codificação de comprimento compact-u16. Este formato usa 1 byte para valores 0-127 e 2-3 bytes para valores maiores.
Layout de transação legada (na rede):
| Campo | Tamanho | Descrição |
|---|---|---|
num_signatures | 1-3 bytes (compact-u16) | Número de assinaturas |
signatures | num_signatures x 64 bytes | Assinaturas Ed25519 |
num_required_signatures | 1 byte | Campo 1 do MessageHeader |
num_readonly_signed | 1 byte | Campo 2 do MessageHeader |
num_readonly_unsigned | 1 byte | Campo 3 do MessageHeader |
num_account_keys | 1-3 bytes (compact-u16) | Número de chaves de conta estáticas |
account_keys | num_account_keys x 32 bytes | Chaves públicas |
recent_blockhash | 32 bytes | Blockhash |
num_instructions | 1-3 bytes (compact-u16) | Número de instruções |
instructions | variável | Array de instruções compiladas |
Cada instrução compilada é serializada como:
| Campo | Tamanho | Descrição |
|---|---|---|
program_id_index | 1 byte | Índice nas chaves de conta |
num_accounts | 1-3 bytes (compact-u16) | Número de índices de conta |
account_indices | num_accounts x 1 byte | Índices de chaves de conta |
data_len | 1-3 bytes (compact-u16) | Comprimento dos dados da instrução |
data | data_len bytes | Dados opacos da instrução |
Cálculo de tamanho
Dado PACKET_DATA_SIZE = 1.232 bytes, o espaço disponível pode ser
calculado:
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
Exemplo: transação de transferência de SOL
O diagrama abaixo mostra como transações e instruções funcionam em conjunto para permitir que os utilizadores interajam com a rede. Neste exemplo, SOL é transferido de uma conta para outra.
Os metadados da conta do remetente indicam que esta deve assinar a transação. Isto permite que o System Program deduza lamports. Tanto a conta do remetente como a do destinatário devem ser graváveis, para que o seu saldo de lamports possa ser alterado. Para executar esta instrução, a carteira do remetente envia a transação contendo a sua assinatura e a mensagem contendo a instrução de transferência de SOL.
Diagrama de transferência de SOL
Após o envio da transação, o System Program processa a instrução de transferência e atualiza o saldo de lamports de ambas as contas.
Diagrama do processo de transferência de SOL
O exemplo abaixo mostra o código relevante para os diagramas acima. Consulte a
função transfer
do 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);
O exemplo seguinte mostra a estrutura de uma transação que contém 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));
O código abaixo mostra a saída dos trechos de código anteriores. O formato difere entre SDKs, mas note que cada instrução contém as mesmas informações necessárias.
{"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}}]}
Obter detalhes da transação
Após o envio, obtenha os detalhes da transação usando a assinatura da transação e o método RPC getTransaction.
Também pode encontrar a transação usando o 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?