Durable Nonce Fan-out untuk SWQoS
Skenario latar belakang dan penggunaan
In Solana transaksi yang dikirim, slot progresi, penempatan pemimpin, dan jalur jaringan kemacetan terus-menerus mengubah rute mana yang mencapai pemimpin tercepat. Ini tidak spesifik bagi apapun RPC penyedia atau layanan pengiriman; ini adalah karakteristik struktural SolanaModel eksekusi.
ERPC's SWQoS titik akhir memberikan jalur pengiriman bahwa menyuntikkan transaksi ke jalur prioritas yang dialokasikan oleh para pemimpin berdasarkan Kualitas Layanan Stake- berbobot (SWQoS). Prioritas bandwidth ini adalah sekitar 5 × dari jalur bukan prioritas dan diterapkan sebelum evaluasi biaya prioritas.
Untuk alasan itu, SWQoS titik akhir adalah pilihan penting untuk pengiriman transaksi, tetapi dalam produksi titik akhir tunggal tidak selalu menjadi yang tercepat bahkan dalam slot yang sama, perbedaan jalur transien atau batang beban dapat memungkinkan titik akhir lain untuk memimpin.
Mengingat karakteristik ini, pola operasional mengirimkan transaksi yang sama ke beberapa jalan cepat secara paralel dan menerima yang pertama diproses dapat efektif, daripada mengandalkan pada satu rute.
Namun, ketika transaksi yang sama dikirim ke beberapa titik akhir, Anda tidak dapat menjamin bahwa itu akan mengeksekusi hanya sekali tanpa kontrol tambahan. Fan-out tanpa kontrol ini dapat menyebabkan eksekusi ganda yang tidak diinginkan atau kontrol ulang rusak.
Solana menyediakan Durable Nonce sebagai mekanisme untuk ini. Menggunakan Durable Noonce memungkinkan Anda mengirim transaksi yang sama ditandatangani di beberapa rute sambil membatasi on-rantai eksekusi untuk satu waktu.
Halaman ini menjelaskan bagaimana mengimplementasikan operasi fan-out yang menggabungkan ERPC's SWQoS titik akhir dengan cepat lainnya RPC titik akhir, dengan asumsi transaksi yang dikirim dengan Durable Nonce.
Scope dan prasyarat
Pemandu ini mencakup pembuatan akun Durable Nonce dengan web3.js dan menggunakannya untuk transaksi pengiriman dan operasi fan- out.
Prasyarat untuk memahami:
- Untuk transaksi Durable Nonce, gunakan nilai nonce sebagai
recentBlockhashdan tempatnonceAdvancesebagai instruksi pertama - Sekali
nonceAdvanceexecutes, nonce dapat dikonsumsi bahkan jika instruksi kemudian gagal. Anda tidak dapat mengirim ulang rawTx sama seperti - adalah. - Pembuatan akun nonce adalah setup satu waktu dan biasanya digunakan kembali
Langkah 1: Siapkan otoritas nonce dan koneksi
Otoritas nonce adalah Keypair yang dapat memajukan nonce tersebut.
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 */)- Otoritas nonce dapat mengeksekusi
nonceAdvance - Ini bisa menjadi biaya pembayar, atau pasangan kunci terpisah
Langkah 2: Buat sebuah Keypair akun nonce
Akun nonce adalah akun sistem.
typescript
const nonceAccount = Keypair.generate()const nonceAccount = Keypair.generate()Keypair ini digunakan untuk:
- Memegang nilai nonce (pengganti untuk
recentBlockhash) - Hanya menandai waktu penciptaan
- Penyimpanan sesudahnya aman; tidak diperlukan untuk mengirim sehari-hari
Langkah 3: perhitungkan minimum yang dibebas-terminimalkan
Akun nonce harus dicantumkan. Jangan nilai kode keras; mengambilnya dari RPC.
typescript
const lamports =
await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
)const lamports =
await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH,
)Langkah 4: Buat dan inisialisasi akun nonce
Buat akun nononce dengan * * createAccount + nonceInisialize * * dalam sebuah transaksi tunggal.
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',
)Gunakan ini
nonceAccount.publicKey untuk mengirimkan berikutnya.Langkah 5: Ambil nonce sebelum masing-masing mengirim
Untuk setiap transaksi, ambil nilai nonce saat ini.
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- Gunakan
nonceasrecentBlockhash context.slotDigunakan dalam konfirmasi
Langkah 6: Membangun transaksi Durable Nonce
Transaksi yang dapat didupkan Nonce harus memenuhi kondisi berikut:
recentBlockhash = nonce- Instruksi pertama adalah
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()Catatan:
- Jika Anda menggunakan instruksi ComputeBudget, mereka harus datang * * setelah * *
nonceAdvance - If
nonceAdvancebukan yang pertama, transaksinya akan ditolak
Langkah 7: menyebar ke beberapa RPC
Kirim rawTx yang sama secara bersamaan.
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- Tanda tangan identik di seluruh titik akhir
- Pengirimansukses adalah * * bukan * * konfirmasi
Langkah 8: Konfirmasi dengan Durable Nonce
Dengan Durable Nonce, masukkan info nonce dalam konfirmasi.
typescript
await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)await connection.confirmTransaction(
{
signature,
nonceAccountPubkey: nonceAccount.publicKey,
nonceValue: nonce,
minContextSlot,
},
'confirmed',
)Jika konfirmasi berhasil:
- The nonce kemajuan
- Salinan dikirim ke RPC lain kembali
InvalidNonce
Berikutnya kirim
- Setelah konfirmasi, ambil satu nonce segar
- Jangan gunakan nilai rawTx atau nonce yang sama
- Untuk paralel mengalir, gunakan akun nonce terpisah
Untuk transaksi berikutnya, ambil nonce terbaru dan bangun transaksi menggunakan langkah yang sama.