NOVO: Helius adquire a Light Protocol
Como fazer transações serem confirmadas na Solana
Blog/Desenvolvimento

Como fazer transações serem confirmadas na Solana

Engenheiro de Experiência do DesenvolvedorAnam Ansari no XAnam Ansari no LinkedIn
18 min de leitura

A Solana tem registrado um volume sem precedentes ultimamente, causando uma alta taxa de transações com falha ou descartadas.

As transações por segundo (TPS) da Solana estão em torno de mais de 1.000 transações sem voto. O Quinn (implementação em Rust da camada de rede — QUIC) tem limitações para lidar de maneira eficaz com spam em cenários de alta demanda, o que pode obrigar os líderes de bloco a descartar conexões seletivamente. Entre todas as transações com falha, aproximadamente 8% foram iniciadas por usuários reais, enquanto as demais eram transações arbitrárias de bots.

Entender como as transações são enviadas e processadas na Solana é essencial para lidar com falhas. Este artigo analisa as possíveis causas de falha nas transações e recomenda práticas para aumentar o throughput. O artigo pressupõe um conhecimento básico do modelo de programação da Solana, além da criação e do envio de transações.

Transações

A execução de um programa começa com uma transação enviada ao cluster. Uma transação contém:

  • Um array de todas as contas que pretende ler ou gravar
  • Uma ou mais instruções (ou seja, a menor unidade de execução)
  • Um blockhash recente
  • Uma ou mais assinaturas

O runtime processa cada instrução contida na transação em ordem e de forma atômica. Se qualquer parte de uma instrução falhar, toda a transação falhará.

O que é um blockhash?

Um "blockhash" é o hash mais recente de Proof of History (PoH) de um slot. Como a Solana usa a PoH como um relógio confiável, o blockhash recente de uma transação pode ser considerado um timestamp. O blockhash evita duplicações e define a vida útil das transações. Se uma transação tiver um blockhash antigo demais, ela será rejeitada. A idade máxima de um blockhash é de 150 blocos, ou cerca de ~1 minuto e 19 segundos.

Como as transações são enviadas?

A Solana é mantida por um grupo de validadores que validam as transações adicionadas ao ledger. Um validador líder é escolhido nesse grupo para adicionar as entradas ao ledger. Uma entrada no ledger pode ser um tick ou uma entrada de transação. O ledger mantém uma lista de entradas com transações assinadas por clientes. Conceitualmente, o ledger remonta ao bloco gênese. Porém, o ledger real de um validador pode conter apenas blocos mais recentes para reduzir o armazenamento, pois, por definição, os blocos mais antigos não são necessários para validar blocos futuros.

O validador líder só pode produzir um bloco por slot, e o blockhash é um identificador exclusivo usado para identificar cada bloco. Ele é um hash de todas as entradas de um bloco, incluindo o hash do bloco anterior. A programação de líderes é determinada antes de cada época, geralmente cerca de dois dias antes, para definir qual validador atuará como líder em cada momento. Quando uma transação é iniciada, ela é encaminhada ao validador líder atual e ao próximo.

As transações podem ser enviadas ao líder por meio de: 

  1. Servidor RPC: as transações podem ser enviadas por um provedor de RPC pelo método JSON-RPC sendTransaction. O nó RPC receptor tentará enviá-la como um pacote UDP ao líder atual e ao próximo a cada dois segundos, até que a transação seja finalizada ou seu blockhash expire (após 150 blocos ou cerca de ~1 minuto e 19 segundos). Até lá, não há registro da transação além do que o cliente e os nós RPC de retransmissão conhecem. 
  2. Cliente TPU: o cliente TPU simplesmente envia a transação. O software cliente precisa cuidar da retransmissão e do encaminhamento ao líder.

Para usar o método sendTransaction, você precisa passar o objeto da transação codificado como uma string. Outros parâmetros opcionais incluem:

  1. encoding: a codificação usada para os dados da transação é base58 ou base64. 
  2. skipPreflight: as verificações de preflight incluem validar as assinaturas da transação e simulá-la no slot do banco especificado pelo commitment de preflight. Se a verificação de preflight falhar, um erro será retornado. A configuração padrão desse recurso é false, o que significa que as verificações de preflight não são ignoradas.
  3. preflightCommitment: especifica o nível de commitment usado durante as verificações de preflight. Por padrão, o nível de commitment é definido como finalized, mas pode ser alterado com uma string. Recomenda-se especificar o mesmo nível de commitment e de preflight para evitar comportamentos confusos.
  4. maxRetries: o parâmetro maxRetries determina o número máximo de vezes que o nó RPC deve tentar reenviar a transação ao líder. Se esse parâmetro não for fornecido, o nó RPC tentará reenviar a transação até que ela seja finalizada ou que o blockhash expire. 
  5. minContextSlot: o parâmetro minContextSlot especifica o slot mínimo para realizar as verificações de preflight da transação.

