Mengulang Kembali Transaksi

Pada beberapa kesempatan, transaksi yang terlihat valid mungkin dibatalkan sebelum dimasukkan ke dalam blok. Ini paling sering terjadi saat ada kemacetan jaringan, ketika node RPC gagal melakukan rebroadcast transaksi ke leaderopen in new window. Bagi end-user, mungkin transaksi mereka tampak seolah-olah hilang sama sekali. Disaat node RPC telah dilengkapi dengan algoritma generic rebroadcasting, pengembang aplikasi juga mampu mengembangkan logika rebroadcasting kustom mereka sendiri.

Fakta

Lembar Fakta

  • Node RPC akan mencoba untuk melakukan rebroadcast ulang transaksi menggunakan algoritma generik
  • Pengembang aplikasi dapat menerapkan logika penyiaran ulang kustom mereka sendiri
  • Pengembang dapat memanfaatkan parameter maxRetries pada metode JSON-RPC sendTransaction
  • Pengembang harus mengaktifkan pemeriksaan sebelum broadcast untuk mendeteksi kesalahan sebelum transaksi diajukan
  • Sebelum menandatangani ulang transaksi apa pun, sangat penting untuk memastikan bahwa blockhash transaksi awal telah kedaluwarsa

Perjalanan dari sebuah Transaksi

Bagaimana Klien Mengirimkan Transaksi

Di Solana, tidak ada konsep mempool. Semua transaksi, baik itu dimulai oleh program atau oleh end-user, secara efisien diarahkan ke leader sehingga mereka dapat diproses menjadi block. Ada dua cara utama di mana transaksi dapat dikirim ke leader:

  1. Dengan proxy melalui server RPC dan sendTransactionopen in new window metode JSON-RPC
  2. Langsung ke leader melalui TPU Clientopen in new window

Sebagian besar end-user akan mengirimkan transaksi melalui server RPC. Ketika klien mengajukan transaksi, node RPC penerima pada gilirannya akan mencoba untuk melakukan broadcast transaksi ke leader saat ini dan berikutnya. Sampai transaksi diproses oleh leader, tidak ada catatan transaksi di luar yang diketahui oleh klien dan node RPC yang mengirimkan. Dalam kasus TPU client, rebroadcast dan leader forwarding ditangani sepenuhnya oleh perangkat lunak klien. Perjalanan Transaksi

Bagaimana Node RPC melakukan broadcast Transaksi

Setelah node RPC menerima transaksi melalui sendTransaction, node tersebut akan mengubah transaksi menjadi paket UDPopen in new window sebelum meneruskannya ke leader terkait. UDP memungkinkan validator untuk berkomunikasi dengan cepat satu sama lain, tetapi tidak memberikan jaminan apa pun terkait pengiriman transaksi.

Karena jadwal leader Solana diketahui sebelum setiap epocjopen in new window (~2 hari), node RPC akan menyiarkan transaksinya langsung ke pemimpin saat ini dan selanjutnya. Ini berbeda dengan gossip protocol lain seperti Ethereum yang menyebarkan transaksi secara acak dan luas di seluruh jaringan. Secara default, node RPC akan mencoba meneruskan transaksi ke leader setiap dua detik hingga transaksi diselesaikan atau hash block transaksi kedaluwarsa (150 block atau ~ 1 menit 19 detik pada saat penulisan ini). Jika ukuran antrian rebroadcast yang belum diselesaikan lebih besar dari 10.000 transaksiopen in new window, transaksi yang baru dikirimkan tidak akan diproses. Ada argumenopen in new window command-line yang dapat disesuaikan oleh operator RPC untuk mengubah default behaviour dari logika percobaan ulang ini.

Saat node RPC melakukan broadcast transaksi, node tersebut akan mencoba meneruskan transaksi ke Transaction Processing Unit (TPU)open in new window leader . TPU memproses transaksi dalam lima fase berbeda:

Ringkasan TPUGambar Atas Perkenan Jito Labs

Dari kelima fase ini, Fetch Stage bertujuan untuk menerima transaksi. Dalam Fetch Stage, validator akan mengkategorikan transaksi yang masuk berdasarkan tiga port berikut:

Untuk informasi lebih lanjut tentang TPU, silakan lihat tulisan luar biasa ini oleh Jito Labsopen in new window.

Bagaimana Transaksi dapat dibatalkan

Sepanjang perjalanan transaksi, ada beberapa skenario di mana transaksi dapat secara tidak sengaja dibatalkan dari jaringan.

Sebelum transaksi diproses

Jika jaringan menghentikan sebuah transaksi, kemungkinan besar jaringan akan melakukannya sebelum transaksi diproses oleh leader. UDP packet lossopen in new window adalah alasan paling sederhana mengapa hal ini dapat terjadi. Selama masa beban jaringan yang intens, validator juga mungkin kewalahan oleh banyaknya transaksi yang perlu diproses. Meskipun validator telah diatur untuk meneruskan surplus transaksi melalui tpu_forwards, ada batasan jumlah data yang dapat diforwardopen in new window. Selanjutnya, tiap forward hanya terbatas pada satu hop antara validator. Artinya, transaksi yang diterima pada port tpu_forwards tidak diteruskan ke validator lain.

