BARU: Helius mengakuisisi Light Protocol
Apa itu Solana Virtual Machine (SVM)?
Blog/Dasar-Dasar

Apa itu Solana Virtual Machine (SVM)?

Developer Experience Engineer0xIchigo di X0xIchigo di LinkedIn0xIchigo di GitHub
Bacaan 67 menit

Terima kasih banyak kepada Lostin, Alessandro, Brian, Brady, dan Daniel Cumming karena telah meninjau versi awal karya ini. 

Wawasan yang Dapat Ditindaklanjuti

  • SVM mencakup seluruh stack eksekusi transaksi, tidak seperti EVM, yang secara jelas merujuk pada eksekutor bytecode.
  • Persyaratan agar transaksi mendeklarasikan account yang akan diakses sebelum eksekusi memungkinkan eksekusi paralel di berbagai core CPU dan pasar biaya lokal.
  • Kode sumber Rust dikompilasi melalui rustc menjadi LLVM IR, lalu backend eBPF LLVM, khususnya fork sBPF milik Solana, menurunkannya menjadi bytecode sBPF. Artinya, bahasa apa pun yang memiliki frontend LLVM (misalnya C, C++, Zig) dapat digunakan untuk menulis program Solana.
  • sBPF adalah fork eBPF Linux milik Solana yang, untuk segala tujuan praktis, pada dasarnya sama, dengan beberapa penambahan historis yang pernah diperkenalkan. Namun, penambahan ini akan dibatalkan. Satu-satunya perbedaan penting adalah fungsi eBPF upstream memiliki maksimum 5 argumen, sedangkan fork Solana dapat memiliki lebih banyak.
  • Bytecode sBPF yang telah dikompilasi disimpan dalam file ELF yang berisi bagian untuk instruksi dan konstanta, serta tabel relokasi. Linker menyelesaikan referensi syscall menjadi hash Murmur3 32-bit deterministik dan menulis ulang fungsi internal sebagai lompatan relatif agar portabel. 
  • BPF Loader Upgradeable menggunakan model dua account untuk upgrade dan deployment di tempat, sedangkan Loader V4 yang akan datang menyederhanakannya menjadi satu account dengan kompresi opsional. Semua bytecode diverifikasi secara statis sebelum ditandai sebagai dapat dieksekusi.
  • Transaksi berisi array alamat account dengan izin baca dan tulis, instruksi, serta tanda tangan. Format terstruktur ini memungkinkan deteksi konflik dan penjadwalan transaksi yang tidak berkonflik secara paralel.
  • Banking Stage milik TPU bertanggung jawab menjadwalkan transaksi yang tidak berkonflik secara paralel. Bank memuat status account dari AccountsDB. BPF Loader menyediakan VM sBPF terisolasi dengan wilayah memori dan anggaran komputasi yang dibatasi. Perubahan status yang berhasil diterapkan secara atomik, sedangkan kegagalan dibatalkan sepenuhnya.
  • SVM ISA adalah satu-satunya spesifikasi formal—selain whitepaper Alpenglow dan rangkaian artikel yang awalnya ditulis oleh Toly, tidak ada satu “spesifikasi SVM” tunggal. Runtime muncul dari interaksi antara Bank, scheduler, BPF Loader, dan VM sBPF itu sendiri.

Pendahuluan

Solana Virtual Machine (SVM) adalah salah satu sistem yang paling sering disalahpahami dalam blockchain saat ini. Tidak seperti Ethereum Virtual Machine (EVM), yang secara jelas merujuk pada eksekutor opcode, istilah SVM mencakup seluruh pipeline eksekusi transaksi, mulai dari scheduler Banking Stage hingga interpreter bytecode sBPF itu sendiri. Ambiguitas ini mencerminkan perbedaan arsitektur Solana: tidak ada spesifikasi tradisional yang mendefinisikan “SVM” secara terpisah. Satu-satunya spesifikasi yang mendekatinya berkaitan dengan Solana Virtual Machine Instruction Set Architecture (SVM ISA), yang menjelaskan cara bytecode sBPF harus dieksekusi, tetapi tidak membahas runtime yang lebih luas.

Artikel ini bertujuan menjadi referensi komprehensif tentang apa itu SVM, cara kerjanya, dan mengapa SVM pada dasarnya berbeda dari perspektif implementasi validator Agave milik Anza. Pembahasan tentang cara kerja klien Firedancer serta implementasi virtual machine khusus mereka yang mematuhi SVM ISA berada di luar cakupan tulisan ini.

Kami ingin menelaah codebase yang sebenarnya, bukan spesifikasi abstrak. Kami menelusuri pipeline eksekusi lengkap—cara kode sumber Rust dikompilasi melalui LLVM menjadi bytecode sBPF, cara program di-deploy dan diverifikasi, cara runtime menyediakan lingkungan eksekusi terisolasi untuk eksekusi paralel, serta cara transaksi berinteraksi dengan bytecode yang telah di-deploy.

Beberapa bagian pertama memberikan konteks mengenai ambiguitas SVM dan gambaran umum tingkat tinggi tentang cara kerjanya. Bagian selanjutnya ditujukan bagi pembaca yang lebih teknis dan menginginkan pemahaman mendalam tentang lapisan eksekusi Solana.

Definisi yang Diperdebatkan

Istilah “Solana Virtual Machine” (SVM) telah memicu perdebatan sengit di dalam komunitas, terutama sejak munculnya ekstensi jaringan dan blockchain lapisan lain yang dibangun di atas Solana. Perdebatan berasal dari cakupan istilah tersebut: Apakah SVM hanya interpreter sBPF tingkat rendah, atau mencakup seluruh stack eksekusi transaksi? 

Pandangan sempit memperlakukan SVM sebagai sesuatu yang serupa dengan virtual machine (VM) tradisional, seperti eksekutor opcode EVM. Secara lebih spesifik, virtual machine turunan eBPF (rBPF, kini sBPF) yang menginterpretasikan dan mengompilasi bytecode dengan JIT. Perspektif ini menekankan SVM sebagai eksekutor berbasis register dalam sandbox yang menangani instruksi seperti operasi ALU atau system call khusus Solana. Pada dasarnya, SVM terinspirasi oleh model keamanan eBPF Linux, tetapi disesuaikan untuk infrastruktur blockchain. Hal ini selaras dengan frasa seperti SVM ISA (Instruction Set Architecture) dalam kode validator, dengan SVM yang merujuk pada lapisan VM saja.

Pandangan luas mendefinisikan SVM sebagai seluruh lapisan eksekusi transaksi dari validator Solana. Ini mencakup lebih dari sekadar menjalankan bytecode; cakupannya juga meliputi komponen upstream seperti scheduler Banking Stage, penganggaran unit komputasi, dan pembaruan status melalui database account yang biasa disebut AccountsDB. Inilah “runtime” yang mengubah transaksi mentah menjadi perubahan status tervalidasi.

Ambiguitas muncul karena komunikasi resmi Solana menggunakan istilah “runtime” dan “SVM” secara bergantian, tanpa satu definisi baku. Anza telah memberikan kejelasan yang sangat dibutuhkan dalam perdebatan ini, dengan secara eksplisit mengakui perdebatan tersebut sekaligus mengedepankan perspektif pragmatis dan berorientasi tindakan yang berlandaskan engineering. Kerangka mereka yang memandang SVM sebagai runtime berbasis Bank yang menyediakan VM eBPF menawarkan pandangan lebih luas dan mencakup pipeline, yang dapat digunakan untuk menyusun definisi SVM yang tepat.

Hal ini diformalkan dalam spesifikasi SVM resmi Anza, yang mendefinisikan SVM sebagai “komponen yang bertanggung jawab atas eksekusi transaksi,” dan dikemas sebagai library mandiri untuk validator, bukti fraud, sidecar, dan lainnya.

Untuk keperluan kita, Solana Virtual Machine dapat didefinisikan sebagai:

Antarmuka runtime dan pipeline pemrosesan transaksi yang terpisah di dalam validator Solana, digerakkan oleh komponen Bank, yang mengatur eksekusi instruksi paralel dan program on-chain, serta menyediakan virtual machine khusus berbasis eBPF untuk interpretasi bytecode yang aman, kompilasi JIT, dan pengukuran sumber daya.

Sekilas tentang SVM

Solana Virtual Machine (SVM) berfungsi sebagai lingkungan eksekusi untuk memproses transaksi yang berinteraksi dengan program on-chain di seluruh jaringan. Inilah lapisan runtime tempat kode bertemu status—lingkungan eksekusi yang mengubah transaksi bertanda tangan kriptografis menjadi perubahan status tervalidasi. 

Untuk benar-benar memahami SVM, kita harus terlebih dahulu memahami arti virtual machine dalam konteks blockchain.

Virtual Machine

Virtual machine (VM) adalah perangkat lunak yang memvirtualisasikan atau mengemulasikan sistem komputer, dengan menyediakan lingkungan eksekusi terisolasi yang berperilaku seperti hardware fisik. Konsep ini muncul dari pekerjaan IBM pada tahun 1960-an terkait sistem mainframe, yang memungkinkan beberapa pengguna menjalankan sistem operasi berbeda pada mesin fisik yang sama. Ada dua kategori utama VM: VM sistem dan VM proses. VM sistem menjadi pengganti mesin nyata, sedangkan VM proses dirancang untuk mengeksekusi program dalam lingkungan yang independen dari platform. Untuk keperluan kita, fokusnya adalah virtual machine sistem, dan selanjutnya kita akan menyebutnya “virtual machine” atau cukup “VM”.

Virtual machine menyelesaikan beberapa masalah mendasar. Pertama, VM menawarkan lapisan abstraksi untuk hardware. Artinya, program yang ditulis untuk sebuah VM dapat berjalan pada hardware fisik apa pun yang mendukung VM tersebut tanpa perlu ditulis ulang. Filosofi Java “tulis sekali, jalankan di mana saja” menunjukkan hal ini, karena bytecode Java berjalan secara identik di Windows, macOS, Linux, dan sistem lain yang memasang Java Virtual Machine (JVM).

VM juga menyediakan jaminan isolasi dan keamanan. Setiap instance VM beroperasi dalam sandbox, yang berarti VM tidak dapat mengakses sumber daya sistem host atau VM lain kecuali diizinkan secara eksplisit. Jadi, jika sebuah program mengalami crash atau berisi kode berbahaya, dampaknya terbatas pada instance VM tersebut. Prinsip isolasi ini menjadi alasan penyedia cloud seperti Google Cloud dan AWS menggunakan VM untuk memisahkan workload pelanggan.

VM juga menawarkan keluaran yang dapat diprediksi. Artinya, VM menyediakan lingkungan terkendali tempat input yang sama selalu menghasilkan output yang sama, apa pun hardware yang mendasarinya. Prediktabilitas ini sangat penting untuk debugging, pengujian, dan pencapaian konsensus di antara sistem terdistribusi.

VM juga dapat memiliki performa yang sangat tinggi. VM modern menggunakan kompilasi Just-In-Time (JIT) untuk meminimalkan overhead performa. Kompilasi JIT menerjemahkan bytecode VM menjadi kode mesin native saat runtime untuk mencapai performa mendekati native, sambil mempertahankan portabilitas dan jaminan keamanan yang disebutkan sebelumnya.

Virtual Machine dalam Blockchain

Blockchain mengadaptasi konsep VM untuk mengatasi tantangan unik: bagaimana ribuan komputer independen di seluruh dunia mengeksekusi kode yang tidak tepercaya dan mencapai hasil yang identik? VM berfungsi sebagai lingkungan runtime deterministik yang mengeksekusi smart contract (yaitu, program di Solana) dan mengelola status jaringan (yaitu, status terkini semua account, saldo, dan data lain di seluruh jaringan).

Ketika transaksi dikirimkan ke blockchain, VM bertanggung jawab untuk:

  • Memuat data account yang diperlukan dari penyimpanan.
  • Mengeksekusi bytecode program yang ditentukan dalam transaksi.
  • Mengukur konsumsi sumber daya untuk mencegah loop tak terbatas atau serangan denial-of-service (DoS).
  • Memvalidasi bahwa semua perubahan status mengikuti aturan konsensus jaringan yang telah ditentukan.
  • Menyimpan kembali status yang telah diperbarui ke penyimpanan permanen (yaitu, ledger).

Aturan spesifik tentang cara transisi status berlangsung ditentukan oleh instruction set architecture dan batasan runtime VM.

Cara Kerja SVM

SVM adalah pipeline subsistem yang bekerja bersama untuk mengeksekusi transaksi dengan aman dan efisien. Bank mengatur eksekusi untuk slot tertentu, mengelola status account, menegakkan aturan konsensus, serta mengoordinasikan Banking Stage dan penyimpanan persisten (yaitu, AccountsDB). Setiap Bank merepresentasikan status semua account pada slot tertentu dan melalui tiga siklus hidup: aktif (yaitu, terbuka untuk transaksi baru), dibekukan (yaitu, tidak terbuka untuk transaksi baru karena slot telah selesai), dan menjadi root (yaitu, bagian dari chain kanonis).

