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
recentBlockhashund platzieren SienonceAdvanceals erste Anweisung - Sobald
nonceAdvanceausgefü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 */)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
nonceAdvanceausfü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()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)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',
)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.slotimport { 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
noncealsrecentBlockhash context.slotwird 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()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
nonceAdvancekommen - Wenn
nonceAdvancenicht 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.valueconst 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',
)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
InvalidNoncezurü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.






