Transaksi dan Instruksi
Di Solana, pengguna mengirim transaksi untuk berinteraksi dengan jaringan. Transaksi berisi satu atau lebih instruksi yang menentukan operasi yang akan diproses. Logika eksekusi untuk instruksi disimpan pada program yang di-deploy ke jaringan Solana, di mana setiap program mendefinisikan serangkaian instruksinya sendiri.
Berikut adalah detail penting tentang pemrosesan transaksi Solana:
- Jika transaksi mencakup beberapa instruksi, instruksi tersebut dieksekusi dalam urutan yang ditambahkan ke transaksi.
- Transaksi bersifat "atomik" - semua instruksi harus diproses dengan sukses, atau seluruh transaksi gagal dan tidak ada perubahan yang terjadi.
Transaksi pada dasarnya adalah permintaan untuk memproses satu atau lebih instruksi. Anda dapat menganggap transaksi sebagai amplop yang berisi formulir. Setiap formulir adalah instruksi yang memberi tahu jaringan apa yang harus dilakukan. Mengirim transaksi seperti mengirimkan amplop melalui pos untuk memproses formulir-formulir tersebut.
Transaksi Disederhanakan
Poin-poin Penting
- Transaksi Solana mencakup instruksi yang memanggil program di jaringan.
- Transaksi bersifat atomic - jika ada instruksi yang gagal, seluruh transaksi gagal dan tidak ada perubahan yang terjadi.
- Instruksi dalam transaksi dijalankan secara berurutan.
- Batas ukuran transaksi adalah 1232 byte.
- Setiap instruksi memerlukan tiga bagian informasi:
- Alamat program yang akan dipanggil
- Akun yang dibaca atau ditulis oleh instruksi
- Data tambahan yang diperlukan oleh instruksi (misalnya, argumen fungsi)
Contoh Transfer SOL
Diagram di bawah ini menggambarkan transaksi dengan satu instruksi untuk mentransfer SOL dari pengirim ke penerima.
Di Solana, "dompet" adalah akun yang dimiliki oleh System Program. Hanya pemilik program yang dapat mengubah data akun, sehingga mentransfer SOL memerlukan pengiriman transaksi untuk memanggil System Program.
Transfer SOL
Akun pengirim harus menandatangani (is_signer
) transaksi untuk memungkinkan
System Program mengurangi saldo lamport-nya. Akun pengirim dan penerima harus
dapat ditulis (is_writable
) karena saldo lamport mereka berubah.
Setelah mengirim transaksi, System Program memproses instruksi transfer. System Program kemudian memperbarui saldo lamport dari kedua akun pengirim dan penerima.
Proses Transfer SOL
Contoh di bawah ini menunjukkan cara mengirim transaksi yang mentransfer SOL dari satu akun ke akun lainnya. Lihat kode sumber instruksi transfer System Program di sini.
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Create a connection to clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Fund sender with airdropawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: sender.address,lamports: lamports(LAMPORTS_PER_SOL), // 1 SOLcommitment: "confirmed"});// Check balance before transferconst { value: preBalance1 } = await rpc.getBalance(sender.address).send();const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});// Add the transfer instruction to a new transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();const transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));// Send the transaction to the networkconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });const transactionSignature = getSignatureFromTransaction(signedTransaction);// Check balance after transferconst { value: postBalance1 } = await rpc.getBalance(sender.address).send();const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();console.log("Sender prebalance:",Number(preBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient prebalance:",Number(preBalance2) / Number(LAMPORTS_PER_SOL));console.log("Sender postbalance:",Number(postBalance1) / Number(LAMPORTS_PER_SOL));console.log("Recipient postbalance:",Number(postBalance2) / Number(LAMPORTS_PER_SOL));console.log("Transaction Signature:", transactionSignature);
Library klien sering mengabstraksi detail untuk membangun instruksi program. Jika library tidak tersedia, Anda dapat membangun instruksi secara manual. Ini mengharuskan Anda mengetahui detail implementasi dari instruksi tersebut.
Contoh di bawah ini menunjukkan cara membangun instruksi transfer secara manual.
Tab Expanded Instruction
secara fungsional setara dengan tab Instruction
.
const transferAmount = 0.01; // 0.01 SOLconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount * LAMPORTS_PER_SOL});
Pada bagian di bawah ini, kita akan membahas detail tentang transaksi dan instruksi.
Instruksi
Sebuah instruksi pada program Solana dapat dianggap sebagai fungsi publik yang dapat dipanggil oleh siapa saja menggunakan jaringan Solana.
Anda dapat menganggap program Solana sebagai server web yang dihosting di
jaringan Solana, di mana setiap instruksi seperti endpoint API publik yang dapat
dipanggil pengguna untuk melakukan tindakan tertentu. Memanggil instruksi mirip
dengan mengirim permintaan POST
ke endpoint API, memungkinkan pengguna untuk
menjalankan logika bisnis program.
Untuk memanggil instruksi program di Solana, Anda perlu membuat Instruction
dengan tiga bagian informasi:
- Program ID: Alamat program dengan logika bisnis untuk instruksi yang dipanggil.
- Accounts: Daftar semua akun yang dibaca atau ditulis oleh instruksi.
- Instruction Data: Array byte yang menentukan instruksi mana yang akan dipanggil pada program dan argumen apa pun yang diperlukan oleh instruksi.
pub struct Instruction {/// Pubkey of the program that executes this instruction.pub program_id: Pubkey,/// Metadata describing accounts that should be passed to the program.pub accounts: Vec<AccountMeta>,/// Opaque data passed to the program for its own interpretation.pub data: Vec<u8>,}
Instruksi Transaksi
AccountMeta
Saat membuat Instruction
, Anda harus menyediakan setiap akun yang diperlukan
sebagai
AccountMeta
.
AccountMeta
menentukan hal-hal berikut:
- pubkey: Alamat akun
- is_signer: Apakah akun harus menandatangani transaksi
- is_writable: Apakah instruksi memodifikasi data akun
pub struct AccountMeta {/// An account's public key.pub pubkey: Pubkey,/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.pub is_signer: bool,/// True if the account data or metadata may be mutated during program execution.pub is_writable: bool,}
Dengan menentukan di awal akun mana yang dibaca atau ditulis oleh instruksi, transaksi yang tidak memodifikasi akun yang sama dapat dijalankan secara paralel.
Untuk mengetahui akun mana yang diperlukan oleh instruksi, termasuk yang harus dapat ditulis, hanya-baca, atau menandatangani transaksi, Anda harus merujuk pada implementasi instruksi sebagaimana didefinisikan oleh program.
Dalam praktiknya, biasanya Anda tidak perlu membuat Instruction
secara manual.
Sebagian besar pengembang program menyediakan pustaka klien dengan fungsi
pembantu yang membuat instruksi untuk Anda.
AccountMeta
Contoh Struktur Instruksi
Jalankan contoh di bawah ini untuk melihat struktur instruksi transfer SOL.
import { generateKeyPairSigner, lamports } from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});console.log(JSON.stringify(transferInstruction, null, 2));
Contoh berikut menunjukkan output dari cuplikan kode sebelumnya. Format persisnya berbeda tergantung pada SDK, tetapi setiap instruksi Solana memerlukan informasi berikut:
- Program ID: Alamat program yang akan mengeksekusi instruksi.
- Akun: Daftar akun yang diperlukan oleh instruksi. Untuk setiap akun, instruksi harus menentukan alamatnya, apakah harus menandatangani transaksi, dan apakah akan ditulis.
- Data: Buffer byte yang memberi tahu program instruksi mana yang akan dieksekusi dan mencakup argumen yang diperlukan oleh instruksi.
{"accounts": [{"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","role": 3,"signer": {"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC","keyPair": {"privateKey": {},"publicKey": {}}}},{"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p","role": 1}],"programAddress": "11111111111111111111111111111111","data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}
Transaksi
Setelah Anda membuat instruksi yang ingin dipanggil, langkah selanjutnya adalah
membuat Transaction
dan menambahkan instruksi ke transaksi. Sebuah
transaksi
Solana terdiri dari:
- Tanda tangan: Array
tanda tangan
dari semua akun yang diperlukan sebagai penandatangan untuk instruksi dalam
transaksi. Tanda tangan dibuat dengan menandatangani
Message
transaksi dengan kunci privat akun. - Pesan: Pesan transaksi mencakup daftar instruksi yang akan diproses secara atomik.
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
Format Transaksi
Struktur pesan transaksi terdiri dari:
- Header Pesan: Menentukan jumlah penandatangan dan akun hanya-baca.
- Alamat Akun: Array alamat akun yang diperlukan oleh instruksi pada transaksi.
- Blockhash Terbaru: Bertindak sebagai stempel waktu untuk transaksi.
- Instruksi: Array instruksi yang akan dieksekusi.
pub struct Message {/// The message header, identifying signed and read-only `account_keys`.pub header: MessageHeader,/// All the account keys used by this transaction.#[serde(with = "short_vec")]pub account_keys: Vec<Pubkey>,/// The id of a recent ledger entry.pub recent_blockhash: Hash,/// Programs that will be executed in sequence and committed in/// one atomic transaction if all succeed.#[serde(with = "short_vec")]pub instructions: Vec<CompiledInstruction>,}
Ukuran Transaksi
Transaksi Solana memiliki batas ukuran 1232 byte. Batas ini berasal dari ukuran Maximum Transmission Unit (MTU) IPv6 sebesar 1280 byte, dikurangi 48 byte untuk header jaringan (40 byte IPv6 + 8 byte header).
Ukuran total transaksi (tanda tangan dan pesan) harus tetap di bawah batas ini dan mencakup:
- Tanda tangan: 64 byte masing-masing
- Pesan: Header (3 byte), kunci akun (32 byte masing-masing), blockhash terbaru (32 byte), dan instruksi
Format Transaksi
Header Pesan
Header pesan menentukan izin untuk akun dalam transaksi. Header ini bekerja bersama dengan alamat akun yang diurutkan secara ketat untuk menentukan akun mana yang merupakan penandatangan dan mana yang dapat ditulis.
- Jumlah tanda tangan yang diperlukan untuk semua instruksi pada transaksi.
- Jumlah akun bertanda tangan yang hanya-baca.
- Jumlah akun tidak bertanda tangan yang hanya-baca.
pub struct MessageHeader {/// The number of signatures required for this message to be considered/// valid. The signers of those signatures must match the first/// `num_required_signatures` of [`Message::account_keys`].pub num_required_signatures: u8,/// The last `num_readonly_signed_accounts` of the signed keys are read-only/// accounts.pub num_readonly_signed_accounts: u8,/// The last `num_readonly_unsigned_accounts` of the unsigned keys are/// read-only accounts.pub num_readonly_unsigned_accounts: u8,}
Header Pesan
Format Array-Kompak
Array kompak dalam pesan transaksi adalah array yang diserialisasi dalam format berikut:
- Panjang array (dikodekan sebagai compact-u16)
- Item-item array yang tercantum satu demi satu
Format array kompak
Format ini digunakan untuk mengkodekan panjang array Alamat Akun dan Instruksi dalam pesan transaksi.
Array Alamat Akun
Pesan transaksi berisi daftar tunggal dari semua alamat akun yang diperlukan oleh instruksinya. Array dimulai dengan angka compact-u16 yang menunjukkan berapa banyak alamat yang dikandungnya.
Untuk menghemat ruang, transaksi tidak menyimpan izin untuk setiap akun secara
individual. Sebagai gantinya, transaksi mengandalkan kombinasi dari
MessageHeader
dan urutan ketat alamat akun untuk menentukan izin.
Alamat-alamat selalu diurutkan dengan cara berikut:
- Akun yang dapat ditulis dan penandatangan
- Akun yang hanya-baca dan penandatangan
- Akun yang dapat ditulis dan bukan penandatangan
- Akun yang hanya-baca dan bukan penandatangan
MessageHeader
menyediakan nilai-nilai yang digunakan untuk menentukan jumlah
akun untuk setiap grup izin.
Array kompak alamat akun
Blockhash Terbaru
Setiap transaksi memerlukan blockhash terbaru yang memiliki dua tujuan:
- Bertindak sebagai stempel waktu untuk kapan transaksi dibuat
- Mencegah transaksi duplikat
Blockhash kedaluwarsa setelah 150 blok (sekitar 1 menit dengan asumsi waktu blok 400ms), setelah itu transaksi dianggap kedaluwarsa dan tidak dapat diproses.
Anda dapat menggunakan metode RPC
getLatestBlockhash
untuk mendapatkan
blockhash terkini dan tinggi blok terakhir di mana blockhash akan tetap valid.
Array Instruksi
Pesan transaksi berisi array instruksi dalam tipe CompiledInstruction. Instruksi dikonversi ke tipe ini ketika ditambahkan ke transaksi.
Seperti array alamat akun dalam pesan, array ini dimulai dengan compact-u16 yang menunjukkan panjang diikuti oleh data instruksi. Setiap instruksi berisi:
- Indeks ID Program: Indeks yang menunjuk ke alamat program dalam array alamat akun. Ini menentukan program yang memproses instruksi.
- Indeks Akun: Array indeks yang menunjuk ke alamat akun yang diperlukan untuk instruksi ini.
- Instruction Data: Array byte yang menentukan instruksi mana yang akan dipanggil pada program dan data tambahan apa pun yang diperlukan oleh instruksi (misalnya argumen fungsi).
pub struct CompiledInstruction {/// Index into the transaction keys array indicating the program account that executes this instruction.pub program_id_index: u8,/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.#[serde(with = "short_vec")]pub accounts: Vec<u8>,/// The program input data.#[serde(with = "short_vec")]pub data: Vec<u8>,}
Array kompak dari Instruksi
Contoh Struktur Transaksi
Jalankan contoh di bawah ini untuk melihat struktur transaksi dengan satu instruksi transfer SOL.
import {createSolanaRpc,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";const rpc = createSolanaRpc("http://localhost:8899");const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Contoh berikut menunjukkan output pesan transaksi dari cuplikan kode sebelumnya. Format persisnya berbeda tergantung pada SDK, tetapi mencakup informasi yang sama.
{"version": 0,"header": {"numSignerAccounts": 1,"numReadonlySignerAccounts": 0,"numReadonlyNonSignerAccounts": 1},"staticAccounts": ["HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa","5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL","11111111111111111111111111111111"],"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1","instructions": [{"programAddressIndex": 2,"accountIndices": [0, 1],"data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}]}
Setelah mengirimkan transaksi, Anda dapat mengambil detailnya menggunakan metode RPC getTransaction. Responsnya akan memiliki struktur yang mirip dengan cuplikan berikut. Atau, Anda dapat memeriksa transaksi menggunakan Solana Explorer.
"Tanda tangan transaksi" secara unik mengidentifikasi transaksi di Solana. Anda menggunakan tanda tangan ini untuk mencari detail transaksi di jaringan. Tanda tangan transaksi hanyalah tanda tangan pertama pada transaksi tersebut. Perhatikan bahwa tanda tangan pertama juga merupakan tanda tangan dari pembayar biaya transaksi.
{"blockTime": 1745196488,"meta": {"computeUnitsConsumed": 150,"err": null,"fee": 5000,"innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [989995000, 10000000, 1],"postTokenBalances": [],"preBalances": [1000000000, 0, 1],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 13049,"transaction": {"message": {"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"accountKeys": ["8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ","7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma","11111111111111111111111111111111"],"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf","instructions": [{"accounts": [0, 1],"data": "3Bxs4NN8M2Yn4TLb","programIdIndex": 2,"stackHeight": null}],"indexToProgramIds": {}},"signatures": ["3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"]},"version": "legacy"}
Is this page helpful?