Banking Stage adalah tempat eksekusi transaksi berlangsung dalam Transaction Processing Unit (TPU) milik validator. Tahap ini menerima transaksi terverifikasi dari tahap SigVerify, menampungnya dalam buffer, lalu menjadwalkannya untuk eksekusi paralel menggunakan deteksi konflik pada lock account. Thread worker di Banking Stage memproses batch transaksi yang tidak berkonflik, memanggil metode eksekusi Bank untuk memuat account, menyediakan instance VM sBPF bagi setiap instruksi, mengeksekusi bytecode program, dan mengumpulkan hasil. Banking Stage terus memproses batch transaksi yang tidak berkonflik hingga Bank dibekukan pada batas slot. Perhatikan bahwa batch berbeda dari entri, yang merupakan unit transaksi tercatat dan ditulis ke ledger untuk replikasi serta konsensus.

BPF Loader mengelola siklus hidup program: deployment, kompilasi JIT, upgrade, dan eksekusi. Ketika sebuah instruksi menargetkan program tertentu, VM sBPF disediakan dengan wilayah memori dan anggaran komputasinya sendiri, lalu eksekusi diserahkan kepada bytecode program tersebut.

VM sBPF adalah lingkungan eksekusi dalam sandbox tempat bytecode program benar-benar berjalan. VM ini berasal dari eBPF Linux dan menggunakan arsitektur berbasis register dengan 11 register serbaguna. VM menerapkan isolasi memori melalui lima wilayah memori berbeda, masing-masing dengan batas dan izin eksplisit. VM juga mengukur konsumsi unit komputasi untuk mencegah eksekusi tak terkendali dan meneruskan system call untuk operasi berizin seperti kriptografi, logging, atau Cross-Program Invocation (CPI).

AccountsDB adalah lapisan status persisten tempat semua data account berada. Status account dimuat sebelum eksekusi dengan memanfaatkan cache agar account yang sering diakses tidak perlu dibaca berulang kali dari disk. Setelah eksekusi berhasil, pembaruan disimpan kembali ke AccountsDB. Jika eksekusi gagal, semua perubahan status dibatalkan secara atomik.

Bersama-sama, komponen ini membentuk SVM, sebuah mesin eksekusi yang terpisah dan dapat digunakan kembali.

Keistimewaan SVM: Deklarasi Account di Awal

Keputusan arsitektur yang menentukan SVM adalah bahwa semua transaksi harus secara eksplisit mendeklarasikan account yang akan dibaca dan ditulis sebelum eksekusi dimulai. Persyaratan sederhana yang tertanam dalam format transaksi ini membuka dua kemampuan transformatif yang membedakan Solana: eksekusi paralel dan pasar biaya lokal.

Eksekusi Paralel (Sealevel)

Tidak seperti Ethereum Virtual Machine (EVM), yang memproses transaksi secara berurutan—satu per satu, menunggu setiap transaksi selesai sebelum beralih ke transaksi berikutnya—SVM memungkinkan penskalaan horizontal dengan mengeksekusi beberapa transaksi secara bersamaan di berbagai core CPU. Paralelisasi ini dimungkinkan karena semua transaksi Solana secara eksplisit mendeklarasikan account yang akan dibaca dan ditulis sebelum eksekusi dimulai. 

Deklarasi account yang akan dibaca dan ditulis oleh transaksi memungkinkan runtime menganalisis dependensi account untuk mendeteksi konflik dan menjadwalkan transaksi yang tidak berkonflik:

  • Transaksi yang menyentuh account yang sepenuhnya berbeda dapat berjalan secara paralel tanpa overhead koordinasi.
  • Transaksi yang hanya membaca dari account yang sama juga dapat berjalan secara paralel karena operasi baca tidak berkonflik.
  • Transaksi yang mencoba menulis ke account yang sama berjalan secara berurutan untuk mencegah race condition dan memastikan konsistensi status. 

Pasar Biaya Lokal

Karena runtime mengetahui dengan tepat account yang akan diakses setiap transaksi sebelum eksekusi, biaya dapat dilokalkan ke account tertentu alih-alih bersaing secara global di seluruh jaringan. Konsep ini dikenal sebagai pasar biaya lokal. 

Di Ethereum dan chain EVM lainnya, setiap transaksi bersaing dalam satu pasar biaya global—mengirim ETH kepada teman, mencetak NFT, atau berdagang di Uniswap semuanya saling menawar blockspace yang sama. Lonjakan di satu bidang meningkatkan biaya bagi semua orang, terlepas dari apakah mereka sedang mencoba melakukan sesuatu yang sama sekali berbeda. 

Di Solana, hanya transaksi yang mengakses account yang sama yang saling bersaing. Pengguna yang mentransfer SOL antara dua account tidak perlu mengkhawatirkan proses mint NFT populer yang berlangsung pada saat bersamaan. Priority fee sebuah transaksi semata-mata ditentukan oleh kontensi account. Lokalisasi inilah yang membuat transaksi Solana tetap murah, bahkan selama periode aktivitas tinggi.

Sebagai contoh, pada 10 Oktober, pasar kripto mengalami peristiwa likuidasi terbesar sepanjang masa. Meskipun terjadi lonjakan aktivitas yang memecahkan rekor, transaksi Solana tetap relatif murah, dengan median biaya transaksi mencapai $0.007, biaya rata-rata sempat mencapai $0.10, dan 1% transaksi teratas memuncak sedikit di atas $1.00. Dalam periode yang sama, median biaya Ethereum dan Arbitrum melonjak di atas $100, sementara biaya Base memuncak di atas $3.

Pergeseran Paradigma

AspekEVMSVM
ArsitekturVM berbasis stackVM berbasis register (turunan eBPF)
EksekusiBerurutanParalel (deteksi konflik)
Pasar BiayaGlobalLokal (kontensi per account)
Deklarasi AccountTidak diperlukan di awalDiperlukan di awal
ISA~140 opcode, operasi stack~100 opcode, register serupa RISC
Kompilasi JITOpsional (bergantung pada klien)Standar (performa native)
Model StatusBiaya penyimpanan kontrakDatabase account datar
BahasaSolidity/Vyper → bytecode EVMRust/C/C++ → LLVM → sBPF

SVM merepresentasikan pendekatan yang pada dasarnya berbeda terhadap eksekusi blockchain. Bitcoin memperkenalkan uang yang dapat diprogram. Ethereum memperkenalkan smart contract serbaguna dan eksekusi on-chain arbitrer. Namun, keduanya dibatasi oleh eksekusi berurutan dan pasar biaya global—keputusan arsitektur yang memberikan batasan mendasar pada throughput dan biaya.

SVM menyimpang dari batasan tradisional dan menawarkan jaringan yang mampu menangani throughput tinggi tanpa mengorbankan kemampuan pemrograman atau memaksa pengguna mengikuti lelang biaya yang sangat mahal. Keputusan untuk mewajibkan deklarasi account di awal sederhana tetapi sangat efektif, karena memungkinkan eksekusi paralel di berbagai core CPU dan melokalkan biaya ke pasar tingkat account.

Tentu saja, ini bukan satu-satunya optimalisasi yang ditawarkan Solana dibandingkan blockchain lain. Mantra Solana, Tingkatkan Bandwidth, Kurangi Latensi, serta obsesi mendalam untuk mewujudkan impian Internet Capital Markets telah menghasilkan berbagai optimalisasi performa, pilihan desain, dan implementasi untuk mengembangkan jaringan ber-throughput tinggi.

Bagian selanjutnya dari artikel ini membahas secara mendetail cara kerjanya—cara kode sumber Rust dikompilasi menjadi bytecode, cara bytecode tersebut di-deploy dan diverifikasi, serta cara runtime menyediakan lingkungan eksekusi terisolasi untuk menjalankan ribuan program dengan aman secara paralel sambil mempertahankan determinisme dan jaminan keamanan yang ketat. 

Dari Kode Sumber Rust ke Bytecode sBPF: Pipeline Kompilasi

Rust

Rust adalah lingua franca pengembangan program Solana. Framework seperti Anchor menyediakan pendekatan yang tangguh dan terarah bagi developer untuk membangun program aman secara efisien. solana_program ditujukan sebagai library dasar untuk semua program on-chain. Baru-baru ini, Pinocchio, library tanpa dependensi yang sangat dioptimalkan, telah menjadi opsi pilihan bagi developer yang ingin membangun program Solana native. 

Apa pun framework atau library yang digunakan, semua program memiliki entrypoint yang akan dipanggil runtime saat program dijalankan. Makro entrypoint milik solana_program menghasilkan boilerplate standar yang diperlukan untuk memulai eksekusi program. Yaitu, melakukan deserialisasi input serta menyiapkan allocator global dan panic handler. Pinocchio mengekspor makro entrypoint yang berfungsi serupa, tetapi memisahkan entrypoint dari penyiapan heap allocator dan panic handler sehingga memberikan lebih banyak pilihan kepada developer. 

Anatomi Dasar Program 

Program adalah jenis account yang dapat menjalankan kode. Secara lebih spesifik, program adalah account executable yang menyimpan blob bytecode sBPF dalam account milik BPF Loader dengan public key unik. Program dirancang tanpa status: semua data persisten berada dalam account terpisah yang dapat dibaca atau ditulis oleh program saat dipanggil.

SVM mengharuskan semua program memiliki kerangka tertentu—entrypoint yang menerima tiga input:

  • Program ID: Alamat program itu sendiri, yang digunakan untuk pemeriksaan referensi diri (misalnya, kepemilikan).
  • Account: Array metadata account (yaitu, pubkey, saldo lamport, buffer data, pemilik, dan flag). Inilah “status” yang perlu dibaca dan ditulis oleh program.
  • Data Instruksi: Slice byte berisi data arbitrer dari transaksi.

Program diharapkan memproses input ini melalui entrypoint, memutasi account writable yang relevan, menghasilkan log atau event, lalu mengembalikan status berhasil yang menunjukkan apakah semua proses tersebut berhasil dilakukan. Semua ini bermuara pada fungsi process_instruction.

Program sederhana yang ditulis dalam Rust menggunakan crate solana_program terlihat seperti berikut:

simple_rust_program.rs
use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    pubkey::Pubkey,
};

entrypoint!(process_instruction);

pub fn process_instruction(
    _program_id: &Pubkey,
    _accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    msg!("Hello, Solana!");

    Ok(())
}

Di balik layar, semua ini ditentukan oleh Application Binary Interface (ABI) SVM, yang akan kita bahas nanti.

Compiler Rust dan LLVM IR

Rust, seperti banyak bahasa pemrograman lainnya, adalah abstraksi tingkat kedua yang dibangun di atas assembly. Rust dirancang agar manusia dapat menulis kode yang aman, konkuren, dan mudah dibaca tanpa mengelola setiap interaksi hardware secara mendetail. Namun, komputer tidak memahami Rust maupun bahasa tingkat tinggi lainnya. 

Komputer memahami kode mesin—instruksi biner yang disesuaikan untuk arsitektur atau virtual machine tertentu. Semua program pada akhirnya dikonversi menjadi kode biner, dan penerjemahan ini dilakukan oleh komputer. Kompilasi adalah proses penerjemahan multi-tahap yang menghilangkan abstraksi tingkat tinggi, mengoptimalkan efisiensi, dan menghasilkan bytecode executable.

rustc adalah compiler resmi Rust. Sebagian besar developer biasanya tidak berinteraksi langsung dengan rustc; mereka menjalankannya melalui Cargo, package manager Rust. Meskipun demikian, rustc membawa kode sumber Rust melalui tiga tahap utama sebelum menghasilkan bytecode executable. Setiap tahap menghilangkan abstraksi, menerapkan keamanan, dan menyiapkan kode untuk transformasi berikutnya:

  • Parsing dan Ekspansi
  • MIR (Mid-level Intermediate Representation)
  • LLVM IR (Low Level Virtual Machine Intermediate Representation)

Parsing dan Ekspansi

Compiler membaca file Rust tertentu, yang ditandai dengan ekstensi .rs, sebagai teks biasa. Compiler mencari token tertentu (misalnya, use, fn, None, impl, &[u8]) dalam proses yang dikenal sebagai lexing. Tokenisasi leksikal adalah proses mengonversi teks menjadi token leksikal bermakna yang termasuk dalam kategori tertentu (misalnya, identifier, operator, separator, literal, keyword). 

rustc mengambil token leksikal ini dan mengonversinya menjadi struktur data yang dikenal sebagai Abstract Syntax Tree (AST). Struktur seperti pohon ini merepresentasikan struktur kode sumber Rust yang bertingkat dan hierarkis. Artinya, fungsi berisi blok, blok berisi ekspresi, ekspresi berisi operator, dan seterusnya. Meski masih berada di tingkat tinggi, AST memberikan representasi kode sumber dan logika dasarnya secara akurat.

