Fan-out باستخدام Durable Nonce لـ SWQoS
الخلفية وسيناريو الاستخدام
عند إرسال المعاملات في Solana، يغيّر تقدّم الخانات وموضع القائد وازدحام مسار الشبكة باستمرار أيّ المسارات يصل إلى القائد أسرع. وهذا ليس خاصًا بأي مزود RPC أو خدمة إرسال؛ بل هو سمة هيكلية في نموذج تنفيذ Solana.
توفر نقطة نهاية SWQoS من ERPC مسار إرسال يحقن المعاملات في المسار ذي الأولوية الذي يخصصه القادة بناءً على Stake-weighted Quality of Service (SWQoS). ويبلغ نطاق الأولوية الترددي هذا نحو 5× نطاق المسار غير ذي الأولوية، ويُطبَّق قبل تقييم priority fee.
لهذا السبب، تُعد نقطة نهاية SWQoS خيارًا مهمًا لإرسال المعاملات، لكن نقطة نهاية واحدة ليست دائمًا الأسرع في بيئة الإنتاج. فحتى داخل الخانة نفسها، قد تسمح الاختلافات العابرة في المسار أو عدم توازن الحمل لنقطة نهاية سريعة أخرى بأن تتصدر.
وبالنظر إلى هذه الخصائص، قد يكون النمط التشغيلي المتمثل في إرسال المعاملة نفسها إلى عدة مسارات سريعة بالتوازي وقبول أول ما يُعالَج منها فعالًا، بدلًا من الاعتماد على مسار واحد.
ومع ذلك، عند إرسال المعاملة نفسها إلى عدة نقاط نهاية، لا يمكنك ضمان تنفيذها مرة واحدة فقط دون تحكم إضافي. وقد يؤدي fan-out دون هذا التحكم إلى تنفيذ مزدوج غير مقصود أو تعطّل التحكم في إعادة المحاولة.
توفر Solana آلية Durable Nonce لهذا الغرض. يتيح استخدام Durable Nonce إرسال المعاملة الموقعة نفسها عبر مسارات متعددة مع حصر التنفيذ على السلسلة بمرة واحدة.
تشرح هذه الصفحة كيفية تنفيذ عمليات fan-out التي تجمع بين نقطة نهاية SWQoS من ERPC ونقاط نهاية RPC سريعة أخرى، بافتراض إرسال المعاملات باستخدام Durable Nonce.
النطاق والمتطلبات الأساسية
يغطي هذا الدليل إنشاء حساب Durable Nonce باستخدام web3.js واستخدامه في إرسال المعاملات وعمليات fan-out.
متطلبات يجب فهمها:
- في معاملات Durable Nonce، استخدم قيمة nonce كـ
recentBlockhashوضعnonceAdvanceكأول تعليمة - بمجرد تنفيذ
nonceAdvance، يمكن استهلاك nonce حتى لو فشلت التعليمات اللاحقة. ولا يمكنك إعادة إرسال rawTx نفسها كما هي. - إنشاء حساب nonce إعداد يُجرى مرة واحدة ويُعاد استخدامه عادةً
الخطوة 1: تجهيز سلطة nonce والاتصال
سلطة nonce عبارة عن Keypair يمكنها ترقية قيمة 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 */)- يمكن لسلطة nonce تنفيذ
nonceAdvance - ويمكن أن تكون هي fee payer، أو keypair منفصلًا
الخطوة 2: إنشاء Keypair لحساب nonce
حساب nonce هو System Account.
typescript
const nonceAccount = Keypair.generate()const nonceAccount = Keypair.generate()يُستخدم هذا الـ Keypair في:
- الاحتفاظ بقيمة nonce (بديل عن
recentBlockhash) - التوقيع عند الإنشاء فقط
- التخزين الآمن بعد ذلك؛ إذ لا حاجة إليه في الإرسالات اليومية
الخطوة 3: حساب الحد الأدنى للإعفاء من الإيجار
يجب أن تكون حسابات nonce معفاة من الإيجار. لا تُثبِّت القيم يدويًا في الكود؛ بل اجلبها من RPC.
typescript
const lamports =
await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)const lamports =
await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)الخطوة 4: إنشاء حساب nonce وتهيئته
أنشئ حساب nonce باستخدام createAccount + nonceInitialize في معاملة واحدة.
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',
)استخدم
nonceAccount.publicKey هذا في الإرسالات اللاحقة.الخطوة 5: جلب قيمة nonce قبل كل إرسال
لكل معاملة، اجلب قيمة 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- استخدم
nonceكـrecentBlockhash - يُستخدم
context.slotفي التأكيد
الخطوة 6: بناء معاملة Durable Nonce
يجب أن تستوفي معاملات Durable Nonce الشروط التالية:
recentBlockhash = nonce- التعليمة الأولى هي
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()ملاحظات:
- إذا كنت تستخدم تعليمات ComputeBudget، فيجب أن تأتي بعد
nonceAdvance - إذا لم تكن
nonceAdvanceفي المقدمة، فسيتم رفض المعاملة
الخطوة 7: التوزيع fan-out إلى عدة RPC
أرسل rawTx نفسها بشكل متزامن.
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- التوقيع متطابق عبر جميع نقاط النهاية
- الإرسال الناجح ليس تأكيدًا
الخطوة 8: التأكيد باستخدام Durable Nonce
مع Durable Nonce، ضمّن معلومات nonce في التأكيد.
typescript
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)إذا نجح التأكيد:
- تُرقَّى قيمة nonce
- تُعيد النسخ المُرسلة إلى RPC الأخرى الخطأ
InvalidNonce
الإرسالات التالية
- بعد التأكيد، اجلب قيمة nonce جديدة
- لا تُعد استخدام rawTx نفسها أو قيمة nonce نفسها
- لسير العمل المتوازي، استخدم حسابات nonce منفصلة
للمعاملة التالية، اجلب قيمة nonce المحدّثة وابنِ المعاملة باتباع الخطوات نفسها.