Como as transações são processadas?

A Unidade de Processamento de Transações (TPU) do validador recebe a transação, verifica a assinatura, executa a transação e a compartilha com outros validadores da rede.

A TPU processa transações em cinco fases distintas:

Fetch Stage

O Fetch Stage é responsável por receber transações. Ele categoriza as transações recebidas de acordo com três portas:

  • tpu: processa transações comuns, como transferências de tokens, emissão de NFT e instruções de programas
  • tpu_vote: concentra-se exclusivamente em transações de voto
  • tpu_forwards: se o líder atual não conseguir processar todas as transações, ele encaminha os pacotes não processados ao próximo líder

Os pacotes são agrupados em lotes de 128 e encaminhados ao SigVerify Stage.

SigVerify Stage

O SigVerify Stage verifica as assinaturas dos pacotes e os elimina se a verificação falhar. Votos e pacotes comuns passam por dois pipelines separados. Do ponto de vista do software, os pacotes recebidos contêm alguns metadados, mas ainda não está claro se são transações.

Se houver uma GPU instalada, ela será usada para verificar assinaturas. Além disso, há uma lógica para lidar com o excesso de pacotes em períodos de maior tráfego, que usa endereços IP para descartar pacotes.

Banking Stage

Essa etapa é responsável por filtrar e processar transações. Atualmente, ela consiste em seis threads de trabalho independentes: duas de votação e quatro sem votação. As transações comuns são adicionadas às threads sem votação. Cada thread tem um buffer local capaz de armazenar até 64 transações sem conflito em uma fila de prioridade. Essas transações são processadas em paralelo graças ao Sealevel. Consulte este vídeo para saber mais sobre o Banking Stage.

Serviço de Proof of History

O módulo PoH Service registra a passagem dos ticks. Cada tick representa uma unidade de tempo, e há 64 ticks em um slot. O hash é gerado repetidamente até que um registro seja recebido do Banking Stage:

next_hash = hash(prev_hash, hash(transaction_ids))

Esses registros são então convertidos em entradas e transmitidos à rede pelo Broadcast Stage.

Broadcast Stage

As entradas do serviço PoH são convertidas em shreds, que representam a menor unidade de um bloco, e enviadas ao restante da rede por meio de uma técnica de propagação de blocos chamada Turbine. Em termos gerais, o Turbine divide um bloco em partes menores e as distribui por uma estrutura hierárquica de nós. Os nós não precisam estar em contato com todos os outros nós. Eles só precisam se comunicar com alguns nós selecionados. Consulte este artigo para saber mais sobre o Turbine e como ele funciona. 

Por que as transações falham?

Excluindo falhas causadas por instruções incorretas ou erros de programas personalizados, estes são os possíveis motivos para falhas em transações:

Descartes na rede

A camada de rede pode descartar uma transação antes mesmo de um líder processá-la. A perda de pacotes UDP é o motivo mais simples para isso. Outro motivo está relacionado ao fetch stage da TPU. Quando a rede está sob alta carga, os validadores podem ficar sobrecarregados com o número de transações que precisam processar. Os validadores podem encaminhar as transações excedentes à porta tpu_forward do próximo validador. No entanto, há um limite para a quantidade de dados que pode ser encaminhada, e cada encaminhamento é limitado a um salto entre validadores. Isso significa que as transações recebidas na porta tpu_forwards não são encaminhadas a outros validadores. Se a fila de retransmissão pendente ultrapassar 10.000 transações, as novas transações enviadas serão descartadas.

Blockhash desatualizado/incorreto 

Toda transação tem um "blockhash recente" que funciona como timestamp para o relógio de Proof of History (PoH). Esse blockhash ajuda os validadores a evitar o processamento da mesma transação duas vezes e registra quando e em qual ordem as transações foram processadas. Durante o processamento, o validador rejeitará uma transação com blockhash inválido.

O blockhash expira