Setelah AST dibuat, compiler melakukan beberapa transformasi utama:

  • Ekspansi Makro: Makro seperti entrypoint! dan println! diperluas menjadi kode Rust mentah sehingga semua makro dikonversi menjadi node AST biasa.
  • Lowering: Sintaks ringkas tingkat tinggi yang dirancang agar kode lebih mudah dibaca ditulis ulang menjadi bentuk yang lebih primitif dalam proses yang disebut lowering. Misalnya, loop for diubah menjadi loop dengan iterasi manual. Hasil lowering adalah High-Level Intermediate Representation (HIR).
  • Pemeriksaan Borrow dan Analisis Keamanan: Rust mengambil HIR dan melakukan pemeriksaan tipe, resolusi trait, serta inferensi tipe. Hasil proses ini adalah Typed High-Level Intermediate Representation (THIR).

Pada tahap ini, compiler menangani kode unsafe. Kode unsafe memungkinkan developer melakukan operasi yang melewati jaminan keamanan Rust (misalnya, melakukan dereference pointer mentah, memanggil fungsi asing, atau mengimplementasikan trait unsafe). Kode ini berfungsi sebagai jalan keluar yang disengaja untuk kontrol tingkat rendah, dengan melonggarkan aturan tertentu sambil tetap mengharuskan kode dikompilasi sesuai semantik Rust. Hal ini penting bagi program Solana, tempat kode unsafe dapat digunakan secara terbatas untuk meningkatkan performa operasi kritis, seperti deserialisasi account zero-copy (misalnya, dalam struct wrapper Pinocchio untuk Account).    

Kode unsafe pertama kali diidentifikasi setelah lexing saat AST dibangun, karena token unsafe dikenali dan ditandai sebagai node khusus. Kode tersebut kemudian ditangani setelah ekspansi AST selama pemeriksaan tipe dan borrow. Compiler memastikan bahwa operasi unsafe terbatas pada konteks unsafe dan menandai error jika tidak demikian (misalnya, “tidak dapat melakukan dereference pointer mentah di luar unsafe”). Sebagai contoh, compiler tidak memeriksa apakah kode unsafe merusak atau salah mengelola memori.

Pada akhir tahap ini, AST yang telah diperluas, kini menjadi THIR, merupakan representasi kode sumber Rust yang telah divalidasi dan diturunkan.

MIR

THIR kemudian diturunkan menjadi Mid-Level Intermediate Representation (MIR), bentuk yang berpusat pada Rust dan merepresentasikan kode sumber sebagai control-flow graph (CFG) yang disederhanakan. Semua sintaks ringkas dan konstruksi kompleks khusus Rust (misalnya, pattern matching, trait, closure) dinyatakan dalam bentuk blok dasar, yang mencakup assignment dan branch. Blok dasar ini dihubungkan oleh lompatan, atau lebih spesifiknya Goto, serta branch, sehingga alur program mudah dianalisis. 

MIR tidak mutlak diperlukan. Compiler sebenarnya dapat langsung menurunkan THIR menjadi LLVM IR. Namun, MIR menyediakan lapisan yang memahami Rust, sehingga compiler dapat menerapkan aturan khusus Rust dan melakukan optimalisasi sebelum optimalisasi generik LLVM diterapkan. Hal ini menjadikan MIR ideal untuk pemeriksaan dan transformasi yang terlalu tinggi tingkatnya bagi LLVM, tetapi terlalu rendah bagi THIR, termasuk:

  • Pemeriksaan Borrow: Pemeriksaan semantik awal dilakukan selama analisis tipe pada THIR. Namun, CFG MIR yang disederhanakan memungkinkan pemeriksaan borrow yang lengkap dan presisi untuk menerapkan semua aturan ownership, borrowing, dan lifetime.
  • Pemeriksaan Move dan Drop: Compiler memastikan semua nilai dipindahkan dan dihapus sesuai jaminan keamanan memori Rust, sehingga mencegah error use-after-free.
  • Analisis Inisialisasi: Compiler memastikan semua variabel diinisialisasi sebelum digunakan.
  • Inlining dan Optimalisasi Awal: Compiler dapat melakukan inline pada fungsi kecil, menyederhanakan ekspresi aritmetika, dan menghapus kode yang tidak dapat dijangkau.

Sebagai contoh, sebelumnya kita memanggil msg!(“Hello, Solana!”) dalam contoh program Rust. Ini adalah makro yang didefinisikan dalam crate solana-program yang, untuk ekspresi tunggal seperti string statis, diperluas selama fase ekspansi makro agar langsung memanggil sol_log($msg), dengan $msg sebagai ekspresinya. Syscall sol_log menerima pointer ke data string beserta panjangnya, lalu mencatatnya ke output SVM tanpa overhead pemformatan. Dalam MIR, ini dapat disederhanakan menjadi:

Kode
bb0: {
  _0 = const "Hello, Solana!"; // Constant string allocation
  _1 = len(_0); // Compute length
  sol_log(move _0, move _1); // Syscall invocation with explicit moves for ownership
  return = Ok(());
}

Di sini,

  • _0 = const “Hello, Solana!”;—MIR memperkenalkan nilai sementara (yaitu, _0) untuk nilai perantara. String diperlakukan sebagai slice konstan yang dialokasikan dalam data read-only.
  • _1 = len(_0);—MIR mengekspos operasi panjang sederhana pada slice untuk kemungkinan constant folding.
  • sol_log(move _0, move _1);—Pemanggilan syscall yang diterjemahkan menjadi urutan instruksi sBPF untuk memuat register dan memanggil ID syscall. Kita akan membahas artinya secara mendetail di bagian selanjutnya, tetapi hal penting yang perlu diperhatikan adalah move ini berkaitan dengan semantik ownership Rust dan menerapkannya saat kompilasi.
  • Return = Ok(());—Mengakhiri blok dengan terminator, yang menandakan keberhasilan kepada SVM.

Fokus MIR pada semantik Rust menjadikannya tempat yang ideal untuk menemukan inefisiensi dan melakukan debugging pada pola yang membutuhkan banyak CU. Misalnya, jika log Anda berisi string dinamis, MIR dapat mengidentifikasi alokasi atau loop tambahan yang dapat dioptimalkan. Developer dapat menggunakan perintah cargo rustc -- -Z dump-mir=all untuk mengekspor MIR.

MIR memastikan kode valid secara semantik, telah dioptimalkan, dan bebas dari semua aturan khusus Rust sebelum akhirnya menurunkannya menjadi LLVM IR.

Perhatikan bahwa proses ini umumnya disebut fase Code Generation. Fase ini tidak selalu menggunakan LLVM. Namun, LLVM umum digunakan dan biasanya menjadi hal yang terpikirkan ketika membahas pembuatan kode Rust. Compiler Rust juga dilengkapi backend GCC dan Cranelift yang masing-masing menghasilkan GIMPLE dan CLIF. Kita berfokus pada LLVM IR dalam konteks Solana, tetapi perlu diperhatikan bahwa hal ini tidak selalu berlaku untuk Rust secara umum.

LLVM IR

LLVM, yang awalnya berarti "Low Level Virtual Machine," merujuk pada framework compiler modular. Alih-alih memiliki satu compiler, LLVM adalah toolkit berisi komponen yang dapat digunakan kembali untuk membangun compiler, optimizer, dan generator kode. Banyak bahasa (misalnya, Rust, C, C++, Julia, Swift, Brainfuck, Zig) menggunakan LLVM untuk memanfaatkan kemampuannya dalam menargetkan beragam arsitektur, mulai dari CPU x86 hingga ISA virtual. 

rustc menerjemahkan MIR menjadi LLVM IR (Low Level Virtual Machine Intermediate Representation)—jembatan antara semantik Rust dan bytecode yang pada akhirnya di-deploy di Solana. Bentuknya jauh lebih dekat dengan kode mesin, dengan alokasi memori eksplisit (yaitu, alloca), store, load, dan pemanggilan fungsi. LLVM IR tidak mengenal ownership, lifetime, atau trait karena abstraksi Rust tersebut telah diperluas hingga tidak lagi ada—jaminan dari tahap sebelumnya tetap dipertahankan ke tahap berikutnya.

Berbagai optimalisasi diterapkan pada tahap ini, termasuk:

  • Constant Folding (yaitu, mengevaluasi konstanta pada waktu kompilasi).
  • Inlining (yaitu, mengganti pemanggilan dengan isi fungsi).
  • Dead Code Elimination (yaitu, menghapus instruksi yang tidak memengaruhi hasil).
  • Loop Unrolling dan Vectorization (yaitu, menulis ulang loop agar dieksekusi lebih cepat).

Dengan demikian, LLVM menyediakan:

  • LLVM IR: Format perantara portabel yang menyerupai assembly.
  • Pass Optimalisasi: Untuk membuat LLVM IR, LLVM menggunakan Static Single Assignment (SSA), yang memastikan setiap variabel diberi nilai tepat satu kali sehingga memungkinkan optimalisasi seperti inlining dan penghapusan dead code.
  • Generator Kode: Target yang menurunkan LLVM IR menjadi kode mesin sebenarnya (yaitu, x86_64, ARM, WebAssembly, eBPF).

Program Rust biasanya dikompilasi untuk target hardware seperti x86_64 atau ARM. Namun, program Solana tidak berjalan langsung pada hardware. Program tersebut berjalan di dalam Solana Virtual Machine. Karena itu, backend LLVM menurunkan LLVM IR menjadi bytecode BPF, yang di Solana menjadi bytecode sBPF (yaitu, fork eBPF yang menghapus fitur nondeterministik dan memperkenalkan syscall khusus Solana).

Perhatikan bahwa meskipun Rust adalah lingua franca pengembangan program Solana, bahasa apa pun yang dapat menargetkan backend BPF LLVM (misalnya, C, Nim, Swift, Zig) dapat digunakan.

eBPF

LLVM IR diturunkan menjadi eBPF, ISA berbasis register yang menjadi fondasi runtime Solana. eBPF (Extended Berkeley Packet Filter) berasal dari Berkeley Packet Filter (BPF), yang dikembangkan pada tahun 1992 oleh Steven McCanne dan Van Jacobson saat berada di Lawrence Berkeley Laboratory untuk sistem Unix Berkeley Software Distribution (BSD). Pada dasarnya, BPF adalah network tap dan filter paket yang memungkinkan paket jaringan ditangkap dan difilter pada tingkat sistem operasi tanpa menyalin data, dengan memanfaatkan qualifier. 

Sejak itu, eBPF berevolusi (yaitu, diperluas) menjadi VM serbaguna dalam sandbox di dalam kernel Linux. Kemampuan yang dibukanya serupa dengan yang dibuka JavaScript bagi pengembangan web—eBPF adalah mesin skrip yang aman untuk kernel. eBPF memungkinkan developer menjalankan program kecil yang telah diverifikasi secara langsung di dalam kernel Linux dengan set instruksi terbatas untuk tugas seperti pemantauan performa, observabilitas, keamanan, dan jaringan.

Hal ini penting karena developer memperoleh:

  • Eksekusi dalam Sandbox: Program eBPF berjalan dalam virtual machine terbatas di dalam kernel, sehingga tidak dapat menyebabkan crash atau merusak memori kernel.
  • Jaminan Keamanan: Bytecode eBPF diverifikasi secara statis sebelum dimuat untuk memastikan tidak terjadi akses memori tidak valid, lompatan di luar batas, atau operasi berizin lainnya, sehingga memberikan keamanan tanpa overhead runtime.
  • Efisiensi: eBPF berbasis register (bukan berbasis stack seperti EVM) dan dapat dikompilasi dengan JIT menjadi kode mesin untuk mencapai kecepatan mendekati native berkat desainnya yang ringan (yaitu, tanpa overhead OS penuh).
  • Fleksibilitas: eBPF mengekspos system call, yang juga dikenal sebagai syscall, yang pada dasarnya merupakan hook ke fitur kernel. Syscall dapat diperluas dengan kemampuan baru tanpa perlu mendesain ulang set instruksi.

Solana memerlukan VM yang deterministik, aman, dan berperforma tinggi untuk menjalankan program yang tidak tepercaya di seluruh kumpulan validatornya. eBPF menawarkan model keamanan teruji, ISA portabel dan efisien yang dirancang untuk menjalankan ribuan program ringan, serta dukungan JIT untuk performa yang lebih baik. Karena itu, alih-alih menciptakan VM yang benar-benar baru, Solana membuat fork eBPF dan menciptakan sBPF.

sBPF 

Awalnya, Solana Labs membuat fork rBPF karya Quentin Monnet untuk menciptakan versi rBPF milik Solana, sehingga setiap validator memiliki format bytecode yang menghasilkan hasil persis sama saat mengeksekusi program dan input tertentu. 

Saat itu, fork eBPF dianggap diperlukan karena konsensus Solana memerlukan eksekusi deterministik dan penggunaan sumber daya yang terbatas. Meskipun eBPF sendiri deterministik, Solana membutuhkan jaminan tambahan dan fungsionalitas khusus blockchain:

  • Waktu dan biaya instruksi tetap.
  • Eksekusi user-space.
  • Runtime deterministik.

Khususnya, sistem ini dirancang untuk berjalan di userspace, bukan kernel, sehingga tidak memerlukan hak istimewa atau modifikasi kernel. Hal ini memungkinkan deployment di berbagai lingkungan OS tanpa memerlukan akses root atau modul kernel khusus. Userspace merupakan pilihan praktis bagi Solana—portabilitas, pengujian, dan deployment yang lebih mudah. Meski berjalan di userspace, JIT tetap mencapai performa mendekati native. Selain itu, eksekusi user-space juga memungkinkan pengujian dan fuzzing dilakukan tanpa akses kernel.

