Durable Nonce Fan-out untuk SWQoS

Latar belakang dan skenario penggunaan

Dalam pengiriman transaksi Solana, progresi slot, penempatan leader, dan kemacetan jalur jaringan terus-menerus mengubah rute mana yang paling cepat mencapai leader. Hal ini tidak khusus pada penyedia RPC atau layanan pengiriman mana pun; ini adalah karakteristik struktural dari model eksekusi Solana.
SWQoS Endpoint milik ERPC menyediakan jalur pengiriman yang menyuntikkan transaksi ke jalur prioritas yang dialokasikan oleh Leader berdasarkan Stake-weighted Quality of Service (SWQoS). Bandwidth prioritas ini sekitar 5x dibandingkan jalur non-prioritas dan diterapkan sebelum evaluasi Priority-fee.
Karena itu, SWQoS Endpoint adalah opsi penting untuk pengiriman transaksi, tetapi di lingkungan produksi satu endpoint saja tidak selalu menjadi yang tercepat. Bahkan di dalam slot yang sama, perbedaan jalur yang bersifat sementara atau ketimpangan beban dapat membuat endpoint cepat lainnya unggul.
Mengingat karakteristik tersebut, alih-alih mengandalkan satu rute saja, pola operasional berupa pengiriman transaksi yang sama secara paralel ke beberapa jalur cepat dan menerima yang pertama kali diproses dapat menjadi efektif.
Namun, ketika transaksi yang sama dikirim ke beberapa endpoint, tanpa kontrol tambahan Anda tidak dapat menjamin bahwa transaksi tersebut hanya dieksekusi satu kali. Fan-out tanpa kontrol ini dapat menyebabkan eksekusi ganda yang tidak diinginkan atau kontrol retry yang rusak.
Solana menyediakan Durable Nonce sebagai mekanisme untuk hal ini. Dengan Durable Nonce, Anda dapat mengirim transaksi yang sama dan sudah ditandatangani melalui beberapa rute, sekaligus membatasi eksekusi on-chain hanya satu kali.
Halaman ini menjelaskan cara mengimplementasikan operasi fan-out yang menggabungkan SWQoS Endpoint milik ERPC dengan endpoint RPC cepat lainnya, dengan asumsi pengiriman transaksi dilakukan menggunakan Durable Nonce.

Cakupan dan prasyarat

Panduan ini mencakup pembuatan akun Durable Nonce dengan web3.js serta penggunaannya untuk pengiriman transaksi dan operasi fan-out.
Prasyarat yang perlu dipahami:
  • Untuk transaksi Durable Nonce, gunakan nilai nonce sebagai recentBlockhash dan tempatkan nonceAdvance sebagai instruksi pertama
  • Setelah nonceAdvance dieksekusi, nonce dapat terpakai meskipun instruksi berikutnya gagal. Anda tidak dapat mengirim ulang rawTx yang sama apa adanya.
  • Pembuatan akun nonce adalah penyiapan sekali jalan dan akun tersebut 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 */)
  • Otoritas nonce dapat mengeksekusi nonceAdvance
  • Otoritas ini bisa menjadi fee payer, atau berupa Keypair terpisah

Langkah 2: Buat Keypair untuk akun nonce

Akun nonce adalah System Account.
typescript
const nonceAccount = Keypair.generate()
Keypair ini digunakan untuk:
  • Menyimpan nilai nonce (pengganti recentBlockhash)
  • Menandatangani hanya pada saat pembuatan
  • Penyimpanan yang aman setelahnya; Keypair ini tidak diperlukan untuk pengiriman harian

Langkah 3: Hitung saldo minimum rent-exempt

Akun nonce harus rent-exempt. Jangan menuliskan nilainya secara hardcode; ambil nilai tersebut dari RPC.
typescript
const lamports =
  await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH)

Langkah 4: Buat dan inisialisasi akun nonce

Buat akun nonce dengan createAccount + nonceInitialize dalam satu transaksi.
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',
)
Gunakan nonceAccount.publicKey ini untuk pengiriman berikutnya.

Langkah 5: Ambil nonce sebelum setiap pengiriman

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.slot
  • Gunakan nonce sebagai recentBlockhash
  • context.slot digunakan dalam konfirmasi

Langkah 6: Bangun transaksi Durable Nonce

Transaksi Durable 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()
Catatan:
  • Jika Anda menggunakan instruksi ComputeBudget, instruksi tersebut harus ditempatkan setelah nonceAdvance
  • Jika nonceAdvance tidak berada di urutan pertama, transaksi akan ditolak

Langkah 7: Fan-out ke beberapa RPC

Kirim rawTx yang sama secara bersamaan.
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.value
  • Tanda tangan identik di seluruh endpoint
  • Pengiriman yang berhasil bukan merupakan konfirmasi

Langkah 8: Konfirmasi dengan Durable Nonce

Dengan Durable Nonce, sertakan informasi nonce dalam konfirmasi.
typescript
await connection.confirmTransaction(
  {
    signature,
    nonceAccountPubkey: nonceAccount.publicKey,
    nonceValue: nonce,
    minContextSlot,
  },
  'confirmed',
)
Jika konfirmasi berhasil:
  • Nonce akan maju
  • Salinan yang dikirim ke RPC lain akan mengembalikan InvalidNonce

Pengiriman berikutnya

  • Setelah konfirmasi, ambil nonce yang baru
  • Jangan gunakan kembali rawTx atau nilai nonce yang sama
  • Untuk alur kerja paralel, gunakan akun nonce yang terpisah
Untuk transaksi berikutnya, ambil nonce yang telah diperbarui dan bangun transaksi menggunakan langkah-langkah yang sama.