Como uma Transação Flui
Uma transação Solana viaja do seu client pela rede até um líder produtor de blocos, executa atomicamente on-chain e ganha votos até atingir o nível de commitment escolhido. Entender esse caminho explica latência, falhas e estratégia de retry.
Receita
Cartão de receita de referência rápida - pronto para copiar.
Client build → Sign → sendTransaction → Leader receives (Gulf Stream)
→ PoH ordering → Sealevel execution → Block propagation (Turbine)
→ Validator votes (TowerBFT) → RPC confirmation polling
import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const rpcSubs = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");
// Após sendTransaction retornar signature:
const sub = await rpcSubs.signatureNotifications(signature, { commitment: "confirmed" });Quando usar:
- Depurar por que transação foi descartada vs rejeitada
- Escolher nível de commitment certo para sua UX
- Entender timing de retry relativo à expiração de blockhash
- Explicar "processed" sub-segundo vs "finalized" ~12s aos usuários
- Projetar reenvio idempotente de transação
Exemplo Prático
import {
createSolanaRpc,
createSolanaRpcSubscriptions,
createKeyPairSignerFromBytes,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
signTransactionMessageWithSigners,
getSignatureFromTransaction,
pipe,
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
import { readFileSync } from "node:fs";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const rpcSubs = createSolanaRpcSubscriptions("wss://api.devnet.solana.com");
const secret = Uint8Array.from(
JSON.parse(readFileSync(process.env.HOME + "/.config/solana/id.json", "utf8")),
);
const signer = await createKeyPairSignerFromBytes(secret);
// 1. Buscar blockhash recente (lifetime da transação)
const { value: blockhash } = await rpc.getLatestBlockhash().send();
// 2. Montar e assinar
const message = pipe(
createTransactionMessage({ version: 0 }),
(m) => setTransactionMessageFeePayer(signer.address, m),
(m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
(m) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({
source: signer.address,
destination: signer.address, // self-transfer demo
amount: 1n,
}),
m,
),
);
const signed = await signTransactionMessageWithSigners(message, [signer]);
const signature = getSignatureFromTransaction(signed);
// 3. Enviar
await rpc.sendTransaction(signed, { encoding: "base64" }).send();
console.log("Submitted:", signature);
// 4. Aguardar commitment confirmed
const notifications = await rpcSubs.signatureNotifications(signature, {
commitment: "confirmed",
});
for await (const note of notifications) {
if (note.value.err) {
console.error("Failed on-chain:", note.value.err);
} else {
console.log("Confirmed at slot:", note.context.slot);
}
break;
}O que isso demonstra:
- Blockhash vincula transação a estado recente do ledger (~60-90 segundos de validade)
sendTransactiontransmite ao RPC, que encaminha via Gulf Stream- WebSocket
signatureNotificationsé mais eficiente que pollinggetSignatureStatuses confirmedsignifica supermaioria votou no bloco;finalizedé mais forte
Aprofundamento
Como Funciona
- Build - client monta instruções, contas e blockhash recente
- Sign - signatários exigidos produzem assinaturas Ed25519
- Submit - nó RPC recebe e encaminha ao líder atual/próximo
- Schedule - líder ordena transações usando ticks de Proof of History
- Execute - Sealevel executa instruções em paralelo onde locks de conta permitem
- Commit - mudanças de transação são escritas no accounts database
- Vote - validadores votam no bloco; TowerBFT acumula lockouts
- Confirm - RPC reporta status no nível de commitment solicitado
Níveis de Commitment
| Nível | Latência | Segurança | Caso de Uso |
|---|---|---|---|
processed | ~400 ms | Pode ser descartada/reordenada | Apenas dicas de UI |
confirmed | ~1-2 s | Supermaioria votou | Maioria das ações de dapp |
finalized | ~12-13 s | Enraizada, sem rollback | Liquidações de alto valor |
Modos de Falha
- Dropped - nunca entrou em bloco (skip de líder, priority fee baixa, blockhash expirado)
- Rejected - entrou mas programa retornou erro (fundos insuficientes, violação de constraint)
- Simulation failure - preflight RPC capturou erro antes do envio
Erros Comuns
- Tratar
processedcomo final - forks podem reordenar neste nível. Correção: aguardeconfirmedoufinalizedantes de UX irreversível. - Não atualizar blockhash no retry - transações expiradas são descartadas silenciosamente. Correção: busque novo blockhash e re-assine em cada retry.
- Ignorar erros de preflight -
skipPreflight: trueoculta falhas de simulação. Correção: simule primeiro em desenvolvimento; decodifique erros antes de retry. - Polling em loop apertado - martela RPC e ainda perde confirmações rápidas. Correção: use subscriptions WebSocket de signature.
- Reenvio da mesma signature - válido e idempotente, mas inútil se blockhash expirou. Correção: reenvie mesma signature só dentro da janela de blockhash.
- Skip de líder durante congestionamento - transações enfileiram mais. Correção: adicione priority fees via Compute Budget Program.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Subscribe WebSocket signature | UX de confirmação em tempo real | Scripts CLI pontuais |
Polling getSignatureStatuses | Scripts simples | Apps de alto throughput |
skipPreflight: true | Txs conhecidamente boas, latência crítica | Desenvolvimento e depuração |
| Durable nonce | Sessões de assinatura longas | Transações normais de usuário |
FAQs
O que acontece imediatamente após sendTransaction?
O RPC encaminha sua transação aos líderes via Gulf Stream. Ainda não está confirmada - apenas enviada.
Quanto tempo blockhash é válido?
Aproximadamente 60-90 segundos (~150 slots). Após expiração, transação é rejeitada mesmo se reenviada.
Qual diferença entre dropped e rejected?
Dropped: nunca executada em bloco. Rejected: executada mas programa retornou erro - está on-chain com status de falha.
Por que finalized leva ~12 segundos?
TowerBFT exige votos suficientes de validadores em múltiplos slots para enraizar bloco. Isso leva ~32 slots.
Posso acelerar confirmação?
Priority fees aumentam prioridade de agendamento. Bundles Jito ajudam em períodos com MEV pesado. Não dá para pular o processo de votação.
O que é simulação de preflight?
O RPC executa sua transação contra estado atual antes de transmitir. Captura a maioria dos erros cedo.
sendTransaction é síncrono?
Retorna signature imediatamente. Confirmação é assíncrona - faça poll ou subscribe separadamente.
O que é Gulf Stream?
Protocolo que encaminha transações diretamente aos próximos líderes de bloco em vez de usar mempool tradicional.
Todas instruções em transação executam atomicamente?
Sim. Se qualquer instrução falha, toda transação é revertida (a menos que programa capture e trate erros de CPI).
Transação pode ter sucesso parcial?
Não. Todas instruções têm sucesso ou nenhuma mudança de estado é commitada.
Qual commitment UI de pagamento deve usar?
confirmed para maioria dos pagamentos. finalized para ações de alto valor ou irreversíveis.
Como rastrear transação no explorer?
Use https://explorer.solana.com/tx/<SIGNATURE>?cluster=devnet e inspecione logs, CU e breakdown de instruções.
Relacionado
- Noções Básicas de Solana - exemplo ponta a ponta
- Slots, Blocos e Epochs - contexto de temporização
- Keypairs e Endereços - etapa de assinatura
Versões da stack: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0 e LiteSVM 0.6.x.