rBPF tidak lagi digunakan. Sebagai gantinya, saat Anza dibentuk, mereka membuat fork rBPF untuk menciptakan sBPF (Solana Berkeley Packet Filter). Repositori GitHub rBPF milik Solana Labs diarsipkan pada 10 Januari 2025.

SVM ISA

SVM ISA (Solana Virtual Machine Instruction Set Architecture) adalah spesifikasi inti yang menentukan cara VM kompatibel Solana (misalnya, sBPF Agave atau implementasi ulang Firedancer) harus mengeksekusi program. Ini bukan VM itu sendiri, melainkan standar atau kontrak yang memastikan konsistensi dan kepatuhan protokol di berbagai implementasi SVM. SVM ISA-lah yang menerapkan batasan keamanan dan determinisme ini pada eBPF, menghapus fitur yang berpusat pada kernel sekaligus menambahkan fungsionalitas khusus blockchain.

ISA mengatur register, encoding instruksi, opcode, class, aturan verifikasi, kondisi panic, dan Application Binary Interface (ABI). Setiap perubahan pada SVM ISA harus diimplementasikan melalui SIMD untuk mendukung evolusi terkendali dari set instruksi ini, sehingga memastikan eksekusi deterministik di seluruh validator.

Register

Register adalah slot penyimpanan kecil di dalam VM yang menyimpan angka atau alamat saat instruksi berjalan, serupa dengan variabel atau kotak berlabel di meja kerja. SVM ISA mendefinisikan arsitektur register 64-bit dengan 11 register serbaguna (R0-R10) dan program counter tersembunyi. Register memiliki lebar 64 bit untuk integer dan alamat, sehingga dapat menangani nilai besar atau pointer secara efisien. R0 menyimpan nilai kembalian fungsi, R1-R5 meneruskan lima argumen fungsi pertama seperti parameter, R6-R9 disimpan oleh callee dan bertahan di seluruh pemanggilan fungsi, sedangkan R10 berfungsi sebagai frame pointer read-only yang menandai stack frame saat ini. Program counter tersembunyi melacak eksekusi dan menunjukkan instruksi yang harus dieksekusi berikutnya.

Instruksi

Instruksi adalah satu operasi yang dapat dilakukan VM, seperti “jumlahkan dua angka ini” atau “lompat ke baris kode ini.” Instruksi memiliki desain serupa RISC dengan sekitar 100 opcode, dibandingkan ribuan dalam arsitektur CISC seperti x86, sehingga verifikasi berlangsung cepat dan kompilasi JIT menjadi efisien.

Instruksi di-encode sebagai nilai 64-bit dalam format Little Endian dengan struktur:

  • opcode: 8 bit
  • dst_reg: 4 bit
  • src_reg: 4 bit
  • offset: 16 bit (signed)
  • immediate: 32 bit (signed)

opcode menunjukkan apa yang harus dilakukan, dst_reg menunjukkan lokasi hasil, src_reg menunjukkan asal input, offset menunjukkan offset memori yang harus dilihat, dan immediate adalah konstanta tambahan yang dapat disertakan dalam instruksi.

lddw, atau load double word, adalah satu-satunya instruksi lebar yang menempati dua slot 64-bit untuk mendukung nilai immediate 64-bit penuh. 

Instruksi dikategorikan ke dalam berbagai class, termasuk operasi memori, operasi aritmetika atau logika, branch kondisional dan tanpa syarat, pemanggilan dan pengembalian fungsi, serta konversi Endianness.

Wilayah Memori

ISA mengidentifikasi lima wilayah memori, masing-masing dengan batas eksplisit (yaitu, [addr, addr+len]) mengenai lokasi yang dapat dibaca atau ditulis oleh program:

  • Kode program: instruksi yang telah dikompilasi itu sendiri (baca + eksekusi).
  • Stack: ruang kerja sementara untuk fungsi (baca + tulis, biasanya 4KB per frame).
  • Heap: memori dinamis yang dapat diminta program (baca + tulis).
  • Data input: byte read-only yang diteruskan bersama transaksi.
  • Data read-only: konstanta dan nilai immutable.

Program memiliki peta memori virtual yang telah ditentukan, sehingga kode program dimulai pada alamat 0x000000000 atau 0x100000000, bergantung pada versi kompilasi, stack frame dimulai pada 0x200000000, heap dimulai pada 0x300000000, dan data input dimulai pada 0x400000000.

Verifier

Verifier melakukan analisis statis sebelum eksekusi—memeriksa setiap kemungkinan jalur kode tanpa menjalankan program—untuk memastikan jaminan keamanan saat pemuatan, bukan saat runtime. Ini mencakup pemeriksaan berikut:

  • Tidak ada instruksi yang tidak dikenal atau tidak didukung.
  • Semua target lompatan berada pada batas instruksi yang valid, dan lompatan mundur ditangani
  • Tidak ada jalur kode yang tidak dapat dijangkau.
  • Batas kedalaman pemanggilan fungsi diterapkan.
  • Pembagian atau modulo dengan nol ditolak secara statis.
  • Program memiliki batas ukuran maksimum dan harus berada dalam batas tersebut.

Meskipun bermanfaat, verifier tidak mencegah developer memperkenalkan perilaku tak terduga. Artinya, developer masih dapat menimbulkan error use-after-free dan buffer overflow, misalnya, dalam program Solana. 

Kondisi Panic

Kondisi panic adalah daftar kasus error runtime yang ditentukan oleh SVM ISA. Kondisi tersebut meliputi:

  • Instruksi tidak valid atau tidak didukung.
  • Pembagian atau modulo dengan nol.
  • Akses memori di luar batas.
  • Akses memori tidak valid untuk wilayah memori yang berbeda (yaitu, pelanggaran izin).
  • Stack overflow.
  • Kedalaman pemanggilan terlampaui.
  • Jumlah maksimum instruksi yang diizinkan terlampaui.
  • Program mengembalikan kode error.

ABI

Application Binary Interface (ABI) adalah kontrak format antara program Solana dan SVM. Meskipun bagian Anatomi Dasar Program sebelumnya menunjukkan cara kerjanya dalam Rust (yaitu, process_instruction dengan tiga input), ABI menentukan cara input dan output tersebut direpresentasikan dalam memori, sehingga memastikan setiap validator dapat mengeksekusi program secara deterministik.

Secara umum, ABI mendefinisikan tiga hal: konvensi entrypoint, konvensi pemanggilan dan register, serta tata letak memori.

Setiap program Solana harus mengekspos fungsi entrypoint. Loader menserialisasi input program ke dalam ruang memori VM dalam urutan kanonis: program ID, array account, dan data instruksi. VM kemudian meneruskan pointer ke wilayah ini melalui entrypoint program.

Lima register pertama (yaitu, R1-R5) dicadangkan untuk argumen entrypoint, sedangkan register kembalian (yaitu, R0) menyimpan kode keluar program. Kode keluar nol dianggap berhasil, sedangkan nilai bukan nol merupakan kegagalan yang dipetakan ke InstructionError tertentu. Hal ini memastikan semua program mengembalikan kode status secara konsisten. Selain itu, parameter setelah lima parameter pertama diteruskan melalui stack. R6-R9 mengikuti konvensi callee-saved, yang berarti fungsi harus mempertahankan nilai tersebut jika menggunakannya.

Account dan data diserialisasi ke dalam memori linear VM sebagai irisan byte. Program harus mendeserialisasikannya menjadi tipe Rust tingkat tinggi (misalnya, AccountInfo, Pubkey). ABI menerapkan batas yang ketat agar program tidak dapat mengakses memori di luar wilayah yang dialokasikan untuknya.

Secara keseluruhan, aturan ini menjadikan ABI sebagai “perekat” yang menghubungkan pengalaman developer tingkat tinggi dengan ISA tingkat rendah. ABI memastikan bahwa signature fungsi Rust sederhana dikompilasi menjadi penggunaan register, tata letak memori, dan kode pengembalian yang tepat, sehingga setiap validator selalu menafsirkan program tertentu dengan cara yang sama persis.

Syscall

ISA sengaja dibuat minimal, tanpa account atau state bawaan. ISA juga tidak menyediakan fungsionalitas tingkat tinggi seperti pencatatan log, hashing, atau pemanggilan lintas program secara langsung. Sebagai gantinya, system call—fungsi khusus yang tertanam dalam VM agar program dapat berinteraksi dengan dunia luar—diekspos. Pemanggilan ini umumnya disebut sebagai syscall.

Syscall dapat dianggap sebagai API yang disediakan oleh VM. Alih-alih mengharuskan setiap program mengimplementasikan ulang primitive kriptografi atau logika account tertentu, syscall mengekspos operasi yang aman dan terstandardisasi serta dijamin berperilaku deterministik di semua validator.

Kategori syscall yang populer meliputi:

  • Pencatatan Log dan Debugging (misalnya, syscall sol_log menulis string UTF-8 ke log program dan digunakan di balik layar oleh msg!).
  • Pemanggilan Lintas Program (CPI) (misalnya, sol_invoke_signed memungkinkan program memanggil program on-chain lain dengan meneruskan account dan data instruksi, yang sangat penting bagi komposabilitas Solana).
  • Kriptografi (misalnya, syscall sol_sha256, sol_keccak256, dan sol_ed25519_verify menyediakan primitive kriptografi yang deterministik dan cepat tanpa mengharuskan developer menulis implementasinya sendiri).
  • Utilitas Memori dan Account (yaitu syscall yang menyediakan helper untuk peminjaman data account, realokasi memori, atau penggunaan alokasi heap milik program).
  • Anggaran dan Pengukuran Komputasi (yaitu setiap syscall menggunakan CU, yang diterapkan oleh sistem pengukuran runtime).

Syscall dipanggil menggunakan instruksi khusus CALL_IMM dengan identifier hash unik. Saat program memanggil syscall, VM sBPF menghentikan eksekusi, mencari hash tersebut dalam registry syscall, lalu meneruskannya ke implementasi native yang berjalan dalam kode runtime dengan hak istimewa. Syscall dieksekusi di luar sandbox dengan akses ke state runtime, yang sepenuhnya berbeda dari cara pemanggilan fungsi biasa dalam program dieksekusi.

Syscall mengikuti ABI yang sama seperti fungsi biasa, dengan lima argumen pertama diteruskan ke register R1 hingga R5 dan nilai pengembalian di R0. Setiap syscall memiliki biaya compute unit tetap untuk memastikan konsumsi resource yang deterministik. Misalnya, semua pemanggilan syscall secp256k1_recover menggunakan 25.000 CU.

Syscall membentuk batas keamanan yang terkontrol karena setiap syscall memvalidasi input dan memeriksa izin terkait sebelum menjalankan operasi dengan hak istimewa. Misalnya, syscall Pemanggilan Lintas Program (CPI) memverifikasi bahwa pemanggil memiliki izin yang sesuai atas account yang diteruskan.

Perlu diperhatikan bahwa syscall baru dapat ditambahkan melalui feature gate tanpa perlu mengubah ISA itu sendiri. Hal ini memungkinkan Solana memperluas kemampuan VM-nya, termasuk dukungan untuk primitive kriptografi baru, sambil mempertahankan kompatibilitas mundur dengan program yang sudah ada.  

Binary Program

Pada akhir kompilasi, seluruh tahap—dari source Rust ke LLVM IR, lalu ke eBPF, sBPF, dan kepatuhannya terhadap ISA SVM—menghasilkan satu output: binary program. Binary inilah yang benar-benar di-deploy ke Solana. 

ELF

Program Solana dikompilasi menjadi file Executable and Linkable Format (ELF), format binary standar yang digunakan di berbagai sistem serupa Unix. Format ELF berfungsi sebagai container yang mengemas semua hal yang dibutuhkan VM untuk mengeksekusi program tertentu sekaligus mempertahankan independensi platform.

File ELF umumnya berisi bagian berikut:

  • Bagian Bytecode: Berisi instruksi sBPF yang telah dikompilasi dalam bagian .text.
  • Bagian Data Hanya-Baca: Menyimpan konstanta, string statis, dan nilai immutable dalam bagian .rodata.
  • Bagian BSS dan Data: Masing-masing berisi variabel global atau statis yang mutable dalam bagian .bss dan .data. Perlu diperhatikan bahwa Solana tidak mengizinkan data mutable. Artinya, ELF dapat memiliki .rodata, tetapi tidak boleh memiliki bagian BSS dan data. 
  • Tabel Simbol dan Relokasi: Menentukan cara pemanggilan fungsi, syscall, dan referensi memori diselesaikan saat pemuatan, dalam bagian .symtab dan .strtab untuk simbol serta bagian .rel.dyn dan .rela.dyn untuk entri relokasi.

Setiap file ELF juga menyertakan header yang menjelaskan arsitektur, lebar instruksi (yaitu 64-bit), endianness (yaitu Little Endian), dan alamat entrypoint.

Linking dan Relokasi

