NOVO: Helius adquire a Light Protocol
Taxas de prioridade: entenda a mecânica das taxas de transação da Solana
Blog/Fundamentos

Taxas de prioridade: entenda a mecânica das taxas de transação da Solana

Developer Experience Engineer0xIchigo no X0xIchigo no LinkedIn0xIchigo no GitHub
10 min de leitura

Sobre o que é este artigo?

A Solana é rápida. Ainda assim, mesmo na blockchain mais rápida disponível, os usuários querem um processamento otimizado para transações importantes. As taxas de prioridade são uma forma de garantir que a transação de um usuário seja colocada no início da fila de execução. São taxas adicionais e opcionais que o usuário pode adicionar à transação.

Este artigo apresenta brevemente as nuances do processamento de transações na Solana. Ele aborda as transações, seu ciclo de vida e como funcionam as taxas de transação. Em seguida, explora as taxas de prioridade, como implementá-las de forma programática e as práticas recomendadas.

Transações e seu ciclo de vida

As transações são usadas para invocar programas da Solana e efetuar alterações de estado. Elas são conjuntos de instruções (ou seja, diretivas para uma única invocação de programa) que informam ao validador quais ações executar, em quais contas e se elas têm as permissões necessárias.

O ciclo de vida geral de uma transação na Solana é o seguinte:

  • O usuário tem um objetivo claro para uma ação que deseja executar. Por exemplo, Alice quer enviar 10 SOL para Bob
  • O usuário gera uma transação para a ação desejada. Por exemplo, Alice cria uma transação com uma instrução para transferir 10 SOL da conta dela para a de Bob. Alice também inclui um blockhash recente e assina a transação usando sua chave privada
  • O usuário envia a transação para a rede. Em seguida, recebe informações sobre a inclusão bem-sucedida ou não da transação. Por exemplo, Alice envia a transação com o status de compromisso confirmado. Quando a transação é confirmada, ela recebe uma assinatura de transação. Alice pode usar essa assinatura em um explorador de blocos, como o Orb, para verificar que enviou 10 SOL para Bob com sucesso. Foram debitados 10 SOL da conta dela e creditados 10 SOL na conta de Bob

Quando os usuários enviam uma transação assinada para a rede, eles usam um provedor de RPC como a Helius. Os RPCs da Helius recebem a transação e verificam a programação atual de líderes. Na Solana, apenas validadores específicos são responsáveis por adicionar entradas ao ledger em determinados momentos. O líder é responsável por produzir um bloco para seu slot atual e recebe quatro slots consecutivos. A transação assinada é enviada ao líder atual e aos dois líderes seguintes.

O líder atual valida a transação assinada e executa outras etapas de pré-processamento antes de agendá-la para execução. Ao contrário de outras L1s, como a Ethereum, a Solana não tem uma fila global de transações. A maioria dos validadores usa a implementação do agendador fornecida pela Solana Labs. No entanto, os validadores que executam o cliente validador da Jito usam um pseudomempool (ou seja, o MempoolStream) para ordenar as transações. O agendador padrão usa várias threads, cada uma mantendo uma fila de transações à espera de execução. As transações são ordenadas em blocos pela combinação do método primeiro a entrar, primeiro a sair (FIFO) com as taxas de prioridade. É importante observar que essa ordenação é inerentemente não determinística, pois as transações são atribuídas às threads de execução de forma parcialmente aleatória.

Quando uma transação é executada, ela é propagada pelo Turbine, e suas taxas são pagas de acordo.

Como funcionam as taxas de transação na Solana

As taxas de transação são pequenos valores pagos para processar transações na Solana. O líder atual processa as transações enviadas pela rede para produzir entradas no ledger. Quando a transação é confirmada como parte do estado, uma taxa é paga para sustentar o modelo econômico da Solana. As taxas de transação trazem benefícios fundamentais para a Solana. Elas:

  • Remuneram os validadores
  • Reduzem o uso de espaço na rede ao introduzir um custo em tempo real para as transações
  • Proporcionam estabilidade econômica de longo prazo à rede por meio de uma taxa mínima capturada pelo protocolo

