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 DisederhanakanTransaksi 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:
    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. 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 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);
Console
Click to execute the code.

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 SOL
const 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.
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

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

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.

AccountMetaAccountMeta

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));
Console
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.
  • 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:

  1. 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.
  2. Pesan: Pesan transaksi mencakup 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:

  • 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.
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>,
}

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 TransaksiFormat 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.

  1. Jumlah tanda tangan yang diperlukan untuk semua instruksi pada transaksi.
  2. Jumlah akun bertanda tangan yang hanya-baca.
  3. Jumlah akun tidak bertanda tangan yang hanya-baca.
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 demi 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 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:

  1. Akun yang dapat ditulis dan penandatangan
  2. Akun yang hanya-baca dan penandatangan
  3. Akun yang dapat ditulis dan bukan penandatangan
  4. Akun yang hanya-baca dan bukan penandatangan

MessageHeader menyediakan nilai-nilai yang digunakan untuk menentukan jumlah akun untuk setiap grup izin.

Array kompak alamat akunArray kompak alamat akun

Blockhash Terbaru

Setiap transaksi memerlukan blockhash terbaru yang memiliki dua tujuan:

  1. Bertindak sebagai stempel waktu untuk kapan transaksi dibuat
  2. 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:

  1. Indeks ID Program: Indeks yang menunjuk ke alamat program dalam array alamat akun. Ini menentukan program yang memproses instruksi.
  2. Indeks Akun: Array indeks 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 apa pun 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));
Console
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
}
}
]
}

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.

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