Langlebige Nonce Fan-out für SWQoS
Hintergrund und Nutzungsszenario
In der Solana-Transaktion senden, Slot Progression, Leader-Platzierung und Netzwerk-Weg-Verstopfung ständig ändern, welche Route erreicht den führenden schnellsten. 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 von Führungskräften auf Basis von Stake-weighted Quality of Dienst (SWQoS) zugewiesene Prioritätsspur injiziert. Diese Prioritätsbandbreite beträgt ca. 5 x die der Nicht-Prioritätsspur und wird vor der Prioritätsgebührenauswertung angewendet.
Aus diesem Grund ist der SWQoS Endpunkt eine wichtige Option für die Transaktionsübertragung, aber in der Produktion ist ein einziger Endpunkt nicht immer der schnellste. Auch innerhalb desselben Slotss können transiente Bahndifferenzen oder Lastskew einen weiteren schnellen Endpunkt führen.
Bei diesen Merkmalen kann ein operationelles Muster des Sendens derselben Transaktion auf mehrere schnelle Pfade parallel und die Einführung des ersten verarbeiteten wirksam sein, anstatt auf eine einzige Route zu verlassen.
Wenn jedoch dieselbe Transaktion an mehrere Endpunkte gesendet wird, können Sie nicht garantieren, dass sie nur einmal ohne zusätzliche Kontrolle ausgeführt wird. Fan-out ohne diese Steuerung kann zu einer unbeabsichtigten Doppelausführung oder einer gebrochenen Wiederaufnahmesteuerung führen.
Solana bietet dauerhafte Nonce als Mechanismus dafür. Mit Durable Nonce können Sie die gleiche unterzeichnete Transaktion über mehrere Routen senden, während Sie die on-chain Ausführung auf eine einzige Zeit 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 dauerhaften Nonce-Accounts mit web3.js und die Verwendung für Transaktions- und Fan-out-Operationen.
Voraussetzungen zu verstehen:
- Für dauerhafte Nonce-Transaktionen verwenden Sie den Nonce-Wert als
recentBlockhashund OrtnonceAdvanceals erste Anleitung - Einmal
nonceAdvanceführt aus, das Nonce kann auch verbraucht werden, wenn spätere Anweisungen ausfallen. Sie können nicht die gleiche rohe Tx unverändert. - Nonce-Account-Erstellung ist ein einmaliges Setup und wird typischerweise wiederverwendet
Schritt 1: Bereiten Sie die nichtze Autorität und Verbindung
Die Nicht-Behörde ist eine Schlüsselfunktion, die die Nicht-Eigenschaft voranbringen 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 Nicht-Behörde kann
nonceAdvance - Es kann der Gebührenzahler sein, oder eine separate Keypair
Schritt 2: Erstellen eines Nicht-E-Kontos
Ein Nonce-Konto ist ein *System-Konto.
typescript
const nonceAccount = Keypair.generate()const nonceAccount = Keypair.generate()Diese Keypair wird verwendet für:
- Nichterfüllungswert (Ersetzung für
recentBlockhash) - Anmeldung nur zu Schöpfungszeit
- Sichere Aufbewahrung nachher; es wird nicht für tägliche Sendungen benötigt
Schritt 3: Berechnen Sie das Miet-Befreiungsminimum
Nonce-Konten müssen mietbefreit sein. Nicht Hardcode-Werte; holen Sie sie vom RPC.
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 erstellen Account + nonce Initialize 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 diese
nonceAccount.publicKey für spätere Sendungen.Schritt 5: Holen Sie die Nonce vor jedem Senden
Für jede Transaktion, holen Sie den aktuellen Nonce-Wert.
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- Verwendung
noncewierecentBlockhash context.slotwird in der Bestätigung verwendet
Schritt 6: Erstellen Sie die dauerhafte Nonce Transaktion
Dauerhafte Nonce-Transaktionen müssen folgende 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 Compute Budget-Anweisungen verwenden, müssen sie nach kommen
nonceAdvance - wenn
nonceAdvancewird nicht zuerst die Transaktion abgelehnt
Schritt 7: Fan out zu mehreren RPCs
Senden Sie den gleichen Roh Tx gleichzeitig.
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.valueconst 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- Die Signatur ist über Endpunkte identisch
- Ein erfolgreicher Versand ist nicht eine Bestätigung
Schritt 8: Bestätigen Sie mit dauerhaften Nonce
Mit Durable Nonce, beinhalten die nicht-ce-Info in Bestätigung.
typescript
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)Erfolgt die Bestätigung:
- Die Nichterfüllung
- Kopien an andere RPCs zurückgeschickt
InvalidNonce
Nächster sendet
- Nach der Bestätigung, holen Sie eine frische Nonce
- Verwenden Sie nicht den gleichen Roh Tx oder Nichtze-Wert
- Für parallele Workflows verwenden Sie separate nicht-ce-Konten
Für die nächste Transaktion, holen Sie die aktualisierte Nonce und bauen Sie die Transaktion mit den gleichen Schritten.