A Solana depende de recompensas inflacionárias baseadas no protocolo para proteger a rede no curto prazo. Para isso, a rede tem uma taxa de inflação global programada para recompensar os validadores. No longo prazo, a Solana depende das taxas de transação para manter a segurança. Uma parcela fixa (inicialmente definida em 50%) de cada taxa de transação é queimada, enquanto o restante é enviado ao líder atual. A Solana queima taxas para fortalecer o valor do SOL e, ao mesmo tempo, desestimular validadores mal-intencionados a censurar transações.

As taxas de transação são calculadas com base em uma taxa-base definida estaticamente por assinatura e nos recursos computacionais usados durante a transação, medidos em unidades de computação (CU). Essa taxa-base pode variar de 50% a 1000% da quantidade-alvo de lamports por assinatura. O valor-alvo padrão por assinatura está atualmente definido como 10.000. Cada transação recebe um orçamento máximo de CU, conhecido como orçamento de computação. Ultrapassar esse orçamento faz com que o ambiente de execução interrompa a transação e retorne um erro. O orçamento máximo por transação é de 1,4 milhão de CU, e o limite de espaço do bloco é de 48 milhões de CU.

O que são taxas de prioridade?

Devido a essas limitações, transações com alto custo computacional podem ocupar o espaço do bloco e atrasar outras transações. A Solana introduziu uma taxa opcional para permitir que uma transação ganhe prioridade sobre outras na fila do líder, conhecida como taxa de prioridade. O pagamento dessa taxa efetivamente impulsiona sua transação, reduzindo o tempo de execução. Isso é útil para transações urgentes ou de alto valor. A prioridade de taxa de uma transação é determinada pelo número de unidades de computação que ela solicita. Quanto mais unidades de computação uma transação solicitar, maior será a taxa que precisará pagar para manter sua prioridade na fila. Cobrar mais por um número maior de unidades de computação evita spam de transações com alto custo computacional.

As taxas de prioridade são o produto do orçamento de computação de uma transação pelo preço de sua unidade de computação, medido em microlamports: priorityFees = computeBudget * computeUnitPrice

Orçamento de computação

O computeBudget especifica o número máximo de unidades de computação que uma transação pode consumir, os custos associados às diferentes operações que a transação pode executar e os limites operacionais que ela deve respeitar. As seguintes operações geram um custo computacional:

  • Executar instruções SBF
  • Transferir dados entre programas
  • Chamar chamadas de sistema (por exemplo, registrar logs, criar um endereço de programa e realizar CPIs)

Nas invocações entre programas (CPIs), o programa chamado opera dentro do orçamento computacional do programa que fez a chamada (ou seja, o programa pai). Se o programa chamado consumir todo o orçamento computacional restante ou ultrapassar um limite definido, toda a cadeia de chamadas de programas falhará. Isso inclui a execução da transação inicial que começou o processo.

O orçamento de computação atual está disponível aqui.

Como implementar taxas de prioridade de forma programática

A taxa de priorização de uma transação é definida por meio de uma instrução SetComputeUnitPricehttps://github.com/solana-labs/solana/blob/2971e84ec87815adb1e4def95cbcd8d0d96845f5/sdk/src/compute_budget.rs#L59e de uma instrução SetComputeUnitLimit opcional. Se uma instrução SetComputeUnitPrice não for fornecida, a transação assumirá a prioridade mais baixa, pois nenhuma taxa adicional foi definida. Se uma instrução SetComputeUnitLimit não for fornecida, o limite será calculado como o produto do número de instruções na transação pelo limite padrão de unidades de computação. O ambiente de execução usa o preço e o limite das unidades de computação para calcular a taxa de priorização, que é usada para priorizar a transação em questão.

Precisamos adicionar essas instruções à transação desejada para incluir taxas de prioridade de forma programática. Em Javascript, o código é o seguinte:

