Durable Nonce Fan-out para SWQoS

Antecedentes y escenario de uso

En el envío de transacciones de Solana, progresión de slots, colocación de líderes y congestión de ruta de red cambian constantemente que la ruta alcanza el líder más rápido. Esto no es específico para cualquier proveedor de RPC o servicio de envío; es una característica estructural del modelo de ejecución de Solana.
El endpoint SWQoS de ERPC proporciona un camino de envío que inyecta transacciones en el carril prioritario asignado por los líderes basados en la calidad de servicio ponderada por Stake (SWQoS). Este ancho de banda prioritario es aproximadamente 5× el del carril no prioritario y se aplica antes de la evaluación de honorarios prioritarios.
Por esa razón, el endpoint SWQoS es una opción importante para el envío de transacciones, pero en la producción un solo endpoint no es siempre el más rápido. Incluso dentro de la misma slot, las diferencias de ruta transitoria o el flujo de carga pueden permitir que otro endpoint rápido para liderar.
Dada estas características, un patrón operativo de enviar la misma transacción a múltiples caminos rápidos en paralelo y aceptar el primero procesado puede ser eficaz, en lugar de depender de una sola ruta.
Sin embargo, cuando la misma transacción se envía a múltiples endpoints, no puede garantizar que se ejecutará sólo una vez sin control adicional. Fan-out sin este control puede llevar a una doble ejecución involuntaria o control de reingreso roto.
Solana proporciona Nonce Durable como el mecanismo para esto. Utilizando Durable Nonce te permite enviar la misma transacción firmada a través de múltiples rutas, limitando la ejecución en cadena a una sola vez.
Esta página explica cómo implementar operaciones de fan-out que combinan el endpoint SWQoS de ERPC con otros endpoints RPC rápidos, asumiendo el envío de transacción con Durable Nonce.

Alcance y requisitos

Esta guía cubre la creación de una cuenta de Nonce Durable con web3.js y utilizarla para el envío de transacciones y operaciones de fan-out.
Prerrequisitos para entender:
  • Para las transacciones de Nonce Durable, utilice el valor de nonce como recentBlockhash y lugar nonceAdvance como primera instrucción
  • Una vez nonceAdvance ejecuta, el nonce puede ser consumido incluso si las instrucciones posteriores fallan. No puedes reenviar el mismo rawTx como-es.
  • La creación de cuenta de Nonce es una configuración única y normalmente se reutiliza

Paso 1: Preparar la autoridad y la conexión de nonce

La autoridad de la nonce es un Keypair que puede avanzar el 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 */)
  • La autoridad de nonce puede ejecutar nonceAdvance
  • Puede ser el pago de honorarios, o un teclado separado

Paso 2: Generar una cuenta de nonce Keypair

Una cuenta de nonce es una cuenta de sistema ****.
typescript
const nonceAccount = Keypair.generate()
Este Keypair se utiliza para:
  • Tener el valor de nonce (sustitución para recentBlockhash)
  • Firmando sólo en el tiempo de creación
  • Almacenamiento seguro después; no es necesario para envíos diarios

Paso 3: Calcular el mínimo de alquiler-exento

Las cuentas de Nonce deben ser exentas de alquiler. No codificar valores; cógelos de la RPC.
typescript
const lamports =
  await connection.getMinimumBalanceForRentExemption(
    NONCE_ACCOUNT_LENGTH,
  )

Paso 4: Crear y inicializar la cuenta de nonce

Crear la cuenta de nonce con crearAccount + nonceInitialize en una sola transacción.
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',
)
Usa esto nonceAccount.publicKey para envíos posteriores.

Paso 5: Trae el nonce antes de cada envío

Para cada transacción, busque el valor actual de 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
  • Uso nonce como recentBlockhash
  • context.slot se utiliza en confirmación

Paso 6: Construir la transacción de Nonce Durable

Las transacciones de Nonce Durable deben satisfacer las siguientes condiciones:
  • recentBlockhash = nonce
  • La primera instrucción es 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:
  • Si utiliza las instrucciones de ComputeBudget, deben venir después nonceAdvance
  • Si nonceAdvance no es primero, la transacción será rechazada

Paso 7: Apague a varios RPCs

Envíe el mismo rawTx simultáneamente.
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
  • La firma es idéntica en todos los endpoints
  • Un envío exitoso es no una confirmación

Paso 8: Confirme con Nonce Durable

Con Durable Nonce, incluye la información de nonce en confirmación.
typescript
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Si la confirmación tiene éxito:
  • El nonce avanza
  • Copies sent to other RPCs return InvalidNonce

Siguiente envía

  • Después de la confirmación, busca un nuevo nonce
  • No reutilizar el mismo valor rawTx o nonce
  • Para los flujos de trabajo paralelos, utilice cuentas de nonce separadas
Para la próxima transacción, busque el nonce actualizado y construya la transacción utilizando los mismos pasos.