Fan-out pour SWQoS
Contexte et scénario d'utilisation
Dans Solana, l'envoi de transactions, la progression des slots, le placement des leaders et la congestion du réseau changent constamment quelle route atteint le leader le plus rapidement. Ce n'est pas spécifique à tout fournisseur de RPC ou service d'envoi; c'est une caractéristique structurelle du modèle d'exécution de Solana.
Le endpoint SWQoS d'ERPC fournit un chemin d'envoi qui injecte les transactions dans la voie prioritaire allouée par les leaders en fonction de la qualité de service pondérée (SWQoS). Cette bande passante prioritaire est d'environ 5× celle de la voie non prioritaire et est appliquée avant l'évaluation des frais de priorité.
Pour cette raison, l'endpoint SWQoS est une option importante pour l'envoi de transactions, mais en production un seul paramètre n'est pas toujours le plus rapide. Même à l'intérieur d'une même slot, des différences de trajectoire transitoires ou un décalage de charge peuvent permettre à un autre endpoint rapide de conduire.
Compte tenu de ces caractéristiques, un schéma opérationnel d'envoi de la même transaction à plusieurs voies rapides en parallèle et d'acceptation du premier traité peut être efficace, plutôt que de compter sur une seule voie.
Cependant, lorsque la même transaction est envoyée à plusieurs paramètres, vous ne pouvez pas garantir qu'elle ne s'exécutera qu'une seule fois sans contrôle supplémentaire. Sans ce contrôle, le ventilateur peut mener à une double exécution involontaire ou à un contrôle de réessayer cassé.
Solana fournit la Nonce Durable comme mécanisme pour cela. Utiliser Durable Nonce vous permet d'envoyer la même transaction signée sur plusieurs routes tout en limitant l'exécution sur la chaîne à une seule fois.
Cette page explique comment mettre en œuvre des opérations de fan-out qui combinent l'endpoint SWQoS d'ERPC avec d'autres endpoints RPC rapides, en supposant l'envoi de transactions avec Durable Nonce.
Portée et conditions préalables
Ce guide couvre la création d'un compte Nonce Durable avec web3.js et son utilisation pour les opérations d'envoi de transactions et de fan-out.
Prérequis pour comprendre:
- Pour les transactions de Nonce Durable, utilisez la valeur nonce comme
recentBlockhashet lieunonceAdvancecomme première instruction - Une fois
nonceAdvanceexécute, le nonce peut être consommé même si les instructions plus tard échouent. Vous ne pouvez pas renvoyer le même Tx brut que-est. - La création de compte Nonce est une configuration unique et est généralement réutilisée
Étape 1: Préparer l'autorité et le lien non-ce
L'autorité de nonce est un Keypair qui peut faire avancer le nonce.
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 */)- L'autorité non-ce peut exécuter
nonceAdvance - Il peut être le payeur de frais, ou une paire de clés séparée
Étape 2: Générer un compte non-ce Keypair
Un compte non-ce est un Compte système.
typescript
const nonceAccount = Keypair.generate()const nonceAccount = Keypair.generate()Ce Keypair est utilisé pour:
- Détention de la valeur non-ce (remplacement pour
recentBlockhash) - Signature seulement au moment de la création
- Stockage sûr après l'envoi; il n'est pas nécessaire pour les envois quotidiens
Étape 3: Calculer le minimum exempt de loyer
Les comptes nominaux doivent être exonérés de loyer. Ne pas coder les valeurs; les récupérer du RPC.
typescript
const lamports =
await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
)const lamports =
await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
)Étape 4: Créer et initialiser le compte nonce
Créer le compte nonce avec createAccount + nonceInitialize dans une seule transaction.
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',
)Utilisez ceci
nonceAccount.publicKey pour les envois ultérieurs.Étape 5: Récupérez le nonce avant chaque envoi
Pour chaque transaction, récupérer la valeur courante nonce.
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- Utilisation
noncecommerecentBlockhash context.slotest utilisé pour la confirmation
Étape 6: Construisez la transaction de Nonce Durable
Les transactions durables doivent satisfaire aux conditions suivantes:
recentBlockhash = nonce- La première instruction est
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()Remarques:
- Si vous utilisez les instructions ComputeBudget, ils doivent venir après
nonceAdvance - Si
nonceAdvancen'est pas d'abord, la transaction sera rejetée
Étape 7: Éventail vers plusieurs RPC
Envoyez simultanément le même Tx brut.
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- La signature est identique entre les paramètres
- Un envoi réussi est Pas une confirmation
Étape 8: Confirmer avec un bouton durable
Avec Durable Nonce, incluez les informations nonce dans la confirmation.
typescript
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)Si la confirmation réussit:
- Les avances non-ce
- Copies envoyées à d'autres RPC
InvalidNonce
Envois suivants
- Après confirmation, chercher un nouveau nonce
- Ne pas réutiliser la même valeur bruteTx ou nonce
- Pour les workflows parallèles, utilisez des comptes non-ce séparés
Pour la transaction suivante, récupérer le nonce mis à jour et construire la transaction en utilisant les mêmes étapes.