Código
import {
  Keypair,
  Connection,
  PublicKey,
  Transaction,
  SystemProgram,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  ComputeBudgetProgram,
} from "@solana/web3.js";

async function main() {
  // Initialize an RPC client
  const clusterUrl = "http://127.0.0.1:8899";
  const connection = new Connection(clusterUrl, "confirmed");

  // Initialize new sender and receiver keypairs
  const fromKeypair = Keypair.generate();
  const toPubkey = new PublicKey(Keypair.generate().publicKey);

  // Airdrop SOL to the from_keypair
  const airdropAmount = 100 * LAMPORTS_PER_SOL;
  try {
    const signature = await connection.requestAirdrop(
      fromKeypair.publicKey,
      airdropAmount
    );
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balance = await connection.getBalance(fromKeypair.publicKey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  // Airdrop SOL to the toPubkey
  const airdropAmountTo = 100 * LAMPORTS_PER_SOL; // 1 SOL in lamports
  try {
    const signature = await connection.requestAirdrop(toPubkey, airdropAmount);
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balanceTo = await connection.getBalance(toPubkey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  console.log(`Account balance: ${balance / LAMPORTS_PER_SOL} SOL`);

  // Create the priority fee instructions
  const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1,
  });

  const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000,
  });

  // Create the transfer instruction
  const transferIx = SystemProgram.transfer({
    fromPubkey: fromKeypair.publicKey,
    toPubkey,
    lamports: 100_000,
  });

  // Create the transaction with priority fees
  const transaction = new Transaction().add(
    computePriceIx,
    computeLimitIx,
    transferIx
  );

  // Fetch the recent blockhash and sign the transaction
  transaction.recentBlockhash = (
    await connection.getLatestBlockhash()
  ).blockhash;
  transaction.sign(fromKeypair);

  // Send the transaction
  try {
    const txid = await sendAndConfirmTransaction(connection, transaction, [
      fromKeypair,
    ]);
    console.log("Transaction sent successfully with signature", txid);
  } catch (e) {
    console.error("Failed to send transaction:", e);
  }
}

main();

Neste trecho, nós:

  • Configuramos um ambiente de teste usando o Localhost
  • Criamos duas novas carteiras (ou seja, fromKeypair e toPubkey)
  • Enviamos 100 SOL por airdrop para cada carteira
  • Confirmamos que os airdrops foram bem-sucedidos
  • Criamos as instruções da taxa de prioridade (ou seja, computePriceIx e computeLimitIx)
  • Criamos uma instrução de transferência para enviar 100, 000 lamports de fromKeypair para toPubkey
  • Criamos uma nova transação e adicionamos todas as instruções a ela
  • Anexamos o blockhash mais recente à transação e a assinamos
  • Enviamos a transação e confirmamos que ela foi enviada com sucesso

É só isso — taxas de prioridade são apenas instruções adicionadas a uma transação para pagar por tempos de execução menores. A maior parte deste trecho configura nosso ambiente de desenvolvimento e duas carteiras para transferir SOL entre elas. O que nos interessa é criar as instruções para adicionar taxas de prioridade e anexá-las a uma transação:

Código
// Other code

const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
	microLamports: 1,
});

const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
	units: 200_000,
});

// Other code

const transaction = new Transaction().add(computePriceIx, computeLimitIx, transferIx);

// Rest of the code

Práticas recomendadas

A ordem dessas instruções é importante, pois elas são executadas sequencialmente. Se, por exemplo, você tiver uma transação que ultrapasse o limite padrão de computação (ou seja, 200 mil CU) antes de ampliar esse limite, a transação falhará. Digamos que tenhamos a seguinte transação:

  • A instrução 1 usa 100 mil
  • A instrução 2 usa 150 mil
  • A instrução 3 amplia o limite de computação

Essa transação falhará, a menos que as instruções 2 e 3 sejam invertidas. Portanto, é recomendável adicionar a instrução de limite de computação antes de incluir outras instruções na transação. Lembre-se: você não precisa usar a instrução SetComputeLimit para adicionar taxas de prioridade à sua transação — ela é totalmente opcional. O posicionamento da instrução SetComputePrice não importa.