O blockhash de uma transação expira quando deixa de ser considerado "recente". Para processar uma transação, os validadores da Solana procuram em um bloco o número do slot correspondente ao blockhash. Se o validador não encontrar um número de slot para o blockhash, ou se o número do slot encontrado estiver mais de 151 slots abaixo do número do slot do bloco em processamento, a transação será rejeitada. Por padrão, as transações da Solana expiram se não forem incluídas em um bloco dentro de determinado período (cerca de ~1 minuto e 19 segundos).

Nós RPC atrasados

Quando você envia uma transação por um RPC, é possível que o pool de RPC esteja à frente do restante da rede. Isso pode causar problemas quando os nós do pool precisam trabalhar em conjunto. Por exemplo, se o recentBlockhash de uma transação for consultado na parte adiantada do pool e enviado à parte atrasada, os nós não reconhecerão o blockhash adiantado e rejeitarão a transação. Você pode detectar isso durante o envio da transação ao ativar as verificações de preflight em sendTransaction.

Forks temporários da rede

Forks temporários da rede também podem resultar em transações descartadas. Se um validador demorar para reproduzir seus blocos no Banking Stage, ele poderá criar um fork minoritário. Quando um cliente cria uma transação, ela pode fazer referência a um recentBlockhash que só existe no fork minoritário. Depois que a transação é enviada, o cluster pode abandonar seu fork minoritário antes que ela seja processada. Nesse cenário, a transação é descartada porque o blockhash não é encontrado.

Como faço para confirmar transações?

Para diagnosticar problemas de confirmação, é importante entender a expiração das transações. Siga estas etapas para aumentar as chances de sucesso:

Resumo

  • Busque o blockhash mais recente com o commitment “confirmed” ou “finalized”
  • Defina skipPreflight como true
  • Otimize a quantidade solicitada de unidades de computação
  • Adicione e calcule taxas de prioridade dinamicamente
  • Defina maxRetries como 0 e adicione uma lógica personalizada de novas tentativas para o envio de transações.
  • Explore conexões com stake
  • Se a transação não for sensível ao tempo, use nonces duráveis

Blockhash

As transações têm um período limitado para serem processadas pelo validador. Se o blockhash associado à transação expirar antes do processamento, a transação será cancelada. Para garantir que sua transação seja processada, é importante enviá-la com um blockhash recente. Se o blockhash expirar antes que o validador processe a transação, você poderá tentar novamente com um novo blockhash. Isso pode ser feito de duas formas: 

1. Defina um novo nível de commitment:

O método recomendado da API RPC para buscar o blockhash mais recente é getLatestBlockhash. Por padrão, esse método usa o nível de commitment finalized para retornar o blockhash do bloco finalizado mais recentemente. Esse nível de commitment indica que o bloco tem pelo menos 31 blocos confirmados adicionados acima dele. Isso elimina o risco de usar um blockhash pertencente a um fork descartado. No entanto, geralmente há uma diferença de pelo menos 32 slots entre os blocos confirmados e finalizados mais recentes. Essa escolha reduz em cerca de 13 segundos o tempo até a expiração das transações, podendo ser ainda maior em condições instáveis do cluster. 

Você pode substituir o commitment do blockhash definindo o parâmetro de commitment como outro nível. O nível de commitment confirmed é recomendado para solicitações RPC, pois normalmente está apenas alguns slots atrás do nível processed e tem baixa probabilidade de pertencer a um fork descartado. Embora o nível de commitment processed busque o blockhash mais recente em comparação com os demais níveis, ele não é recomendado, pois cerca de 5% dos blocos não são finalizados pelo cluster devido a forks no protocolo da Solana. Se a transação usar um blockhash pertencente a um fork descartado, ele não será considerado recente por nenhum bloco da blockchain finalizada.

2. Consulte novos blockhashes recentes com frequência:

Adicione um script para buscar e armazenar com frequência (a cada 60 segundos) o blockhash mais recente usando o método getLatestBlockhash. Assim, sempre que um usuário acionar uma transação, o aplicativo terá um blockhash novo disponível. As carteiras também devem consultar novos blockhashes com frequência e substituir o blockhash recente de uma transação logo antes de assiná-la, garantindo que ele seja o mais recente possível.

Ignorar o preflight

Antes do envio de uma transação, são realizadas as seguintes verificações de preflight:

  • As assinaturas da transação são verificadas.
  • A transação é simulada no slot do banco especificado pelo commitment de preflight. Se houver falha, um erro será retornado. 