Proses mengubah output compiler menjadi satu file ELF yang dapat dieksekusi melibatkan komponen terakhir: linker. Linker bertanggung jawab menggabungkan beberapa unit kode yang telah dikompilasi menjadi satu binary yang kohesif. Linker juga menyelesaikan semua referensi simbolis (yaitu placeholder) yang belum diselesaikan oleh compiler. Sebagai contoh,

  • Saat program memanggil fungsi seperti sol_log, compiler tidak mengetahui lokasi fungsi tersebut di memori sehingga menggunakan placeholder.
  • Linker mengganti referensi simbolis ini dengan identifier hash unik syscall (yaitu hash Murmur3 32-bit yang deterministik).
  • Demikian pula, pemanggilan antar-fungsi internal ditulis ulang sebagai lompatan relatif ke offset instruksi dalam bagian .text.

Proses penulisan ulang referensi simbolis menjadi alamat konkret atau ID syscall yang di-hash disebut relokasi. Namun, perlu dicatat bahwa relokasi lebih merupakan artefak dari cara tooling awal dibuat, bukan persyaratan mendasar. Bahkan, terdapat rencana untuk sepenuhnya menghapus relokasi dalam versi toolchain mendatang guna menyederhanakan proses deployment.

Langkah relokasi ini diperlukan untuk memastikan binary ELF yang sama berjalan secara identik di semua validator karena tidak ada alamat memori absolut atau simbol khusus sistem yang disematkan. 

Selain itu, bytecode yang relokasinya telah diterapkan disimpan dalam cache memori. Dengan demikian, semua eksekusi berikutnya menggunakan bytecode yang telah diperbarui tanpa perlu memproses ulang relokasi.

Setelah linker menghasilkan file ELF yang telah direlokasi sepenuhnya, program siap di-deploy. Hasil akhirnya adalah binary yang:

  • Portabel: Berjalan secara identik pada validator atau implementasi SVM apa pun.
  • Deterministik: Tidak memuat syscall non-deterministik atau dependensi OS.
  • Mandiri: Membawa semua bytecode dan metadata yang diperlukan untuk eksekusi.

Cara Bytecode Diunggah ke Solana

Setelah program Solana dikompilasi dan ditautkan menjadi file ELF yang valid, langkah berikutnya adalah mengunggahnya ke blockchain agar dapat dieksekusi oleh validator. Proses yang dikenal sebagai deployment program ini melibatkan beberapa komponen yang bekerja bersama: BPF Loader, model account, verifikasi bytecode, dan pengelolaan state.

Program BPF Loader

BPF Loader adalah program native yang memvalidasi, merelokasi, dan menandai file ELF sebagai dapat dieksekusi. Pada dasarnya, program ini mengelola siklus hidup program yang di-deploy—memproses instruksi untuk menginisialisasi account, menulis bytecode, men-deploy program, dan menangani upgrade.

Solana telah berkembang melalui beberapa iterasi loader, dan setiap iterasi menyempurnakan versi sebelumnya:

  • BPF Loader: Loader awal untuk program statis yang tidak dapat di-upgrade, yang tidak lagi didukung. 
  • BPF Loader V2: Loader sederhana tanpa instruksi pengelolaan.
  • BPF Loader Upgradeable: Loader saat ini, yang memperkenalkan kemampuan upgrade program.
  • BPF Loader V4: Iterasi terbaru dengan fitur deployment yang lebih baik, menyederhanakan model dua account saat ini menjadi model satu account.

Arsitektur Deployment: Model Account

Model Account Saat Ini

Loader saat ini menggunakan arsitektur dua account untuk memisahkan logika program dari data program. Hasilnya, terdapat dua account untuk program tertentu: account Program dan account ProgramData.

Account Program adalah account kecil berukuran sekitar 36 byte yang menyimpan metadata dan ditandai sebagai dapat dieksekusi. Account ini menyimpan referensi ke ProgramData melalui UpgradeableLoaderState::Program { programdata_address }.

Account ProgramData adalah account lebih besar yang menyimpan bytecode ELF aktual beserta metadata deployment (misalnya, slot dan alamat otoritas upgrade) melalui UpgradeableLoaderState::ProgramData.

Pemisahan kedua account memungkinkan upgrade langsung di tempat. Artinya, account Program tetap berada di alamat yang sama, sedangkan bytecode account ProgramData dapat diganti.

Model Account Mendatang

Loader V4 bertujuan menyederhanakan proses deployment dengan model satu account. Dalam konsep ini, account program akan langsung menyimpan metadata dan bytecode sehingga account ProgramData terpisah tidak lagi diperlukan. Developer juga dapat menyimpan image yang dikompresi dengan zstd untuk menghemat biaya rent.

Cara Program Solana Di-deploy

Proses deployment mencakup pengunggahan binary ELF yang telah dikompilasi, lalu BPF Loader memverifikasi, menyimpan ke cache, dan menandainya sebagai dapat dieksekusi. Prosesnya sedikit berbeda antara upgradeable loader dan V4 karena arsitektur deployment yang dijelaskan pada bagian sebelumnya.

BPF Loader Upgradeable

Proses deployment saat ini dengan upgradeable loader mencakup inisialisasi account buffer untuk menampung sementara bytecode ELF. Pihak yang melakukan deployment mengirimkan instruksi InitializeBuffer ke BPF Loader Upgradeable, yang membuat account baru milik loader dan mengatur state account menjadi UpgradeableLoaderState::Buffer { authority_address }, serta mencatat alamat yang berwenang menulis ke buffer. 

Binary ELF yang telah dikompilasi diunggah ke buffer dalam beberapa potongan menggunakan instruksi Write { offset, bytes }. Setiap instruksi tulis memverifikasi bahwa penanda tangan sesuai dengan otoritas buffer, memastikan buffer masih mutable (yaitu belum di-deploy), dan menulis byte pada offset yang ditentukan setelah header metadata. Perlu diperhatikan bahwa program besar memerlukan beberapa instruksi Write untuk mengunggah seluruh file ELF karena batas ukuran transaksi.

Setelah buffer memuat ELF lengkap, pihak yang melakukan deployment mengirimkan instruksi DeployWithMaxDataLen { max_data_len }. Ini adalah langkah paling kompleks dalam seluruh proses deployment karena mengorkestrasi deployment aktual, mulai dari validasi account hingga finalisasi state.

Loader terlebih dahulu memvalidasi semua account dalam proses deployment dan memverifikasi bahwa:

  • Account program belum diinisialisasi dan bebas rent.
  • Buffer memuat data yang valid, dan otoritas yang menginisialisasi buffer telah menandatangani transaksi.
  • max_data_len cukup besar untuk menampung data buffer.
  • Ukuran total tidak melebihi MAX_PERMITTED_DATA_LENGTH (yaitu 10 MiB, atau 10.485.760 byte). 

Loader kemudian membuat account ProgramData, dengan memperoleh alamat sebagai PDA menggunakan ID program dan ID loader. Selanjutnya, loader mengembalikan lamport buffer kepada pembayar karena account buffer tidak lagi diperlukan setelah deployment. 

Selain itu, loader membuat account ProgramData melalui CPI ke System Program, dengan mengalokasikan ruang yang cukup untuk metadata dan byte sebesar max_data_len. Loader kemudian menggunakan bump seed PDA untuk menandatangani CPI. 

Macro deploy_program! memastikan bytecode aman untuk dieksekusi. Macro ini terlebih dahulu mengurai struktur file ELF untuk memvalidasi magic byte ELF (yaitu 0x7f ‘E’ ‘L’ ‘F’) dan header (yaitu 64-bit, Little Endian), mengekstrak bagian program, memproses tabel relokasi, serta memvalidasi batas dan alignment bagian. Pemuatan akan langsung gagal jika ELF rusak atau menggunakan fitur yang tidak didukung.

RequisiteVerifier (yaitu verifier sBPF) kemudian melakukan analisis statis pada setiap kemungkinan jalur eksekusi tanpa menjalankan program, sehingga memastikan program terbukti aman sebelum instruksi apa pun dieksekusi. Verifier juga menerapkan batasan ISA SVM yang telah disebutkan. Jika verifikasi gagal, deployment ditolak dengan InstructionError::InvalidAccountData, dan program tidak pernah ditandai sebagai dapat dieksekusi.

Setelah verifikasi berhasil, bytecode dikompilasi dan disimpan dalam cache untuk eksekusi. Fungsi load_program_from_bytes membuat ProgramCacheEntry yang berisi:

  • Executable yang dikompilasi JIT: Bytecode sBPF dikompilasi secara Just-In-Time (JIT) menjadi machine code native untuk arsitektur CPU validator. Hal ini memberikan kecepatan eksekusi mendekati native sambil mempertahankan keamanan.
  • Metadata slot: Waktu deployment dan waktu visibilitas program masing-masing dicatat sebagai deployment_slot dan effective_slot. Penundaan ini mencegah program digunakan pada slot yang sama dengan waktu deployment-nya.
  • Lingkungan runtime: Referensi ke registry syscall untuk menentukan syscall yang tersedia, serta konfigurasi eksekusi yang akan digunakan saat program berjalan. 

Entri cache disimpan dalam program_cache_for_tx_batch, sehingga program tersedia untuk dieksekusi dalam transaksi berikutnya. Setelah program berhasil diverifikasi dan disimpan dalam cache, loader memperbarui state account untuk menyelesaikan deployment. State account ProgramData diperbarui untuk mencatat waktu program di-deploy dan pihak yang berwenang meng-upgrade-nya. Bytecode ELF juga disalin dari buffer ke dalam account. State account Program turut diperbarui untuk menautkannya ke account ProgramData, lalu ditandai sebagai dapat dieksekusi. Terakhir, panjang data buffer diatur ke ukuran metadata, yang secara efektif mengosongkan bytecode dan mengambil kembali ruang.

Program kini telah di-deploy sepenuhnya dan dapat dipanggil oleh transaksi.

BPF Loader V4

BPF Loader V4 menyederhanakan deployment dengan menghapus kebutuhan akan account ProgramData terpisah, sehingga account program dapat menyimpan bytecode secara langsung. Versi ini juga memperkenalkan dukungan untuk penyimpanan ELF yang dikompresi dengan zstd, yang secara signifikan mengurangi biaya rent dan didekompresi sesuai kebutuhan saat pemuatan.

Pihak yang melakukan deployment memanggil SetProgramLength { new_size } untuk mengalokasikan ruang bagi metadata dan bytecode program. Untuk program baru, tindakan ini menginisialisasi account dengan status LoaderV4State::Retracted, mencatat otoritas, dan menandai account sebagai dapat dieksekusi, meskipun belum dapat dipanggil.

Pihak yang melakukan deployment kemudian menulis binary ELF melalui instruksi Write { offset, bytes } langsung ke account program. Penulisan ini hanya diizinkan saat program berada dalam state Retracted. Instruksi Copy juga dapat digunakan untuk menyalin bytecode dari program lain tanpa memandang versi loader, yang berguna untuk migrasi.

Instruksi Deploy kemudian digunakan untuk mengubah program dari state Retracted menjadi Deployed. Pada dasarnya, instruksi ini mengekstrak bytecode dari account program pada offset tersebut dan menjalankan pipeline verifikasi yang sama persis dengan BPF Loader Upgradeable (yaitu penguraian ELF, verifikasi statis, kompilasi JIT, dan caching). Jika verifikasi berhasil, state program diperbarui menjadi LoaderV4Status::Deployed, dan slot deployment dicatat.

Program kini telah di-deploy sepenuhnya dan dapat dipanggil oleh transaksi.

Loader V4 juga menerapkan periode cooldown di antara transisi state (yaitu deploy dan retracted) untuk mencegah serangan deployment ulang. Program tidak dapat di-deploy atau ditarik kembali dalam satu slot sejak deployment terakhirnya. Hal ini membantu mencegah pelaku berbahaya memperbarui program dengan cepat untuk mengeksploitasi race condition atau membingungkan pengguna, sekaligus memastikan atomicity per slot alih-alih penundaan multi-slot. Perlu diperhatikan bahwa cooldown ini berlaku untuk instruksi Deploy dan Retract.

Program juga dapat dibuat immutable melalui instruksi Finalize. Instruksi ini mengubah program dari status Deployed menjadi Finalized, yang berarti program tidak lagi dapat ditarik kembali atau di-upgrade. Field otoritas digunakan kembali untuk menunjuk ke alamat program “versi berikutnya”, sehingga memungkinkan jalur upgrade eksplisit sambil mempertahankan sifat immutable program.

Cara Kerja Eksekusi di SVM

SVM adalah engine pemrosesan transaksi di dalam validator, yang bertanggung jawab mengeksekusi pemanggilan program dan memperbarui state sebagaimana mestinya. 

Saat transaksi tiba di validator, transaksi tersebut melewati pipeline multi-tahap: validasi, pemuatan account, eksekusi program dalam VM sBPF yang terisolasi, verifikasi invariant, dan komitmen state. Jika semua instruksi berhasil, perubahan account ditulis ke AccountsDB. Jika ada instruksi yang gagal, seluruh transaksi di-rollback secara atomik.

SVM beroperasi sebagai engine eksekusi yang terpisah. Artinya, SVM tidak mengelola konsensus, jaringan, atau riwayat ledger. Sebaliknya, SVM hanya berfokus pada eksekusi program secara aman, deterministik, dan efisien. 

