Durable Nonce Fan-out für SWQoS

Hintergrund und Nutzungsszenario

Beim Senden von Solana-Transaktionen ändern Slot-Fortschritt, Leader-Platzierung und die Auslastung einzelner Netzwerkpfade laufend, welche Route den Leader am schnellsten erreicht. Dies ist nicht spezifisch für einen RPC-Anbieter oder einen Sendedienst; es ist ein strukturelles Merkmal von Solanas Ausführungsmodell.
Der SWQoS-Endpunkt von ERPC bietet einen Sendepfad, der Transaktionen in die Prioritätsspur einspeist, die Leader auf Basis von Stake-weighted Quality of Service (SWQoS) zuweisen. Diese Prioritätsbandbreite beträgt etwa das 5-Fache der Nicht-Prioritätsspur und wird vor der Priority-Fee-Bewertung angewendet.
Aus diesem Grund ist der SWQoS-Endpunkt eine wichtige Option für den Transaktionsversand, doch im Produktivbetrieb ist ein einzelner Endpunkt nicht immer der schnellste. Selbst innerhalb desselben Slots können kurzzeitige Pfadunterschiede oder eine ungleiche Lastverteilung dazu führen, dass ein anderer schneller Endpunkt vorne liegt.
Angesichts dieser Eigenschaften kann es wirksam sein, dieselbe Transaktion parallel über mehrere schnelle Pfade zu senden und die zuerst verarbeitete zu übernehmen, statt sich auf eine einzige Route zu verlassen.
Wenn dieselbe Transaktion jedoch an mehrere Endpunkte gesendet wird, können Sie ohne zusätzliche Kontrolle nicht garantieren, dass sie nur einmal ausgeführt wird. Ein Fan-out ohne diese Steuerung kann zu unbeabsichtigter Doppelausführung oder einer defekten Retry-Steuerung führen.
Solana bietet dafür Durable Nonce als Mechanismus. Mit Durable Nonce können Sie dieselbe signierte Transaktion über mehrere Routen senden und die On-Chain-Ausführung dennoch auf genau ein Mal begrenzen.
Diese Seite erklärt, wie man Fan-out-Operationen implementiert, die den SWQoS-Endpunkt von ERPC mit anderen schnellen RPC-Endpunkten kombinieren, vorausgesetzt, dass die Transaktion mit Durable Nonce gesendet wird.

Umfang und Voraussetzungen

Dieser Leitfaden umfasst die Erstellung eines Durable-Nonce-Kontos mit web3.js sowie dessen Verwendung für Transaktionsversand und Fan-out-Operationen.
Voraussetzungen zum Verständnis:
  • Verwenden Sie bei Durable-Nonce-Transaktionen den Nonce-Wert als recentBlockhash und platzieren Sie nonceAdvance als erste Anweisung
  • Sobald nonceAdvance ausgeführt wurde, kann die Nonce auch dann verbraucht sein, wenn spätere Anweisungen fehlschlagen. Sie können dieselbe rawTx nicht unverändert erneut senden.
  • Die Erstellung des Nonce-Kontos ist ein einmaliges Setup; das Konto wird üblicherweise wiederverwendet

Schritt 1: Bereiten Sie Nonce Authority und Verbindung vor

Die Nonce Authority ist ein Keypair, das die Nonce vorrücken kann.
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 */)
  • Die Nonce Authority kann nonceAdvance ausführen
  • Sie kann der Fee Payer sein oder ein separates Keypair

Schritt 2: Erzeugen Sie ein Keypair für das Nonce-Konto

Ein Nonce-Konto ist ein System Account.
typescript
const nonceAccount = Keypair.generate()
Dieses Keypair wird verwendet für:
  • Das Halten des Nonce-Werts (Ersatz für recentBlockhash)
  • Das Signieren ausschließlich zum Zeitpunkt der Erstellung
  • Die sichere Aufbewahrung danach; für den täglichen Versand wird es nicht benötigt

Schritt 3: Berechnen Sie den Mindestbetrag für Rent-Exemption

Nonce-Konten müssen rent-exempt sein. Werte nicht hartcodieren, sondern vom RPC abrufen.
typescript
const lamports =
  await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)

Schritt 4: Erstellen und initialisieren Sie das Nonce-Konto

Erstellen Sie das Nonce-Konto mit createAccount + nonceInitialize in einer einzigen Transaktion.
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',
)
Verwenden Sie diesen nonceAccount.publicKey für alle weiteren Sendevorgänge.

Schritt 5: Holen Sie die Nonce vor jedem Senden

Rufen Sie für jede Transaktion den aktuellen Nonce-Wert ab.
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
  • Verwenden Sie nonce als recentBlockhash
  • context.slot wird in der Bestätigung verwendet

Schritt 6: Erstellen Sie die Durable-Nonce-Transaktion

Durable-Nonce-Transaktionen müssen die folgenden Bedingungen erfüllen:
  • recentBlockhash = nonce
  • Die erste Anweisung ist 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()
Anmerkungen:
  • Wenn Sie ComputeBudget-Anweisungen verwenden, müssen diese nach nonceAdvance kommen
  • Wenn nonceAdvance nicht an erster Stelle steht, wird die Transaktion abgelehnt

Schritt 7: Fan-out an mehrere RPCs

Senden Sie dieselbe rawTx parallel.
typescript
const endpoints = [
  'https://swqos-fra2.erpc.global?api-key=YOUR_API_KEY',
  'https://swqos-ams1.erpc.global?api-key=YOUR_API_KEY',
  'https://<backup-rpc-1>',
]

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
  • Die Signatur ist über alle Endpunkte hinweg identisch
  • Ein erfolgreicher Versand ist keine Bestätigung

Schritt 8: Bestätigen Sie mit Durable Nonce

Nehmen Sie bei Durable Nonce die Nonce-Informationen in die Bestätigung auf.
typescript
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Wenn die Bestätigung erfolgreich ist:
  • Die Nonce rückt vor
  • An andere RPCs gesendete Kopien geben InvalidNonce zurück

Weitere Sendevorgänge

  • Holen Sie nach der Bestätigung eine neue Nonce
  • Verwenden Sie weder dieselbe rawTx noch denselben Nonce-Wert erneut
  • Verwenden Sie für parallele Workflows separate Nonce-Konten
Rufen Sie für die nächste Transaktion die aktualisierte Nonce ab und erstellen Sie die Transaktion mit denselben Schritten.