Fan-out Nonce durável para SWQoS

Antecedentes e cenário de uso

No envio de transações Solana, progressão de slots, colocação de líder e congestionamento de caminho de rede mudam constantemente qual rota atinge o líder mais rápido. Isto não é específico para qualquer fornecedor de RPC ou serviço de envio; é uma característica estrutural do modelo de execução de Solana.
O endpoint SWQoS do ERPC fornece um caminho de envio que injeta transações na faixa de prioridade alocada pelos líderes com base na Qualidade de Serviço ponderada em Stake (SWQoS). Esta largura de banda de prioridade é de cerca de 5× a da faixa não prioritária e é aplicada antes da avaliação da taxa de prioridade.
Por essa razão, o endpoint SWQoS é uma opção importante para o envio de transações, mas na produção um único endpoint nem sempre é o mais rápido. Mesmo dentro do mesmo slot, diferenças transientes de caminho ou inclinação de carga podem permitir que outro endpoint rápido leve.
Dadas essas características, um padrão operacional de enviar a mesma transação para múltiplos caminhos rápidos em paralelo e aceitar o primeiro processado pode ser eficaz, em vez de confiar em uma única rota.
No entanto, quando a mesma transação é enviada para múltiplos endpoints, você não pode garantir que ela será executada apenas uma vez sem controle adicional. Fan-out sem este controle pode levar a execução dupla não intencional ou controle de repetição quebrado.
Solana fornece Nonce durável como o mecanismo para isso. Usar o Durable Nonce permite enviar a mesma transação assinada por várias rotas, enquanto limita a execução on-chain a uma única vez.
Esta página explica como implementar operações de fan-out que combinam o endpoint SWQoS do ERPC com outros endpoints rápidos do RPC, assumindo o envio de transações com o Durable Nonce.

Âmbito de aplicação e condições prévias

Este guia cobre a criação de uma conta Nonce durável com web3.js e usá-la para operações de envio de transações e fan-out.
Pré-requisitos para compreender:
  • Para transações Nonce duráveis, use o valor nonce como recentBlockhash e lugar nonceAdvance como a primeira instrução
  • Uma vez nonceAdvance executa, o nonce pode ser consumido mesmo se instruções posteriores falharem. Você não pode reenviar o mesmo rawTx que está.
  • A criação da conta Nonce é uma configuração única e é normalmente reutilizada

Etapa 1: Prepare a autoridade e a conexão do nonce

A autoridade nonce é um Keypair que pode avançar o nonce.
typescript
import {
  Connection,
  Keypair,
  SystemProgram,
  NONCE_ACCOUNT_LENGTH,
} from '@solana/web3.js'

const connection = new Connection(
  'https://<primary-rpc-endpoint>',
  'confirmed',
)

const nonceAuthority = Keypair.fromSecretKey(/* secret key */)
  • A autoridade nonce pode executar nonceAdvance
  • Pode ser o pagador de taxa, ou um keypair separado

Passo 2: Gerar uma conta nonce Keypair

Uma conta nonce é uma Conta do sistema.
typescript
const nonceAccount = Keypair.generate()
Este Keypair é usado para:
  • Detendo o valor nonce (substituição para recentBlockhash)
  • Assinatura apenas na hora da criação
  • Armazenamento seguro depois; não é necessário para envios diários

Etapa 3: Calcule o mínimo sem renda

As contas do Nonce devem ser isentas de renda. Não codificar os valores; obtê- los do RPC.
typescript
const lamports =
  await connection.getMinimumBalanceForRentExemption(
    NONCE_ACCOUNT_LENGTH,
  )

Passo 4: Criar e inicializar a conta nonce

Crie a conta nonce com createAccount + nonceInitialize em uma única transação.
typescript
import { Transaction } from '@solana/web3.js'

const tx = new Transaction()

tx.add(
  SystemProgram.createAccount({
    fromPubkey: nonceAuthority.publicKey,
    newAccountPubkey: nonceAccount.publicKey,
    lamports,
    space: NONCE_ACCOUNT_LENGTH,
    programId: SystemProgram.programId,
  }),
  SystemProgram.nonceInitialize({
    noncePubkey: nonceAccount.publicKey,
    authorizedPubkey: nonceAuthority.publicKey,
  }),
)

// fee payer is the nonce authority
tx.feePayer = nonceAuthority.publicKey

// use a normal blockhash for initialization
const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash('confirmed')

tx.recentBlockhash = blockhash

// sign with both the new account and the authority
tx.sign(nonceAccount, nonceAuthority)

const signature = await connection.sendRawTransaction(tx.serialize())
await connection.confirmTransaction(
  { signature, blockhash, lastValidBlockHeight },
  'confirmed',
)
Usar isto nonceAccount.publicKey para envios subsequentes.

Passo 5: Busque o nonce antes de cada envio

Para cada transação, obtenha o valor atual do nonce.
typescript
import { NonceAccount } from '@solana/web3.js'

const { value, context } =
  await connection.getAccountInfoAndContext(
    nonceAccount.publicKey,
    'confirmed',
  )

if (!value) {
  throw new Error('Nonce account not found')
}

const nonce = NonceAccount.fromAccountData(value.data).nonce
const minContextSlot = context.slot
  • Use nonce como recentBlockhash
  • context.slot é utilizado na confirmação

Passo 6: Construir a transação Nonce durável

As operações de Nonce duráveis devem satisfazer as seguintes condições:
  • recentBlockhash = nonce
  • A primeira instrução é nonceAdvance
typescript
import {
  TransactionMessage,
  VersionedTransaction,
} from '@solana/web3.js'

const instructions = [
  SystemProgram.nonceAdvance({
    noncePubkey: nonceAccount.publicKey,
    authorizedPubkey: nonceAuthority.publicKey,
  }),
  // add your real instructions after this
]

const message = new TransactionMessage({
  payerKey: nonceAuthority.publicKey,
  recentBlockhash: nonce,
  instructions,
}).compileToV0Message()

const tx = new VersionedTransaction(message)
tx.sign([nonceAuthority /* + other signers */])

const rawTx = tx.serialize()
Notas:
  • Se você usar as instruções do ComputeBudget, elas devem vir após ``nonceAdvance`
  • Se nonceAdvance não é primeiro, a transação será rejeitada

Passo 7: Espalhe-se para múltiplos RPCs

Envie o mesmo rawTx simultaneamente.
typescript
const endpoints = [
  'https://<erpc-swqos-fra>',
  'https://<backup-rpc-1>',
  'https://<backup-rpc-2>',
]

const results = await Promise.allSettled(
  endpoints.map((url) =>
    new Connection(url, 'confirmed').sendRawTransaction(rawTx, {
      skipPreflight: true,
      minContextSlot,
    }),
  ),
)

const success = results.find(
  (r): r is PromiseFulfilledResult<string> =>
    r.status === 'fulfilled',
)

if (!success) {
  throw new Error('All sends failed')
}

const signature = success.value
  • A assinatura é idêntica entre os parâmetros
  • Um envio bem- sucedido é não a confirmation

Passo 8: Confirme com Nonce durável

Com Durable Nonce, inclua a informação nonce na confirmação.
typescript
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Se a confirmação tiver sucesso:
  • O nonce avança
  • Cópias enviadas para outros RPC InvalidNonce

Envios seguintes

  • Após confirmação, buscar um nonce fresco
  • Não reutilize o mesmo valor bruto Tx ou nonce
  • Para fluxos de trabalho paralelos, use contas nonce separadas
Para a próxima transação, obter o nonce atualizado e compilar a transação usando os mesmos passos.