Durable Nonce Fan-out voor SWQoS

Achtergrond en gebruiksscenario

Bij het verzenden van Solana-transacties veranderen slotvoortgang, leaderplaatsing en netwerk-padcongestie voortdurend welke route de leader het snelst bereikt. Dit is niet specifiek voor een RPC-provider of verzendservice; het is een structureel kenmerk van Solana's uitvoeringsmodel.
ERPC's SWQoS-endpoint biedt een verzendpad dat transacties injecteert in de prioriteitsbaan die door leaders wordt toegewezen op basis van Stake-weighted Quality of Service (SWQoS). Deze prioriteitsbandbreedte is ongeveer 5x die van de niet-prioriteitsbaan en wordt toegepast voor de evaluatie van prioriteitskosten.
Om die reden is het SWQoS-endpoint een belangrijke optie voor het verzenden van transacties, maar in productie is een enkel endpoint niet altijd het snelst. Zelfs binnen dezelfde slot kunnen tijdelijke padverschillen of belastingscheefheid een ander snel endpoint laten leiden.
Gezien deze kenmerken kan een operationeel patroon van het parallel verzenden van dezelfde transactie naar meerdere snelle paden en het accepteren van de eerste die wordt verwerkt effectief zijn, in plaats van te vertrouwen op een enkele route.
Echter, wanneer dezelfde transactie naar meerdere endpoints wordt verzonden, kunt u niet garanderen dat deze slechts eenmaal wordt uitgevoerd zonder aanvullende controle. Fan-out zonder deze controle kan leiden tot onbedoelde dubbele uitvoering of gebroken retry-controle.
Solana biedt Durable Nonce als mechanisme hiervoor. Het gebruik van Durable Nonce stelt u in staat dezelfde ondertekende transactie over meerdere routes te verzenden terwijl de on-chain uitvoering beperkt wordt tot een enkele keer.
Deze pagina legt uit hoe u fan-out-operaties implementeert die ERPC's SWQoS-endpoint combineren met andere snelle RPC-endpoints, uitgaande van transactieverzending met Durable Nonce.

Scope en vereisten

Deze handleiding behandelt het aanmaken van een Durable Nonce-account met web3.js en het gebruik ervan voor transactieverzending en fan-out-operaties.
Te begrijpen vereisten:
  • Voor Durable Nonce-transacties gebruikt u de nonce-waarde als recentBlockhash en plaatst u nonceAdvance als eerste instructie
  • Zodra nonceAdvance wordt uitgevoerd, kan de nonce worden verbruikt zelfs als latere instructies falen. U kunt dezelfde rawTx niet opnieuw verzenden.
  • Het aanmaken van een nonce-account is eenmalig en wordt doorgaans hergebruikt

Stap 1: Bereid de nonce-autoriteit en verbinding voor

De nonce-autoriteit is een Keypair dat de nonce kan doorschuiven.
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 */)
  • De nonce-autoriteit kan nonceAdvance uitvoeren
  • Het kan de fee payer zijn, of een apart keypair

Stap 2: Genereer een nonce-account Keypair

Een nonce-account is een System Account.
typescript
const nonceAccount = Keypair.generate()
Dit Keypair wordt gebruikt voor:
  • Het bewaren van de nonce-waarde (vervanging voor recentBlockhash)
  • Ondertekenen alleen bij aanmaak
  • Veilige opslag daarna; het is niet nodig voor dagelijkse verzendingen

Stap 3: Bereken het huurvrijstellingsminimum

Nonce-accounts moeten huurvrijgesteld zijn. Hardcode geen waarden; haal ze op via de RPC.
typescript
const lamports =
  await connection.getMinimumBalanceForRentExemption(
    NONCE_ACCOUNT_LENGTH,
  )

Stap 4: Maak en initialiseer het nonce-account

Maak het nonce-account aan met createAccount + nonceInitialize in een enkele transactie.
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',
)
Gebruik deze nonceAccount.publicKey voor volgende verzendingen.

Stap 5: Haal de nonce op voor elke verzending

Haal voor elke transactie de huidige nonce-waarde op.
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
  • Gebruik nonce als recentBlockhash
  • context.slot wordt gebruikt bij bevestiging

Stap 6: Bouw de Durable Nonce-transactie

Durable Nonce-transacties moeten aan de volgende voorwaarden voldoen:
  • recentBlockhash = nonce
  • De eerste instructie is 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()
Opmerkingen:
  • Als u ComputeBudget-instructies gebruikt, moeten deze na nonceAdvance komen
  • Als nonceAdvance niet als eerste staat, wordt de transactie afgewezen

Stap 7: Fan-out naar meerdere RPC's

Verzend dezelfde rawTx gelijktijdig.
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
  • De handtekening is identiek voor alle endpoints
  • Een succesvolle verzending is geen bevestiging

Stap 8: Bevestig met Durable Nonce

Bij Durable Nonce neemt u de nonce-info op in de bevestiging.
typescript
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Als de bevestiging slaagt:
  • De nonce schuift door
  • Kopies verzonden naar andere RPC's retourneren InvalidNonce

Volgende verzendingen

  • Haal na bevestiging een nieuwe nonce op
  • Hergebruik niet dezelfde rawTx of nonce-waarde
  • Gebruik voor parallelle workflows aparte nonce-accounts
Haal voor de volgende transactie de bijgewerkte nonce op en bouw de transactie met dezelfde stappen.