Bank mengorkestrasi eksekusi SVM, menyediakan konteks runtime (misalnya blockhash, rent, dan feature set), serta mengommit hasil ke penyimpanan persisten. SVM mengelola eksekusi program, mulai dari pemuatan bytecode hingga penerapan anggaran komputasi. Pemisahan tanggung jawab ini membuat SVM dapat digunakan kembali di luar validator.

Transaksi 

Transaksi adalah sumber kehidupan Solana, atau blockchain apa pun—transaksi memanggil program untuk menerapkan perubahan state. 

Transaksi adalah kumpulan instruksi yang menjelaskan tindakan yang harus dilakukan, account yang terlibat, dan apakah account tersebut memiliki izin yang diperlukan. 

Instruksi adalah arahan untuk satu pemanggilan program. Instruksi merupakan unit terkecil dari logika eksekusi dan berfungsi sebagai unit operasional paling dasar di Solana.

Program menafsirkan data yang diteruskan dari instruksi untuk beroperasi pada account yang ditentukan. Instruksi mencakup ID program (yaitu program yang dipanggil), daftar account yang akan dibaca dan ditulis, serta input yang diteruskan ke program.

Transaksi dimulai saat pengguna menentukan tujuan, seperti mentransfer 10 SOL ke account lain. Maksud ini diterjemahkan menjadi instruksi yang memberi tahu System Program untuk mentransfer 10 SOL dari account A ke account B. Account A akan diteruskan ke transaksi sebagai penanda tangan yang dapat ditulis, sedangkan account B diteruskan sebagai account yang dapat ditulis. Instruksi tersebut kemudian dikemas ke dalam transaksi yang juga menentukan pembayar biaya, penanda tangan, dan blockhash terbaru.

Transaksi tersebut kemudian biasanya dikirim ke penyedia RPC seperti Helius. Node RPC yang menerima transaksi lalu memverifikasi bahwa semua signature yang diwajibkan tersedia dan valid, transaksi belum pernah diproses, blockhash terbaru yang diberikan masih valid, dan transaksi tidak melebihi ukuran maksimum (yaitu 1.232 byte).

RPC kemudian meneruskan transaksi ke Transaction Processing Unit (TPU) milik leader saat ini. 

Transaction Processing Unit (TPU)

Transaction Processing Unit (TPU) adalah pipeline penerimaan dan pemrosesan transaksi dalam validator Solana. TPU memiliki beberapa tahap yang menerima, memverifikasi, menjadwalkan, dan mengeksekusi transaksi sebelum transaksi tersebut dikomit ke ledger Solana.

Untuk pembahasan ini, kita akan mengulas Fetch Stage, SigVerify Stage, dan Banking Stage secara cukup mendetail sebelum beralih ke Bank dan penyediaan VM sBPF. 

Untuk ulasan TPU yang lebih mendetail, lihat Quality of Service Berbobot Stake: Semua yang Perlu Anda Ketahui.

Fetch Stage

Fetch Stage adalah tahap pertama dalam pipeline TPU. Tahap ini menerima semua transaksi masuk melalui koneksi QUIC, yang menggunakan socket UDP sebagai lapisan transportasi dasarnya, lalu mengelompokkannya untuk pemrosesan hilir.

Tiga socket UDP dibuat:

  • tpu: Transaksi reguler seperti transfer token, pencetakan NFT, dan interaksi program.
  • tpu_vote: Transaksi vote dari validator—hal ini akan berubah dengan Alpenglow ketika transaksi vote dihapus.
  • tpu_forwards: Transaksi belum diproses yang diteruskan oleh leader sebelumnya karena tidak sempat diproses.

Socket ini didaftarkan dalam layanan gossip validator dan disimpan dalam struct ContactInfo, sehingga validator dan node RPC lain dapat menemukan tujuan pengiriman transaksi.

Fetch Stage membuat satu thread per socket, yang semuanya terus-menerus:

  • Melakukan polling pada socket UDP untuk paket masuk.
  • Membuat batch berisi 64 paket.
  • Mengirim batch melalui channel tanpa batasnya.

Channel tanpa batas saat ini digunakan untuk meneruskan batch ke tahap hilir, yang berarti channel memiliki kapasitas tak terbatas. Penggunaan channel tanpa batas juga berarti Fetch Stage dapat beroperasi secara independen dari kecepatan pemrosesan hilir. 

Meskipun mencegah paket langsung dibuang saat terjadi lonjakan traffic, hal ini dapat menimbulkan masalah memori: jika tahap hilir tidak dapat mengimbangi Fetch Stage, channel akan bertambah tanpa batas dan berpotensi menyebabkan perlambatan atau crash akibat kehabisan memori (OOM). 

Implementasi channel berbatas dengan backpressure yang tepat sedang dikembangkan agar sistem dapat menandakan kemacetan dan mencegah pertumbuhan memori tanpa batas.

Fetch Stage juga membuat thread lain yang khusus menangani paket yang diteruskan. Paket ini ditandai dengan flag FORWARDED, lalu ditahan atau dibuang berdasarkan jadwal leader:

  • Diproses: Jika validator saat ini akan segera menjadi leader, paket yang diteruskan diproses melalui channel TPU reguler dan dikirim ke tahap berikutnya.
  • Dibuang: Jika validator saat ini tidak akan segera menjadi leader, paket yang diteruskan dibuang untuk mencegah pemrosesan sia-sia. 

Fetch Stage menggunakan PacketBatchRecycler untuk melakukan pra-alokasi 1.000 batch paket, masing-masing berisi 1.024 paket. Hal ini dilakukan untuk mengurangi overhead alokasi memori karena memori batch paket dapat digunakan kembali, alih-alih mengalokasikan batch baru setiap kali. Secara historis, recycler diperlukan untuk memory pinning CUDA, yang kini tidak lagi berfungsi dan sebagian besar dapat dianggap sebagai utang teknis. 

SigVerify Stage

SigVerify Stage adalah tahap kedua dalam pipeline TPU yang, sesuai namanya, bertanggung jawab memverifikasi signature. Tahap ini dilakukan lebih awal dalam pipeline karena verifikasi signature Ed25519 mahal secara komputasi, meskipun tidak semahal eksekusi transaksi. Memverifikasi transaksi sebelum eksekusi memungkinkan validator menolak transaksi palsu, mencegah serangan denial-of-service, dan memastikan hanya transaksi yang tersusun dengan benar yang mencapai Banking Stage.

SigVerify Stage beroperasi sebagai satu thread yang terus menerima paket dari channel Fetch Stage dan memprosesnya melalui pipeline verifikasi. Meskipun hanya menggunakan satu thread, terdapat banyak paralelisme internal.

Secara default, signature diverifikasi pada CPU menggunakan iterator paralel. Hal ini dilakukan untuk mendistribusikan verifikasi ke seluruh core CPU yang tersedia, sehingga setiap core dapat memverifikasi sebagian signature secara independen. 

Verifikasi signature juga dapat dialihkan ke GPU jika library performa terdeteksi melalui perf_libs::api(). GPU hanya dapat digunakan jika terdapat sedikitnya 64 paket dan 90% diperkirakan valid. Alasannya, GPU memiliki overhead sekitar ~15–20 md untuk penyiapan dan transfer, sedangkan CPU dapat memverifikasi 64 signature dalam waktu sekitar ~10–20 md. Harus diakui, fitur ini terbukti tidak praktis dalam produksi karena overhead latensinya, sehingga jauh lebih lambat daripada verifikasi CPU untuk workload realistis. Terdapat rencana untuk menghapus jalur kode yang tidak digunakan ini.

Proses verifikasinya relatif sederhana:

  • Menerima batch paket dari channel tanpa batas milik Fetch Stage.
  • Membuang transaksi secara acak melalui load shedding jika volume paket melebihi 165.000.
  • Menghapus transaksi duplikat.
  • Membuang paket berlebih agar tidak ada satu IP pun yang dapat memonopoli bandwidth verifikasi.
  • Mengecilkan dan mengatur ulang batch terlebih dahulu untuk meningkatkan lokalitas cache dan mengurangi pemborosan memori.
  • Memverifikasi signature.

Semua paket yang valid diteruskan ke Banking Stage. 

Banking Stage

Banking Stage adalah tempat eksekusi transaksi berlangsung. Di sini, transaksi disimpan dalam buffer, dijadwalkan, dan dieksekusi oleh thread worker paralel.

Pola scheduler pusat digunakan untuk memisahkan penanganan transaksi vote dan non-vote:

  • Satu thread worker menangani transaksi vote dan gossip vote.
  • Empat thread worker memproses semua transaksi non-vote.
  • Satu thread mengoordinasikan distribusi pekerjaan di antara thread worker.

Paket masuk dideserialisasi dan disimpan dalam buffer hingga 100.000 transaksi. Scheduler terus menerima transaksi baru sambil mengelola buffer ini, serta membuang transaksi kedaluwarsa atau tidak valid selama operasi pembersihan antrean yang memeriksa hingga 10.000 transaksi sekaligus.

Scheduler menentukan urutan eksekusi transaksi menggunakan deteksi konflik (yaitu transaksi yang ingin memperoleh lock baca dan tulis untuk account yang sama). Terdapat dua implementasi scheduler yang tersedia secara default:

  • PrioGraphScheduler: Scheduler yang membangun grafik prioritas untuk mendeteksi konflik account.
  • GreedyScheduler: Scheduler default dengan pendekatan FIFO yang lebih sederhana untuk mengurutkan transaksi.

Scheduler kemudian memilih transaksi yang tidak berkonflik dan mengirimkannya ke thread worker. Setiap worker menerima batch dan mulai memprosesnya.

Orkestrasi Bank

Bank merepresentasikan state seluruh account pada slot tertentu. Bank adalah struktur data pusat yang mengelola data account, menerapkan aturan runtime, dan bertindak sebagai orkestrator antara worker Banking Stage dan VM sBPF untuk eksekusi transaksi. 

Siklus Hidup

Setiap Bank melewati tiga state:

  • Aktif: Bank yang baru dibuat dan terbuka untuk transaksi. Worker Banking Stage menerapkan transaksi hingga Bank mencapai jumlah tick target atau semua entri dalam slot telah diproses.
  • Dibekukan: Setelah jumlah tick tercapai atau semua entri telah diproses, Bank dibekukan. Tidak ada lagi transaksi yang dapat diterapkan. Pada tahap ini, biaya transaksi diakumulasikan kepada leader blok, account sysvar diperbarui, dan hash bank akhir dihitung.
  • Di-root: Setelah Bank yang dibekukan menerima cukup vote dari validator, Bank tersebut menjadi rooted. State kini difinalisasi dan menjadi bagian dari ledger chain.

Setiap Bank, kecuali Bank genesis, menunjuk kembali ke Bank induk sehingga membentuk struktur pohon yang merepresentasikan berbagai fork ledger. 

Alur Eksekusi

Worker Banking Stage beroperasi pada working bank saat ini (yaitu Bank aktif dan belum dibekukan yang sedang dibangun untuk slot saat ini). Saat worker menerima batch transaksi dari scheduler, Bank mengorkestrasi eksekusinya:

  • Penguncian Account: Worker memanggil fungsi prepare_sanitized_batch_with_results() untuk mengunci semua account yang direferensikan dalam transaksi guna mencegah modifikasi serentak.
  • Pemuatan Account: Bank mengambil data account dari AccountsDB untuk semua account yang dikunci.
  • Pemotongan Biaya: Bank memotong biaya transaksi dari account pembayar biaya sebelum eksekusi.
  • Validasi: Bank memvalidasi bahwa blockhash masih baru, memeriksa state account nonce, dan memverifikasi kepemilikan account.
  • Penyerahan ke VM: Bank memanggil load_and_execute_transactions(), yang meneruskan account yang telah dimuat ke VM sBPF.
  • Eksekusi: VM sBPF mengeksekusi bytecode program dari setiap instruksi.
  • Hasil: VM sBPF mengembalikan seluruh output eksekusi (yaitu LoadAndExecuteTransactionOutput).
  • Komitmen: Bank menulis kembali state account yang diperbarui ke AccountsDB melalui bank.commit_transactions().

Eksekusi kini memasuki SVM yang sebenarnya (yaitu VM sBPF). Setelah Bank memanggil load_and_execute_transactions(), transaksi melewati pipeline multi-tahap tempat setiap instruksi diproses menggunakan instance VM sBPF baru dan terisolasi yang disediakan untuk mengeksekusi bytecode program.

Instruksi dalam transaksi dieksekusi secara berurutan. Pipeline untuk setiap instruksi adalah:

  • Menemukan Program: Mencari account program dari ID program instruksi.
  • Memuat dari Cache: Memeriksa apakah program tersebut sudah dikompilasi JIT dalam cache program. 
  • Menyediakan VM: Membuat instance VM sBPF terisolasi dengan wilayah memori dan anggaran komputasi tertentu.
  • Mengeksekusi Bytecode: Menjalankan entrypoint program dengan input instruksi.
  • Memverifikasi Invariant: Memastikan eksekusi tidak melanggar aturan runtime apa pun.
  • Mengumpulkan Hasil: Mengemas hasil eksekusi.