Se o bloco escolhido para a simulação for mais antigo que o bloco usado para o blockhash da transação, a simulação falhará com o temido erro “blockhash not found”. 

Se você tiver certeza de que a assinatura da transação foi verificada e de que não há outros erros, poderá ignorar a verificação de preflight. Mesmo que use o parâmetro skipPreflight, sempre defina o parâmetro preflightCommitment com o mesmo nível de commitment usado para buscar o blockhash da transação nas solicitações sendTransaction e simulateTransaction.

Unidades de computação

Quando uma transação é confirmada na rede, ela consome parte das unidades de computação (CU) disponíveis em um bloco. Atualmente, o limite total de computação de um bloco é de 48 milhões de CU. Os desenvolvedores podem especificar um orçamento de unidades de computação para suas transações. Se não definirem um orçamento, será usado o valor padrão de 200.000. Muitas transações não usam todo o orçamento de CU porque não há penalidade por solicitar um orçamento maior que o necessário. Porém, solicitar unidades de computação demais antecipadamente pode dificultar o agendamento eficiente das transações, pois o agendador não sabe quanta capacidade computacional resta em um bloco até que a transação seja executada. Para evitar isso, os desenvolvedores devem definir solicitações de CU mais precisas, alinhadas aos requisitos da transação. Consulte este guia para otimizar o orçamento de unidades de computação. Na próxima atualização v1.18 do cliente da Solana, transações que exigem menos unidades de computação receberão maior prioridade.

Otimizar o uso de unidades de computação (CU) oferece os seguintes benefícios:

  • Uma transação menor tem maior probabilidade de ser incluída em um bloco.
  • Instruções mais baratas tornam seu programa mais combinável.
  • Reduz o uso geral do bloco, permitindo incluir mais transações nele.

Implemente taxas de prioridade

As taxas de prioridade podem ser adicionadas à taxa básica da transação para que os validadores a priorizem. Essas taxas são precificadas em microlamports por unidade de computação (por exemplo, pequenas quantias de SOL). Elas são adicionadas às transações para torná-las economicamente atraentes e incentivar os nós validadores a incluí-las nos blocos da rede.

No entanto, é importante observar que há um limite para o valor que deve ser pago em taxas de prioridade. Pagar mais que a taxa habitual não aumentará a probabilidade de sucesso da transação. Portanto, recomenda-se calcular as taxas de prioridade dinamicamente para pagar o valor adequado, permanecer competitivo e evitar gastos excessivos. Essa integração é simples. Consulte a documentação oficial sobre taxas de prioridade ou use a API da Helius, disponível imediatamente.

Implemente uma lógica robusta de novas tentativas

Em caso de congestionamento da rede, implemente uma lógica personalizada em seu código para lidar com falhas de transação e tentar novamente de forma manual. Para isso, defina o parâmetro maxRetries como 0 ao usar sendTransaction para enviar uma transação. Há diferentes métodos para tentar executar transações novamente:

  • Consulte o transaction status com diferentes níveis de commitment e continue usando a mesma transação assinada até que ela seja confirmada. Use um mecanismo de recuo exponencial para evitar spam. Como alternativa, envie transações em intervalos constantes até ocorrer um timeout.
  • Armazene o lastValidBlockHeight proveniente do getLatestBlockhash method. Em seguida, consulte a altura do bloco do cluster e tente a transação novamente de forma manual depois que a altura atual ultrapassar o lastValidBlockHeight. Ao consultar pelo getLatestBlockhash, recomenda-se especificar o nível de commitment desejado. Ao definir o commitment como confirmed (votado) ou finalized (cerca de ~30 blocos após confirmed), você evita consultar um blockhash de um fork minoritário.

Staked Connections

A capacidade de largura de banda da rede de um líder é limitada. Para usá-la com eficiência, é necessário ponderar pelo stake e evitar a aceitação indiscriminada de transações por ordem de chegada, sem considerar sua origem. A Solana funciona como uma rede proof-of-stake, portanto é natural ampliar o uso da ponderação por stake para melhorar a qualidade de serviço das transações. Isso significa que um nó com 0,5% do stake pode enviar pelo menos 0,5% dos pacotes ao líder. O restante da rede — e nenhuma combinação do stake remanescente — conseguirá eliminá-los por completo. Esse mecanismo é conhecido como Qualidade de Serviço Ponderada por Stake (SWQoS).

