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 leader. 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-RPCsendTransaction
- 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:
- Dengan proxy melalui server RPC dan sendTransaction metode JSON-RPC
- Langsung ke leader melalui TPU Client
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.
Bagaimana Node RPC melakukan broadcast Transaksi
Setelah node RPC menerima transaksi melalui sendTransaction
, node tersebut akan mengubah transaksi menjadi paket UDP 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 epocj (~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 transaksi, transaksi yang baru dikirimkan tidak akan diproses. Ada argumen 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) leader . TPU memproses transaksi dalam lima fase berbeda:
Gambar 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:
- tpu menangani transaksi reguler seperti transfer token, NFT mint, dan instruksi program
- tpu_vote berfokus secara eksklusif pada transaksi pemungutan suara
- tpu_forwards meneruskan paket yang belum diproses ke leader berikutnya jika leader saat ini tidak dapat memproses semua transaksi
Untuk informasi lebih lanjut tentang TPU, silakan lihat tulisan luar biasa ini oleh Jito Labs.
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 loss 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 diforward. 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, recentBlockhash 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 checks di sendTransaction
.
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.
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.
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
- Komitmen 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 getSignatureStatuses 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 wajar.
Pada umumnya, percobaan kembali transaksi secara manual melibatkan penyimpanan lastValidBlockHeight
secara sementara yang berasal dari getLatestBlockhash. Setelah disimpan, aplikasi kemudian dapat melakukan polling ketinggian block cluster 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 backoff, yang lain seperti Mango memilih untuk terus mengirimkan ulang transaksi pada interval konstan hingga beberapa waktu habis.
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();
}
})();
while (blockheight < lastValidBlockHeight) {
connection.sendRawTransaction(rawTransaction, {
skipPreflight: true,
});
await sleep(500);
blockheight = await connection.getBlockHeight();
}
Saat melakukan polling melalui getLatestBlockhash
, aplikasi harus menentukan level commitment 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 getEpochInfo
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 Creech, White Tiger, Le Yafo, Buffalu, dan Jito Labs atas ulasan dan umpan balik mereka.