Ada juga dua alasan yang kurang diketahui mengapa suatu transaksi dapat dibatalkan sebelum diproses. Skenario pertama melibatkan transaksi yang dikirimkan melalui RPC pool. Kadang-kadang, bagian dari RPC pool bisa ada di depan yang lainnya yang merupakan bagian RPC pool yang sama. Ini dapat menyebabkan masalah ketika node dalam pool yang sama perlu bekerja sama. Dalam contoh ini, recentBlockhashopen in new window dari suatu transaksi diambil dari bagian depan pool (Backend A). Ketika transaksi dikirimkan ke bagian pool yang tertinggal (Backend B), node tidak akan mengenali blockhash tadi dan akan membatalkan transaksi. Ini dapat dideteksi saat pengiriman transaksi jika developer mengaktifkan preflight checksopen in new window di sendTransaction.

Dropped melalui RPC Pool

Percabangan jaringan sementara juga dapat mengakibatkan transaksi dibatalkan. Jika validator lambat untuk memutar ulang bloknya dalam Banking Stage, ia kemudian mungkin akan membuat cabang kecil (minority fork). Saat klien membuat transaksi, transaksi mungkin merujuk ke recentBlockhash yang hanya ada di cabang kecil. Setelah transaksi dikirimkan, cluster kemudian dapat beralih dari cabang kecilnya sebelum transaksi diproses. Dalam skenario ini, transaksi dibatalkan karena blockhash tidak ditemukan.

Dibatalkan karena Cabang Kecil/Minority Fork (Sebelum Diproses)

Setelah transaksi diproses dan sebelum diselesaikan

Jika transaksi mereferensikan recentBlockhash dari cabang kecil, transaksi masih mungkin diproses. Dalam hal ini, bagaimanapun, itu akan diproses oleh leader di cabang kecil. Ketika leader ini mencoba untuk membagikan transaksi yang diproses dengan seluruh jaringan, ia akan gagal mencapai kesepakatan dengan mayoritas validator yang tidak mengenali cabang kecil. Pada saat ini, transaksi akan dibatalkan sebelum dapat diselesaikan.

Dibatalkan karena Cabang Kecil/Minority Fork (Setelah Diproses)

Menangani Transaksi yang dibatalkan

Meskipun node RPC akan mencoba untuk melakukan rebroadcast transaksi, algoritma yang mereka gunakan bersifat umum dan seringkali tidak cocok untuk kebutuhan aplikasi tertentu. Untuk mempersiapkan apabila terjadi kemacetan jaringan, pengembang aplikasi harus dapat menyesuaikan logika rebroadcast mereka sendiri.

Menelusuri lebih dalam mengenai sendTransaction

Dalam hal mengirimkan transaksi, metode RPC sendTransaction adalah alat utama yang tersedia untuk pengembang. sendTransaction hanya bertanggung jawab untuk menyampaikan transaksi dari klien ke node RPC. Jika node menerima transaksi, sendTransaction akan mengembalikan id transaksi yang dapat digunakan untuk melacak transaksi. Respons yang berhasil tidak menunjukkan apakah transaksi akan diproses atau diselesaikan oleh cluster.

TIP

Request Parameter

  • transaction: string - Transaksi yang sepenuhnya ditandatangani, sebagai string yang di encode
  • (opsional) configuration object: object
    • skipPreflight: boolean - jika true, lewati pemeriksaan preflight dari transaksi (default: false)
    • (opsional) preflightCommitment: string - Komitmenopen in new window level yang akan digunakan untuk simulasi preflight terhadap slot bank (default: "finalized").
    • (opsional) encoding: string - Encoding yang digunakan untuk data transaksi. Dapat menggunakan "base58" (lambat), atau "base64". (default: "base58").
    • (opsional) maxRetries: usize - Jumlah maksimum percobaan node RPC mengirimkan ulang transaksi ke leader. Jika parameter ini tidak disediakan, node RPC akan mencoba kembali transaksi hingga diselesaikan atau hingga blockhash kedaluwarsa.

Response

  • transaction id: string - Tanda tangan transaksi pertama yang disematkan dalam transaksi, sebagai string dengan encode base-58. ID transaksi ini dapat digunakan dengan getSignatureStatusesopen in new window untuk melakukan polling untuk pembaruan status.

Menyesuaikan Logika Rebroadcast

Untuk mengembangkan logika rebroadcast mereka sendiri, pengembang harus memanfaatkan parameter maxRetries sendTransaction. Jika disediakan, maxRetries akan menggantikan logika coba ulang default node RPC, yang memungkinkan developer mengontrol proses coba lagi secara manual dalam batas yang wajaropen in new window.