Recomenda-se usar o método RPC getRecentPrioritizationFees para obter uma lista das taxas de prioridade pagas recentemente. Esses dados podem ser usados para estimar uma taxa de prioridade adequada para as transações, garantindo que sejam processadas pelo cluster e minimizando as taxas pagas. Como alternativa, a Helius oferece uma nova API de taxas de prioridade, que abordaremos na próxima seção.

As transações também devem solicitar a quantidade mínima de unidades de computação necessária para a execução, a fim de minimizar essas taxas. Observe que os custos não são ajustados quando o número de unidades de computação solicitadas ultrapassa o total de unidades usadas por uma transação.

Alguns provedores de carteira, como a Phantom, reconhecem que as dApps podem definir taxas de prioridade para transações. No entanto, eles desaconselham essa prática, argumentando que ela costuma criar complexidade desnecessária para os usuários finais. Em vez disso, recomendam que os desenvolvedores de dApps deixem a Phantom aplicar taxas de prioridade em nome do usuário. A Solfare, por exemplo, lida com a questão detectando automaticamente se a Solana está sob carga e aumentando ligeiramente as taxas para priorizar sua transação em relação às demais.

API de taxas de prioridade da Helius

Calcular taxas de prioridade usando o método RPC getRecentPrioritizationFees faz sentido intuitivamente para calcular taxas por slot. No entanto, isso pode ser difícil devido às condições da rede, que mudam constantemente, e à natureza da resposta de getRecentPrioritizationFees' (ou seja, ela retorna uma lista de valores dos últimos 150 blocos, útil apenas para avaliar o valor mínimo a ser definido para as taxas).

A API de taxas de prioridade da Helius apresenta um novo método, getPriorityFeeEstimate, que simplifica a resposta em um único valor, considerando os mercados de taxas globais e locais. Esse método usa um conjunto predefinido de percentis para determinar a estimativa. Esses percentis, ou níveis, variam de NONE (percentil 0) a UNSAFE_MAX (percentil 100, classificado como não seguro para evitar que os usuários esgotem seus fundos acidentalmente). Os usuários também podem optar por receber todos os níveis de prioridade e ajustar, por meio de lookbackSlots, o intervalo usado nesse cálculo. Assim, de forma semelhante a getRecentPrioritizationFees, os usuários podem considerar uma determinada quantidade de slots anteriores no cálculo da estimativa.

Por exemplo, podemos calcular todos os níveis de taxa de prioridade para o programa Jupiter v6 com o seguinte script:

Código
const url = `https://mainnet.helius-rpc.com/?api-key=`;

const getRecentPrioritizationFees = async () => {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "getPriorityFeeEstimate",
      params: [{
        "accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
        "options": {
            "includeAllPriorityFeeLevels": true,
        }
      }]
    }),
  });
  const data = await response.json();
  console.log("Fee: ", data);
};

getRecentPrioritizationFees();

Este endpoint está em desenvolvimento ativo. Para mais informações, consulte a documentação da Helius.

Conclusão

Explorar o universo das transações da Solana revela um sistema sofisticado que equilibra a eficiência da rede com incentivos econômicos. Entender como as transações funcionam, além de suas taxas e taxas de prioridade, permite que desenvolvedores e usuários tomem decisões mais informadas para otimizar suas interações na Solana. A capacidade de implementar taxas de prioridade de forma programática abre novas possibilidades para transações urgentes e de alto valor. Isso proporciona maior flexibilidade e eficiência às operações na Solana.

Se você leu até aqui, agradecemos, anon! Insira seu endereço de e-mail abaixo para nunca perder uma atualização sobre as novidades da Solana. Quer se aprofundar? Entre no nosso Discord e comece hoje mesmo a construir o futuro na blockchain de maior desempenho.

Recursos adicionais / Leituras complementares

Assine a Helius

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

Imagem ampliada