A Helius oferece Staked Connections nos planos pagos. Para saber mais, consulte nossa documentação: Envio de transações na Solana.

Nonces duráveis

Os nonces duráveis permitem criar e assinar uma transação que pode ser enviada a qualquer momento no futuro. Eles são usados em casos como serviços de custódia, que precisam de mais tempo para produzir a assinatura de uma transação. Se sua transação não for sensível ao tempo, você poderá usar esse método para contornar a curta vida útil do recentBlockhash da transação.

Para começar a usar transações duráveis, você precisa enviar uma transação que chame instruções para criar uma conta especial de "nonce" on-chain e armazenar nela um "blockhash durável". A conta de nonce armazena o valor do nonce. Desde que a conta de nonce não tenha sido usada, você poderá criar uma transação durável seguindo estas duas regras:

  • A lista de instruções deve começar com uma instrução de sistema "advance nonce", que carrega sua conta de nonce on-chain.
  • O blockhash da transação deve ser igual ao blockhash durável armazenado na conta de nonce on-chain.

Saiba como implementar nonces duráveis pela CLI e pelo Web3.js consultando este artigo.

Abordagem da Helius para o envio de transações

A solicitação sendTransaction é roteada automaticamente para o nó RPC mais próximo. Se maxRetries não for especificado, a transação será reenviada a cada dois segundos até o blockhash expirar. Recomendamos definir maxRetries como 0 e retransmitir a transação por conta própria a cada dois segundos até que ela seja confirmada. 

Para enfrentar o congestionamento atual da rede, trabalhamos sem parar para melhorar a taxa de confirmação de transações dos nossos usuários. Reduzimos o limite de taxa da solicitação sendTransaction. Essa medida ajuda a gerenciar o congestionamento e evitar spam no validador. Consulte os limites aqui.

Além disso, roteamos tráfego de alta qualidade dos planos pagos por Staked Connections. Usamos nosso validador para essas Staked Connections. O tráfego é considerado de alta qualidade quando a taxa de prioridade total é de pelo menos 10.000 lamports (mediana do cluster).

Exigimos taxas totais acima de 10.000 lamports para garantir que forneçamos tráfego de alta qualidade aos validadores. Os validadores começaram a limitar a taxa — ou até mesmo bloquear por completo — de fontes de tráfego que enviam transações com taxas baixas.

Usar Staked Connections pode melhorar significativamente sua taxa de confirmação de transações. Consulte este artigo para saber mais sobre como definir taxas de prioridade ao criar uma transação.

O que recomendamos

Usuários iniciantes/intermediários

Recomendamos definir skipPreflight como false. As verificações de preflight incluem validar as assinaturas da transação e simulá-la no slot do banco especificado pelo commitment de preflight. Se a verificação de preflight falhar, um erro será retornado. Sem uma verificação de preflight, suas transações podem ser descartadas devido a uma configuração incorreta.

Usuários avançados

Usuários avançados que precisam da menor latência possível devem definir skipPreflight como true. No entanto, você é responsável por garantir que a transação esteja configurada corretamente. 

Conclusão

Confirmar transações com sucesso na rede Solana durante períodos de congestionamento exige uma compreensão detalhada da arquitetura da rede e dos mecanismos de processamento de transações. Ao compreender conceitos centrais, como o papel do blockhash na exclusividade e no timing das transações, o processo de envio por servidores RPC ou clientes TPU e a importância de definir os parâmetros corretos — como skipPreflight, preflightCommitment e maxRetries —, os usuários podem melhorar significativamente o desempenho das transações. Implementar um mecanismo personalizado de novas tentativas e usar Staked Connections pode ajudar a aumentar a taxa de sucesso.

Além disso, é essencial conhecer as limitações atuais da rede e os esforços contínuos da Anza para resolvê-las, como vemos no próximo lançamento da versão v1.18 do cliente. À medida que a rede evolui e escala, manter-se informado e adaptável será fundamental para interagir com ela de forma eficaz. 

Se precisar de ajuda ou suporte, fale conosco no Discord. Informe seu endereço de e-mail abaixo para não perder nenhuma novidade sobre a Solana. Quer se aprofundar? Explore os artigos mais recentes no blog da Helius e continue hoje mesmo sua jornada pela Solana.

Recursos

Assine a Helius

Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos

Imagem ampliada