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 leaders toewijzen op basis van Stake-weighted Quality of Service (SWQoS). Deze prioriteitsbandbreedte is ongeveer 5× die van de niet-prioriteitsbaan en wordt toegepast vóór de evaluatie van de priority fee.
Om die reden is het SWQoS-endpoint een belangrijke optie voor het verzenden van transacties, maar in productie is één endpoint niet altijd het snelst. Zelfs binnen hetzelfde slot kunnen tijdelijke padverschillen of een scheve belasting ervoor zorgen dat een ander snel endpoint voorligt.
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.
Wanneer dezelfde transactie naar meerdere endpoints wordt verzonden, kunt u echter zonder aanvullende controle niet garanderen dat deze maar één keer wordt uitgevoerd. Fan-out zonder deze controle kan leiden tot onbedoelde dubbele uitvoering of een ontregelde 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.
Reikwijdte 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.
Wat u vooraf moet weten:
- 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 mislukken. U kunt dezelfde rawTx niet ongewijzigd 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.
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 */)
- De nonce-autoriteit kan
nonceAdvance uitvoeren
- Het kan de fee payer zijn, of een apart keypair
Stap 2: Genereer een Keypair voor het nonce-account
Een nonce-account is een System Account.
const nonceAccount = Keypair.generate()
const nonceAccount = Keypair.generate()
Dit Keypair wordt gebruikt voor:
- Het bewaren van de nonce-waarde (vervanging voor
recentBlockhash)
- Alleen ondertekenen bij het aanmaken
- 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.
const lamports =
await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)
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.
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',
)
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.
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
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
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()
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.
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
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
- 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.
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)
Als de bevestiging slaagt:
- De nonce schuift door
- Kopieën die naar andere RPC's zijn verzonden, 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.