Pemuatan Program

Sebelum dapat dieksekusi, program harus dimuat dari account on-chain, diverifikasi, dan dikompilasi menjadi machine code native. Proses ini berlangsung melalui cache program dan pipeline kompilasi JIT.

Cache program adalah pengoptimalan performa yang menghindari pemuatan dan kompilasi ulang program pada setiap pemanggilan. Cache ini dipertahankan pada tingkat batch transaksi dan menyimpan objek ProgramCacheEntry, yang berisi:

  • Executable yang Dikompilasi JIT: Machine code native untuk arsitektur CPU validator.
  • Metadata Deployment: Slot ketika program di-deploy dan mulai berlaku.
  • Lingkungan Runtime: Referensi ke registry syscall dan konfigurasi eksekusi.

Saat transaksi mereferensikan ID program, urutan pencarian berikut dijalankan:

  • Memeriksa Cache Batch Transaksi: Mencari versi yang telah disimpan dalam cache dan dikompilasi JIT.
  • Memeriksa Cache Program Global: Jika tidak ada dalam cache batch, memeriksa cache global validator.
  • Memuat dari Account: Jika semua cache tidak menemukan hasil, memuat account program dari AccountsDB.
  • Mengurai ELF: Mengekstrak bytecode dari data account program.
  • Memverifikasi Bytecode: Menjalankan verifier statis untuk memastikan keamanan.
  • Mengompilasi JIT: Menerjemahkan bytecode sBPF menjadi machine code native.
  • Menyimpan Entri dalam Cache: Menyimpan program yang telah dikompilasi untuk pemanggilan berikutnya.

Artinya, pemanggilan pertama program yang baru di-deploy menanggung seluruh biaya pemuatan, verifikasi, dan kompilasi, sedangkan pemanggilan berikutnya langsung mengeksekusi kode native yang tersimpan dalam cache. 

Saat cache tidak menemukan hasil, program harus dimuat dari account on-chain. Untuk program milik BPF Loader Upgradeable, account program berisi referensi ke account ProgramData, yang kemudian dimuat dari AccountsDB. Untuk model satu account Loader V4, bytecode dimuat langsung dari account program dan mungkin perlu didekompresi jika dikompresi dengan zstd.

Byte ELF yang diekstrak diurai untuk menemukan bytecode executable dan bagian yang telah disebutkan (yaitu .text, .rodata, .data / .bss, .symtab / .strtab). Setelah ELF berhasil diurai, RequisiteVerifier (yaitu penganalisis statis sBPF) memverifikasi setiap kemungkinan jalur eksekusi tanpa benar-benar menjalankan program, sebagaimana dijelaskan pada bagian sebelumnya.

Kompilasi JIT

Setelah verifikasi berhasil, bytecode dikompilasi secara Just-In-Time (JIT) menjadi machine code native. Compiler JIT menerjemahkan setiap instruksi sBPF menjadi instruksi CPU native yang setara untuk arsitektur validator. 

Kompilasi JIT memungkinkan VM sBPF memiliki performa yang memadai untuk menangani throughput tinggi Solana. Tanpa JIT, VM harus menginterpretasikan bytecode sBPF instruksi demi instruksi, yang menimbulkan overhead signifikan. 

Setiap instruksi bytecode harus diambil, didekode, dan diteruskan ke kode handler, sehingga menambah overhead interpretasi. Selanjutnya, kode yang diinterpretasikan tidak dapat memanfaatkan pengoptimalan tingkat CPU seperti pipelining, branch prediction, atau out-of-order execution. Akibatnya, setiap instruksi sBPF menjadi pemanggilan fungsi dalam interpreter.

Kompilasi JIT sepenuhnya menghilangkan biaya ini dengan menghasilkan machine code native yang berjalan langsung pada CPU. Hasilnya adalah performa mendekati native, pemeriksaan batas yang dioptimalkan, pengukuran komputasi inline, pengoptimalan tingkat hardware, dan pemetaan alokasi register yang bersih.

Compiler JIT melakukan penerjemahan satu lintasan dari bytecode sBPF ke machine code native. Untuk setiap instruksi sBPF:

  • Instruksi didekode untuk mengekstrak opcode, register, offset, dan nilai immediate.
  • Menyiapkan stack frame dan menyimpan register callee-saved.
  • Memetakan operasi sBPF ke instruksi CPU yang setara agar setiap operasi dikompilasi menjadi instruksi native. Misalnya, dalam pemetaan x86-64, rax dipetakan ke R0 dan rbp dipetakan ke R10.
  • Menghasilkan pemeriksaan batas dan translasi alamat software untuk akses memori—penerjemahan alamat guest ke alamat host merupakan salah satu operasi VM paling lambat karena overhead-nya.
  • Mempertahankan isolasi stack (yaitu stack guest berada dalam buffer yang dialokasikan di heap, bukan stack host).
  • Menyisipkan pengurangan CU dan pemeriksaan anggaran untuk menghasilkan pengukuran komputasi.
  • Memulihkan register dan membersihkan stack.

Penerjemahan Instruksi

Compiler JIT menerjemahkan berbagai jenis instruksi (yaitu aritmetika, akses memori, operasi penyimpanan, percabangan kondisional, dan dispatch syscall) dengan cara tertentu. 

Operasi aritmetika dipetakan langsung ke satu instruksi CPU native tanpa overhead karena register sBPF dapat dipetakan dengan baik ke register hardware validator yang sesuai. 

Operasi akses memori memerlukan pemeriksaan batas untuk mencegah pembacaan dan penulisan di luar batas. Compiler JIT menghasilkan kode validasi yang memeriksa batas bawah dan atas setiap akses memori terhadap batas wilayah yang valid. 

Hal ini pada dasarnya melibatkan 3 hingga 6 instruksi native yang menghitung alamat efektif, memverifikasi bahwa alamat tersebut berada dalam batas yang diharapkan, lalu melakukan pemuatan aktual. Proses ini ditangani dengan relatif efisien melalui branch prediction karena pelanggaran batas jarang terjadi. 

Operasi penyimpanan mencakup pemeriksaan batas dan validasi izin tulis. Kode yang dikompilasi memverifikasi bahwa alamat target berada dalam batas dan wilayah memori memiliki izin tulis sebelum menulis ke memori.

Percabangan kondisional dikompilasi menjadi instruksi lompatan kondisional native. Compiler JIT menyelesaikan semua target lompatan selama kompilasi sehingga offset instruksi relatif sBPF dikonversi menjadi alamat absolut dalam kode native. 

Dispatch syscall memerlukan penyimpanan state VM—seluruh 11 register—dan pemanggilan handler syscall native. Setelah itu, state VM dipulihkan bersama nilai pengembaliannya. Overhead pengelolaan state inilah yang menyebabkan syscall memiliki biaya CU tetap yang lebih tinggi daripada instruksi biasa.

Pengukuran Compute Unit

Compiler JIT menyisipkan pelacakan compute unit secara langsung ke dalam kode yang dihasilkan. Setiap instruksi sBPF menyertakan pemeriksaan anggaran untuk memastikan anggaran belum habis saat instruksi mengurangi jumlah CU yang tersisa. 

Penyisipan inline ini menghindari overhead pemanggilan fungsi dan dapat dilakukan secara efisien melalui branch prediction, out-of-order execution (yaitu pemeriksaan CU dan operasi dapat dijalankan secara paralel), serta paralelisme tingkat instruksi.

Untuk syscall dengan biaya variabel (misalnya syscall sol_sha256 yang diskalakan berdasarkan panjang data), penghitungan biaya dilakukan di dalam implementasi syscall native sebelum kembali ke VM.

Menyimpan Kode Hasil Kompilasi dalam Cache

Setelah kompilasi JIT selesai, executable native disimpan dalam ProgramCacheEntry, yang berisi:

  • Kode hasil kompilasi JIT
  • Metadata slot deployment dan slot efektif
  • Referensi registry syscall
  • Konfigurasi lingkungan runtime

Entri cache ditempatkan dalam cache batch transaksi dan cache program global. Cache pertama tersedia bagi semua instruksi dalam batch saat ini, sedangkan cache kedua tersedia untuk semua transaksi mendatang. 

Entri cache dapat di-invalidasi saat program di-upgrade (yaitu bytecode baru di-deploy), account program ditutup, feature gate mengubah ketersediaan syscall, atau kapan pun validator memutuskan untuk mengosongkan cache.

Penundaan Slot Efektif

Perlu diperhatikan bahwa program yang di-deploy pada slot n tidak dapat dipanggil hingga slot n + 1. Penundaan ini memastikan semua validator mengamati deployment, cache program tersinkronisasi di seluruh jaringan, dan atomicity per slot diterapkan. 

Penyediaan VM sBPF

Setelah program dimuat dan dikompilasi JIT, atau diambil dari cache, VM sBPF baru disediakan untuk setiap eksekusi instruksi. Penyediaan ini berlangsung di BPF Loader dan mencakup penyiapan lima wilayah memori berbeda, inisialisasi anggaran komputasi, dan pendaftaran syscall.

Wilayah Memori

Seperti disebutkan dalam bagian ISA SVM, VM membuat lima wilayah memori berbeda. Bersama-sama, wilayah ini membentuk sandbox terisolasi tempat program Solana dieksekusi. Wilayah memori program biasanya dimulai pada alamat 0x100000000. Wilayah ini berisi kode native hasil kompilasi JIT yang akan dieksekusi—yaitu machine code aktual, bergantung pada arsitektur CPU validator. Jika JIT dinonaktifkan untuk debugging, wilayah ini berisi bytecode sBPF yang diinterpretasikan. Izin untuk bagian ini adalah hanya baca dan eksekusi.

Data hanya-baca juga disertakan dalam 0x100000000. Bagian ini berisi konstanta dan string statis yang diekstrak dari bagian .rodata ELF selama pemuatan program. Tujuan wilayah memori ini adalah menyediakan akses efisien ke konstanta waktu kompilasi tanpa memerlukan alokasi heap. 

Stack dimulai pada 0x200000000 dan berisi variabel lokal, frame pemanggilan fungsi, serta alamat pengembalian. Di sinilah komputasi sementara berlangsung selama eksekusi. Stack tumbuh ke bawah dari bagian atas wilayah, dengan register R10 (yaitu frame pointer) menandai batas frame saat ini. Izin baca dan tulis diperbolehkan untuk wilayah ini, dan ukurannya ditetapkan sebesar 4 KB per frame pemanggilan. Melebihi batas ini akan memunculkan error StackAccessViolation, yang menunjukkan stack overflow. Ukuran stack yang lebih kecil mendorong developer untuk menggunakan heap atau, lebih baik lagi, menyimpan data dalam account daripada mengandalkan penyimpanan berbasis stack.

Heap dimulai pada 0x300000000 dan berisi memori yang dialokasikan secara dinamis untuk struktur data runtime yang tidak muat dalam stack atau account. Ukurannya berkisar dari default 32 KB hingga maksimum 256 KB. Sebelumnya, program dapat memperluas heap melalui syscall sol_alloc_free. Namun, syscall sol_alloc_free telah dihentikan dan dinonaktifkan untuk deployment program baru. Program harus menentukan ukuran heap yang diperlukan pada waktu deployment, bukan memperluasnya secara dinamis. 

Catatan: pertumbuhan heap menggunakan compute unit berdasarkan rumus (heap_size / 32KB) * 8,000 CUs, dengan biaya heap default sebesar 8 CU.

Wilayah memori data input dimulai pada alamat 0x400000000. Wilayah ini berisi parameter entrypoint yang telah diserialisasi dan diterima program saat dipanggil. Ini adalah wilayah memori hanya-baca dengan ukuran aktual yang bervariasi berdasarkan transaksi. Tiga komponen yang diserialisasi adalah pubkey 32-byte dari program yang dipanggil, array account, dan data instruksi.

Penerapan Akses Memori

Setiap instruksi pemuatan dan penyimpanan memori diperiksa batasnya oleh VM. Sebelum setiap akses memori, VM memverifikasi bahwa alamat berada dalam rentang valid wilayah tersebut dan bahwa jenis akses (yaitu baca atau tulis) diizinkan untuk wilayah itu. 

Eksekusi langsung dihentikan dengan error AccessViolation jika salah satu pemeriksaan gagal. Seluruh transaksi di-rollback dan tidak ada perubahan state yang dikomit. 

Penerapan ini hampir tidak menimbulkan biaya runtime karena compiler JIT mengompilasi pemeriksaan tersebut menjadi kode native efisien yang dapat dieksekusi CPU secara langsung. Branch prediction modern menangani pemeriksaan secara efisien karena pelanggaran jarang terjadi. 

Inisialisasi Anggaran Komputasi

Setiap instance VM diinisialisasi dengan anggaran compute unit yang membatasi total pekerjaan yang dapat dilakukan program tertentu. Model eksekusi berbatas ini memastikan program tidak dapat berjalan tanpa henti dan semua validator mengeksekusi transaksi dalam rentang waktu yang dapat diprediksi.

