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.

Transaksi DisederhanakanTransaksi Disederhanakan

Transaksi seperti amplop yang berisi formulir. Setiap formulir adalah instruksi yang memberi tahu jaringan apa yang harus dilakukan. Mengirim transaksi seperti mengirimkan amplop untuk memproses formulir-formulir tersebut.

Poin Penting

  • Transaksi Solana mencakup instruksi yang memanggil program di jaringan.
  • Transaksi bersifat atomik - jika ada instruksi yang gagal, seluruh transaksi gagal dan tidak ada perubahan yang terjadi.
  • Instruksi pada transaksi dieksekusi secara berurutan.
  • Batas ukuran transaksi adalah 1232 byte.
  • Setiap instruksi memerlukan tiga bagian informasi:
    1. Alamat program yang akan dipanggil
    2. Akun yang dibaca atau ditulis oleh instruksi
    3. 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 SOLTransfer 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 SOLProses Transfer SOL

Contoh di bawah ini menunjukkan cara mengirim transaksi yang mentransfer SOL dari satu akun ke akun lainnya.

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 cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const 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 airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { 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 network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { 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);
Click to execute the code.

Library klien sering kali 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.

  • Kit
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});
  • Legacy
const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = SystemProgram.transfer({
fromPubkey: sender.publicKey,
toPubkey: receiver.publicKey,
lamports: transferAmount * LAMPORTS_PER_SOL
});
  • Rust
let transfer_amount = LAMPORTS_PER_SOL / 100; // 0.01 SOL
let transfer_instruction =
system_instruction::transfer(&sender.pubkey(), &recipient.pubkey(), transfer_amount);

Pada bagian di bawah ini, kita akan membahas detail tentang transaksi dan instruksi.

Instruksi

Sebuah instruction pada program Solana dapat dianggap sebagai fungsi publik yang dapat dipanggil oleh siapa saja yang menggunakan jaringan Solana.

Memanggil instruksi program memerlukan tiga informasi penting:

  • Program ID: Program dengan logika eksekusi untuk instruksi tersebut
  • Akun: Daftar akun yang dibutuhkan oleh instruksi
  • Instruction Data: Array byte yang menentukan instruksi yang akan dipanggil pada program dan argumen yang diperlukan oleh instruksi
Instruction
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 TransaksiInstruksi Transaksi

AccountMeta

Setiap akun yang diperlukan oleh instruksi harus disediakan sebagai AccountMeta yang berisi:

  • pubkey: Alamat akun
  • is_signer: Apakah akun harus menandatangani transaksi
  • is_writable: Apakah instruksi memodifikasi data akun
AccountMeta
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,
}

AccountMetaAccountMeta

Dengan menentukan di awal akun mana yang dibaca atau ditulis oleh instruksi, transaksi yang tidak memodifikasi akun yang sama dapat dieksekusi secara paralel.

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 keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
console.log(JSON.stringify(transferInstruction, null, 2));
Click to execute the code.

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.
  • Accounts: Daftar akun yang diperlukan oleh instruksi. Untuk setiap akun, instruksi harus menentukan alamatnya, apakah harus menandatangani transaksi, dan apakah akan ditulisi.
  • 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

Sebuah transaksi Solana terdiri dari:

  1. Signatures: Array tanda tangan yang disertakan pada transaksi.
  2. Message: Daftar instruksi yang akan diproses secara atomik.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Format TransaksiFormat Transaksi

Struktur pesan transaksi terdiri dari:

Message
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>,
}

Pesan TransaksiPesan Transaksi

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 fragmen).

Total ukuran transaksi (tanda tangan dan pesan) harus tetap di bawah batas ini dan mencakup:

  • Signatures: 64 byte masing-masing
  • Message: Header (3 byte), kunci akun (32 byte masing-masing), recent blockhash (32 byte), dan instruksi

Format TransaksiFormat Transaksi

Message Header

Message header menggunakan tiga byte untuk mendefinisikan hak istimewa akun.

  1. Tanda tangan yang diperlukan
  2. Jumlah akun hanya-baca yang ditandatangani
  3. Jumlah akun hanya-baca yang tidak ditandatangani
MessageHeader
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 PesanHeader Pesan

Format Array Kompak

Array kompak dalam pesan transaksi adalah array yang diserialisasi dalam format berikut:

  1. Panjang array (dikodekan sebagai compact-u16)
  2. Item-item array yang tercantum satu per satu

Format array kompakFormat array kompak

Format ini digunakan untuk mengkodekan panjang array Alamat Akun dan Instruksi dalam pesan transaksi.

Array Alamat Akun

Pesan transaksi berisi array alamat akun yang diperlukan oleh instruksinya. Array dimulai dengan angka compact-u16 yang menunjukkan berapa banyak alamat yang dikandungnya. Alamat-alamat tersebut kemudian diurutkan berdasarkan hak istimewanya, sebagaimana ditentukan oleh header pesan.

  • 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

Array kompak alamat akunArray kompak alamat akun

Blockhash Terbaru

Setiap transaksi memerlukan blockhash terbaru yang memiliki dua tujuan:

  1. Bertindak sebagai stempel waktu
  2. Mencegah transaksi duplikat

Blockhash kedaluwarsa setelah 150 blok (sekitar 1 menit dengan asumsi waktu blok 400ms), setelah itu transaksi tidak dapat diproses.

Anda dapat menggunakan metode RPC getLatestBlockhash untuk mendapatkan blockhash terkini dan tinggi blok terakhir di mana blockhash akan valid. Berikut contohnya di Solana Playground.

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, ini dimulai dengan panjang compact-u16 diikuti oleh data instruksi. Setiap instruksi berisi:

  1. Indeks ID Program: Indeks u8 yang menunjuk ke alamat program dalam array alamat akun. Ini menentukan program yang akan memproses instruksi.
  2. Indeks Akun: Array indeks u8 yang menunjuk ke alamat akun yang diperlukan untuk instruksi ini.
  3. Instruction Data: Array byte yang menentukan instruksi mana yang akan dipanggil pada program dan data tambahan yang diperlukan oleh instruksi (misalnya argumen fungsi).
CompiledInstruction
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 InstruksiArray 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 keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Click to execute the code.

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
}
}
]
}

Ketika Anda mengambil transaksi menggunakan tanda tangannya setelah mengirimkannya ke jaringan, Anda akan menerima respons dengan struktur berikut.

Bidang message berisi bidang-bidang berikut:

  • header: Menentukan hak istimewa baca/tulis dan penandatangan untuk alamat dalam array accountKeys

  • accountKeys: Array dari semua alamat akun yang digunakan dalam instruksi transaksi

  • recentBlockhash: Blockhash yang digunakan untuk memberi stempel waktu pada transaksi

  • instructions: Array instruksi yang akan dieksekusi. Setiap account dan programIdIndex dalam instruksi mereferensikan array accountKeys berdasarkan indeks.

  • signatures: Array yang mencakup tanda tangan untuk semua akun yang diperlukan sebagai penandatangan oleh instruksi pada transaksi. Tanda tangan dibuat dengan menandatangani pesan transaksi menggunakan kunci privat yang sesuai untuk akun.

Transaction Data
{
"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?

Daftar Isi

Edit Halaman