Pada umumnya, percobaan kembali transaksi secara manual melibatkan penyimpanan lastValidBlockHeight secara sementara yang berasal dari getLatestBlockhashopen in new window. Setelah disimpan, aplikasi kemudian dapat melakukan polling ketinggian block clusteropen in new window dan mencoba kembali transaksi secara manual dengan interval yang sesuai. Pada saat jaringan macet, akan lebih baik jika menyetel maxRetries ke 0 dan melakukan rebroadcast ulang secara manual melalui algoritma khusus. Di saat beberapa aplikasi mungkin menggunakan algoritma exponential backoffopen in new window, yang lain seperti Mangoopen in new window memilih untuk terus mengirimkan ulangopen in new window transaksi pada interval konstan hingga beberapa waktu habis.

Press </> button to view full source
import {
  Keypair,
  Connection,
  LAMPORTS_PER_SOL,
  SystemProgram,
  Transaction,
} from "@solana/web3.js";
import * as nacl from "tweetnacl";

const sleep = async (ms: number) => {
  return new Promise((r) => setTimeout(r, ms));
};

(async () => {
  const payer = Keypair.generate();
  const toAccount = Keypair.generate().publicKey;

  const connection = new Connection("http://127.0.0.1:8899", "confirmed");

  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
    LAMPORTS_PER_SOL
  );

  await connection.confirmTransaction(airdropSignature);

  const blockhashResponse = await connection.getLatestBlockhashAndContext();
  const lastValidBlockHeight = blockhashResponse.context.slot + 150;

  const transaction = new Transaction({
    feePayer: payer.publicKey,
    blockhash: blockhashResponse.value.blockhash,
    lastValidBlockHeight: lastValidBlockHeight,
  }).add(
    SystemProgram.transfer({
      fromPubkey: payer.publicKey,
      toPubkey: toAccount,
      lamports: 1000000,
    })
  );
  const message = transaction.serializeMessage();
  const signature = nacl.sign.detached(message, payer.secretKey);
  transaction.addSignature(payer.publicKey, Buffer.from(signature));
  const rawTransaction = transaction.serialize();
  let blockheight = await connection.getBlockHeight();

  while (blockheight < lastValidBlockHeight) {
    connection.sendRawTransaction(rawTransaction, {
      skipPreflight: true,
    });
    await sleep(500);
    blockheight = await connection.getBlockHeight();
  }
})();

Saat melakukan polling melalui getLatestBlockhash, aplikasi harus menentukan level commitmentopen in new window yang diinginkan. Dengan menetapkan commitmentnya ke confirmed (diberi suara) atau finalized (~30 blok setelah confirmed), aplikasi dapat menghindari polling blockhash dari cabang kecil/fork minoritas.

Jika aplikasi memiliki akses ke node RPC di belakang load balancer, aplikasi juga dapat memilih untuk membagi beban kerjanya di antara node tertentu. Node RPC yang melayani permintaan data yang intensif seperti getProgramAccounts mungkin cenderung tertinggal dan tidak cocok untuk juga meneruskan transaksi. Untuk aplikasi yang menangani transaksi yang time-sensitive, mungkin lebih bijaksana untuk memiliki node khusus yang hanya menangani sendTransaction.

Dampak apabila Melewatkan Preflight

Secara default, sendTransaction akan melakukan tiga pemeriksaan preflight sebelum mengirimkan transaksi. Secara khusus, sendTransaction akan:

  • Verifikasi apabila semua tanda tangan valid
  • Periksa apakah blockhash yang direferensikan berada dalam 150 blok terakhir
  • Simulasikan transaksi terhadap slot bank yang ditentukan oleh preflightCommitment

Jika salah satu dari tiga pemeriksaan preflight ini gagal, sendTransaction akan memunculkan error sebelum mengirimkan transaksi. Pemeriksaan preflight sering kali dapat menjadi perbedaan antara kehilangan transaksi dan memungkinkan klien menangani kesalahan dengan baik. Untuk memastikan bahwa kesalahan umum ini telah diperhitungkan, sebaiknya pengembang tetap mengatur skipPreflight ke false.

Kapan Menandatangani Ulang (Re-Sign) Transaksi

Terlepas dari semua upaya untuk rebroadcast, mungkin ada saat-saat di mana klien perlu menandatangani ulang (re-sign) transaksi. Sebelum menandatangani ulang transaksi apa pun, sangat penting untuk memastikan bahwa blockhash transaksi awal telah kedaluwarsa. Jika blockhash awal masih valid, ada kemungkinan kedua transaksi tersebut diterima oleh jaringan. Bagi end-user, ini akan tampak seolah-olah mereka secara tidak sengaja mengirim transaksi yang sama dua kali.

Di Solana, transaksi yang dibatalkan dapat dibuang dengan aman setelah blockhash yang dirujuknya lebih lama dari lastValidBlockHeight yang diterima dari getLatestBlockhash. Pengembang harus melacak lastValidBlockHeight ini dengan menjalankan getEpochInfoopen in new window dan membandingkan dengan blockHeight dari respons yang diterima. Setelah blockhash tidak valid, klien dapat masuk kembali dengan blockhash yang baru dibuat.

Ucapan Terima Kasih

Terima kasih banyak kepada Trent Nelson, Jacob Creechopen in new window, White Tiger, Le Yafo, Buffaluopen in new window, dan Jito Labsopen in new window atas ulasan dan umpan balik mereka.

Last Updated:
Contributors: akangaziz