Parameter anggaran saat ini adalah sebagai berikut:

  • Batas Unit Komputasi Default per Instruksi: 200.000 CU.
  • Batas Maksimum Unit Komputasi per Transaksi: 1.400.000 CU.
  • Batas Instruksi Bawaan: 3.000 CU.

CU default per transaksi adalah min(1_400_00, (200_000*non_reserve_instructions + 3_000*reserve_instructions)). Pada dasarnya, nilai ini adalah yang terkecil antara batas maksimum unit komputasi dan biaya default per jenis instruksi berdasarkan instruksi yang diberikan.

Anggaran komputasi melacak unit yang tersisa selama eksekusi. Saat setiap instruksi sBPF dieksekusi, biaya CU-nya dikurangkan dari anggaran yang tersisa. Jika anggaran mencapai nol sebelum program selesai, eksekusi langsung dihentikan dengan InstructionError::ComputationalBudgetExceeded. Transaksi gagal dan tidak ada perubahan state yang diterapkan, tetapi pembayar biaya tetap dikenai biaya transaksi untuk mengompensasi validator yang memproses transaksi tersebut. 

Registri Syscall

Pemetaan antara pengidentifikasi hash Murmur 32-bit unik setiap syscall dan implementasi Rust native-nya juga dibuat selama proses penyediaan VM agar semua syscall yang tersedia terdaftar. 

Hal berikut terjadi saat program mengeksekusi instruksi CALL_IMM dengan hash syscall:

  • VM menjeda stream instruksi sBPF.
  • Dispatcher syscall menemukan implementasi dalam registri menggunakan hash.
  • Syscall memverifikasi bahwa pemanggil memiliki izin yang diperlukan (misalnya, untuk menggunakan CPU atau terkait kepemilikan account).
  • Implementasi Rust syscall dieksekusi dengan akses runtime penuh di luar sandbox.
  • Biaya tetap syscall dikurangkan dari anggaran komputasi yang tersisa.
  • Hasil ditempatkan dalam register R0, lalu eksekusi sBPF dilanjutkan. 

Eksekusi Program

Setelah VM sBPF disediakan, eksekusi dimulai pada fungsi entrypoint program. Untuk program yang dikompilasi dengan JIT, VM langsung beralih ke kode mesin native dan membiarkan CPU validator mengeksekusinya secara native. Seperti dijelaskan pada bagian sebelumnya, kode yang dikompilasi dengan JIT menyertakan semua instrumentasi yang diperlukan—pemeriksaan batas memori, pengukuran komputasi, dan validasi alur kontrol, yang seluruhnya di-inline untuk performa maksimum.

Register R1 berisi pointer ke wilayah data input tempat tiga parameter yang diserialisasi (yaitu pubkey program yang dipanggil, array account, dan data instruksi) berada. 

Catatan: data account diakses melalui pointer, bukan disalin. Penggunaan pointer memungkinkan program membaca dan mengubah data account secara langsung, yang sangat penting untuk performa.

Setiap pemanggilan fungsi mengalokasikan stack frame baru sebesar 4 KB, dengan register R10 diperbarui agar menunjuk ke frame baru. Komputasi diukur sesuai anggaran komputasi yang telah disebutkan.

Pemanggilan Lintas Program (CPI)

Selama eksekusi, program dapat memanggil program lain melalui Pemanggilan Lintas Program (CPI)—fondasi komposabilitas SVM. 

CPI dimulai melalui syscall sol_invoke_signed, yang memerlukan 1.000 CU ditambah biaya tambahan berdasarkan data account yang diserialisasi dan diteruskan. Data account dan serialisasi data instruksi sama-sama memerlukan 250 byte per CU.

Saat program melakukan CPI, konteks eksekusi baru dengan stack frame instruksinya sendiri dibuat. Pada saat artikel ini ditulis, kedalaman maksimum stack instruksi adalah 5, atau 9 jika SIMD-0268 diaktifkan. Artinya, sebuah program dapat memanggil program lain, yang kemudian dapat memanggil program lainnya lagi hingga mencapai batas kedalaman. Setiap pemanggilan bertingkat mempertahankan kumpulan account yang dapat ditulis dan hak istimewa penandatangannya sendiri. CPI dapat memiliki maksimum 16 penanda tangan dan meneruskan 128 struct AccountInfo. 

Pemanggil melakukan serialisasi ID program target, account, dan data instruksi, lalu memanggil syscall. Eksekusi program saat ini dijeda, dan instance VM sBPF baru disediakan untuk program yang dipanggil dengan mengikuti proses penyediaan yang sama seperti yang telah disebutkan. Eksekusi program yang dipanggil kemudian dimulai. Program tersebut berjalan dengan anggaran komputasinya sendiri yang diambil dari sisa anggaran pemanggil. Artinya, pemanggilan CPI berbagi total anggaran komputasi transaksi.

Program dapat menandatangani atas nama account yang dimilikinya melalui Program Derived Addresses (PDA). Saat memanggil dengan sol_invoke_signed, pemanggil memberikan seed yang membuktikan kepemilikan PDA. Derivasi PDA diverifikasi sebelum otoritas penandatanganan diberikan kepada program yang dipanggil. 

Setelah program yang dipanggil selesai, kontrol kembali ke pemanggil. Perubahan account yang dibuat oleh program tersebut dapat dilihat oleh pemanggil, sehingga state dapat mengalir melalui rantai pemanggilan. Jika salah satu program dalam rantai CPI gagal, seluruh transaksi dibatalkan dan semua perubahan state dikembalikan.

Catatan: sol_invoke adalah helper yang memanggil sol_invoke_signed tanpa seed.

Verifikasi Pascaeksekusi

Setelah eksekusi program selesai, baik secara langsung maupun sebagai bagian dari rantai CPI, beberapa pemeriksaan pascaeksekusi dilakukan untuk memastikan konsistensi state dan invariansi keamanan. 

Misalnya, runtime memverifikasi bahwa semua account yang ditandai sebagai dapat ditulis benar-benar dimiliki oleh program atau telah ditandatangani dengan benar—program tidak dapat mengubah account yang bukan miliknya, kecuali account tersebut secara eksplisit ditandai sebagai dapat ditulis dan pemiliknya memberikan izin. Hal ini mencegah perubahan state tanpa otorisasi.

Runtime juga memeriksa bahwa jumlah total lamport di seluruh account dalam transaksi tetap sama, kecuali lamport secara eksplisit ditransfer melalui instruksi System Program. Pemeriksaan konservasi ini mencegah program membuat atau memusnahkan lamport sehingga total suplai SOL tetap konstan.

Runtime memvalidasi bahwa semua account dengan flag executable (yaitu program) tidak diubah. Data program tidak dapat diubah selama eksekusi normal dan hanya dapat ditingkatkan melalui mekanisme otoritas peningkatan BPF Loader.

Hasil Eksekusi

Setelah verifikasi pascaeksekusi selesai, hasil eksekusi dikembalikan ke pemroses transaksi Bank. Hasil tersebut berisi status berhasil atau error (masing-masing berupa hasil nol atau bukan nol), jumlah unit komputasi yang digunakan, dan semua perubahan pada state account. 

Bank menerapkan semua perubahan account secara atomik untuk eksekusi yang berhasil. Data account, saldo lamport, dan metadata yang diperbarui ditulis ke AccountsDB dan dapat dilihat dalam transaksi berikutnya. Unit komputasi yang digunakan dicatat untuk penghitungan biaya transaksi dan metrik jaringan.

Tidak ada perubahan state yang diterapkan untuk transaksi yang gagal—tidak ada pengembalian sebagian di Solana karena seluruh transaksi di-rollback. Namun, biaya transaksi tetap dikurangkan dari account pembayar biaya untuk mengompensasi validator atas pekerjaan komputasi yang dilakukan. Kode error dan unit komputasi yang digunakan dicatat dalam metadata transaksi untuk keperluan debugging dan analisis.

Hasil eksekusi mengalir kembali melalui scheduler Banking Stage, yang memperbarui metrik internalnya dan melanjutkan ke transaksi berikutnya. Transaksi yang berhasil maupun gagal dicatat ke dalam stream Proof of History dan berkontribusi pada blok yang sedang dibangun. Transaksi yang gagal disertakan untuk membantu mencegah serangan replay dan mempertahankan riwayat transaksi yang lengkap.

Saat slot selesai dan mencapai jumlah tick maksimum, Bank beralih ke state beku. Pembekuan adalah operasi satu arah yang mencegah transaksi baru diterapkan dan menghitung hash Bank. Perlu diperhatikan bahwa beku tidak berarti final—slot tersebut mungkin masih berada di fork yang kemudian dibuang. 

Bank menjadi rooted saat validator memanggil BankForks::set_root() untuk menetapkannya sebagai bagian dari chain kanonis. Rooting memicu operasi squash yang meratakan state account Bank yang telah di-root ke AccountsDB, menggabungkan seluruh state induk, dan menjadikannya permanen dari perspektif validator. Fork yang tidak di-root dipangkas dan dibuang. Bahkan Bank yang telah di-root belum final dari perspektif cluster karena adanya perbedaan tingkat commitment di Solana.

Menatap ke Depan

Solana Virtual Machine menghadirkan pendekatan yang secara fundamental berbeda terhadap eksekusi blockchain—blockchain yang skalabel dan tersedia bagi semua orang berkat pemrosesan paralel, pasar biaya lokal, serta runtime turunan eBPF yang berperforma tinggi dan deterministik. 

Untuk memahami SVM, seluruh pipeline eksekusi perlu ditelaah, mulai dari kompilasi source code Rust ke LLVM, sBPF, hingga penyediaan instance VM yang terisolasi. 

Tidak ada satu “spesifikasi” tunggal yang mendefinisikan SVM. Sebaliknya, SVM terbentuk dari interaksi antara Bank, scheduler, BPF Loader, VM sBPF, dan ISA SVM.

Masa depan tampak menjanjikan seiring SVM terus berkembang. 

Toolchain Solana sedang dirombak sepenuhnya untuk menghilangkan infrastruktur LLVM khusus yang selama bertahun-tahun menyulitkan proses onboarding developer. 

Pendekatan saat ini mengharuskan developer menginstal toolchain khusus melalui skrip spesifik platform. Solusinya adalah mengadopsi toolchain yang sama dengan yang digunakan oleh Aya, library eBPF untuk Rust. Developer akan dapat menjalankan dua perintah sederhana untuk mengompilasi langsung ke bytecode eBPF:

Kode
rustup toolchain install nightly
cargo build --target=bpfel-unknown-none

Tanpa skrip. Tanpa fork LLVM khusus. Hanya tooling Rust standar yang mengompilasi langsung ke bytecode eBPF menggunakan target upstream bpfel-unknown-none, dengan memanfaatkan pengembangan kernel Linux selama bertahun-tahun dan peningkatan infrastruktur LLVM yang tak terhitung jumlahnya.

ISA SVM juga akan diperbarui melalui SIMD-0377, yang mengusulkan penyelarasan implementasi eBPF Solana (yaitu sBPF) dengan standar eBPF modern. Hal ini mencakup pengenalan varian instruksi JMP32, operasi pembagian dan modulo bertanda, lompatan tidak langsung, serta stack frame dinamis. Perubahan ini akan membantu mengurangi biaya komputasi, meningkatkan kompatibilitas dengan infrastruktur LLVM upstream, dan memungkinkan pembuatan kode yang lebih efisien. 

Pada dasarnya, SVM adalah sistem yang diukur melalui anggaran komputasi. SIMD-0370 berpotensi mengubah cara kerja ini dengan menghapus batas komputasi tingkat blok dan, mungkin, batas transaksi. Penghapusan batas komputasi ini akan memungkinkan produsen blok memaksimalkan throughput berdasarkan kemampuan hardware mereka, bukan batas artifisial. Jika digabungkan dengan mekanisme timeout Alpenglow, perubahan ini akan memungkinkan kekuatan pasar, alih-alih batasan tingkat protokol, menentukan ukuran blok yang optimal. Tentu saja, ini masih merupakan rencana yang sangat jauh ke depan karena Anza ingin menaikkan batas CU menjadi 100 juta+ terlebih dahulu sebelum menghapus batas tersebut.

Semua perubahan ini dilandasi etos builder untuk terus mendorong batas kemampuan blockchain tanpa mengorbankan keamanan, determinisme, atau desentralisasi. 

SVM bukan sekadar interpreter bytecode—SVM adalah pipeline eksekusi lengkap yang merevolusi kemampuan blockchain. SVM merupakan hasil akhir dari berbagai keputusan arsitektur yang memprioritaskan throughput dan latensi rendah. Seiring Solana semakin matang, SVM akan terus berkembang untuk mendukung aplikasi berperforma tinggi dan efisien dalam penggunaan modal.

Impian Pasar Modal Internet memerlukan infrastruktur yang mampu memenuhi persyaratan throughput, latensi, dan biaya sistem keuangan global. Solana Virtual Machine merupakan langkah penting untuk mewujudkan visi tersebut.

Sumber Daya Tambahan

Berlangganan Helius

Ikuti perkembangan terbaru dalam pengembangan Solana dan dapatkan pembaruan saat kami memublikasikan postingan

Gambar diperbesar