Solana Blockchain in Rust
Core Interview Questions
1. What is Solana and how does it differ from other blockchains?
Solana is a high-performance blockchain designed for scalability without sacrificing decentralization. Unlike Ethereum which uses proof-of-work (historically) or proof-of-stake with a single chain, Solana uses Proof of History (PoH) combined with Proof of Stake to achieve high throughput.
Key differences:
- Throughput: Solana processes 50,000+ TPS vs Ethereum's ~15-30 TPS (pre-sharding)
- Finality: ~400ms confirmation time vs ~12+ seconds for Ethereum
- Consensus: Proof of History (PoH) creates a cryptographic clock that orders transactions without traditional consensus overhead
- Architecture: Uses SeaLevel runtime for parallel transaction processing vs EVM's sequential execution
- Fees: Average transaction fee of $0.00025 vs much higher on Ethereum
2. What is Proof of History and why is it important?
Proof of History is a cryptographic sequence that proves the passage of time between events. It uses a verifiable delay function (VDF) based on SHA-256 hashes in a sequential chain.
Why it matters:
- Precomputable timestamps: Nodes don't need to agree on time—PoH provides a provable ordering
- Efficient consensus: Reduces messaging overhead since nodes can agree on transaction order without extensive communication
- Parallel processing: Knowing the time relationship between transactions enables parallel execution
- Network efficiency: Validators don't need to broadcast their entire state—just the PoH sequence
Code concept:
#![allow(unused)] fn main() { // Simplified PoH hash chain struct PoH { current_hash: [u8; 32], sequence_number: u64, } impl PoH { fn tick(&mut self) -> [u8; 32] { self.current_hash = hash(self.current_hash); self.sequence_number += 1; self.current_hash } } }
3. What are accounts in Solana and how do they differ from Ethereum's account model?
Solana uses a account-based model but with key differences from Ethereum:
Solana Accounts:
- All data is stored in accounts - programs (smart contracts), user state, and system configuration
- Accounts are rent-exempt - you must deposit enough SOL to cover "rent" for 2 years, or the account is garbage collected
- Accounts have explicit owners - each account is owned by a program (smart contract)
- Size is fixed - accounts have fixed data size, though they can be reallocated
- Arbitrary data - accounts can hold any raw bytes, not just balance/state
Ethereum Accounts:
- Smart contracts are separate from state - code and storage are different concepts
- No rent - accounts exist permanently once created
- Balance + storage - accounts have ETH balance and contract storage
- Dynamic storage - smart contracts can grow their storage
Rust example:
#![allow(unused)] fn main() { use solana_program::account_info::AccountInfo; // Account structure in Solana #[derive(Debug)] pub struct Account { pub lamports: u64, // Balance in lamports (1 SOL = 1 billion lamports) pub data: Vec<u8>, // Raw account data pub owner: Pubkey, // Owner program (smart contract) pub executable: bool, // Whether this account holds executable code pub rent_epoch: Epoch, // Epoch when account will be eligible for rent collection } }
4. What is the Solana Runtime (SeaLevel) and how does parallel execution work?
SeaLevel is Solana's runtime that enables parallel transaction execution. Unlike the EVM which executes sequentially, SeaLevel can process multiple transactions simultaneously when they don't conflict.
How it works:
- Transaction analysis - Runtime analyzes which accounts each transaction reads/writes
- Conflict detection - Transactions writing to the same account must execute sequentially
- Parallel execution - Transactions touching disjoint accounts execute in parallel
- Result assembly - Results are committed in PoH order
Benefits:
- Throughput - Multiple transactions process simultaneously
- Predictability - Developers know which accounts their transactions touch
- Optimization - Developers can structure programs to minimize conflicts
Rust example:
#![allow(unused)] fn main() { use solana_program::account_info::AccountInfo; // Transaction specifies which accounts it uses #[solana_program] pub fn process_transaction( accounts: &[AccountInfo], // All accounts this transaction touches instruction_data: &[u8], // Instruction data ) -> ProgramResult { // Runtime can parallelize if these accounts don't conflict let account1 = &accounts[0]; let account2 = &accounts[1]; // Transaction logic here Ok(()) } }
5. What is the BPF Loader and how does Solana execute smart contracts?
Solana smart contracts are called programs and are compiled to eBPF (extended Berkeley Packet Filter) bytecode, which the BPF Loader executes in a JIT-compiled environment.
Key points:
- Compile to BPF - Rust programs compile to BPF bytecode using
solana-program - JIT compilation - BPF Loader compiles to machine code at first execution
- Sandboxed execution - BPF provides memory safety and resource limits
- No gas - Instead of gas, Solana uses rent and compute units
- Fast execution - BPF is designed for performance and safety
Rust example:
#![allow(unused)] fn main() { use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, }; // Entry point - all BPF programs must have this entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { // Program logic here msg!("Hello from Solana BPF program!"); Ok(()) } }
6. What are PDAs (Program Derived Addresses) and how do they work?
Program Derived Addresses are deterministic addresses derived from a program ID and optional seeds (bump seeds). They look like public keys but have no private key—only the program that derived them can sign for them.
Why PDAs matter:
- Program-controlled accounts - PDAs allow programs to control accounts without needing private keys
- Deterministic address generation - Same seeds + program = same PDA
- Cross-program invocations - Programs can sign for PDAs owned by other programs
- State management - PDAs are ideal for storing program state
Rust example:
#![allow(unused)] fn main() { use solana_program::pubkey::Pubkey; // Derive a PDA fn find_pda(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(seeds, program_id) } // Usage let program_id = Pubkey::new_unique(); let user_account = Pubkey::new_unique(); let seeds = [ b"user_state", user_account.as_ref(), ]; let (pda, bump) = find_pda(&seeds, &program_id); // Later, validate the PDA let (expected_pda, expected_bump) = find_pda(&seeds, &program_id); assert_eq!(pda, expected_pda); }
7. What is rent in Solana and how does it work?
Rent is the fee you pay to store data on Solana's blockchain. To keep an account alive, you must deposit enough SOL to cover rent for ~2 years.
Key concepts:
- Rent-exempt - Most accounts are rent-exempt (2 years of rent paid upfront)
- Rent collection - Unpaid rent is collected periodically, and the account is deleted
- Refundable - If you close an account, you get the rent-exempt balance back
- Calculates based on size - Larger accounts require more rent
Rust example:
#![allow(unused)] fn main() { use solana_program::rent::Rent; // Calculate rent for an account fn calculate_rent_exemption(data_size: usize) -> u64 { let rent = Rent::default(); rent.minimum_balance(data_size) } // Usage let account_data_size = 100; // bytes let rent_exempt_amount = calculate_rent_exemption(account_data_size); // Account must have at least this many lamports to be rent-exempt }
Code Snippets
Basic Solana Program Structure
use solana_program::{ account_info::next_account_info, account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, msg, }; entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { msg!("Program started"); // Parse accounts let accounts_iter = &mut accounts.iter(); let payer = next_account_info(accounts_iter)?; let system_account = next_account_info(accounts_iter)?; msg!("Instruction data: {:?}", instruction_data); Ok(()) } fn main() { println!("This is a Solana BPF program"); }
Simple Counter Program
use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, msg, }; entrypoint!(process_instruction); // Counter account structure #[derive(Debug, PartialEq)] pub struct Counter { pub count: u64, } fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { let accounts_iter = &mut accounts.iter(); let counter_account = next_account_info(accounts_iter)?; // Verify ownership if counter_account.owner != program_id { msg!("Counter account is not owned by this program"); return Err(ProgramError::IncorrectProgramId); } // Parse instruction let instruction = instruction_data[0]; match instruction { 0 => { // Increment counter let mut counter_data = counter_account.data.borrow_mut(); let mut counter = Counter::try_from_slice(&counter_data) .unwrap_or(Counter { count: 0 }); counter.count += 1; counter.serialize(&mut counter_data.as_mut())?; msg!("Counter incremented to {}", counter.count); } 1 => { // Reset counter let mut counter_data = counter_account.data.borrow_mut(); let counter = Counter { count: 0 }; counter.serialize(&mut counter_data.as_mut())?; msg!("Counter reset"); } _ => { msg!("Invalid instruction"); return Err(ProgramError::InvalidInstructionData); } } Ok(()) } // Serialization helpers use solana_program::borsh::try_from_slice_with_schema; trait BorshSerialize: borsh::ser::BorshSerialize {} trait BorshDeserialize: borsh::de::BorshDeserialize {} fn main() { println!("Solana Counter Program"); }
PDA Derivation and Validation
use solana_program::{ pubkey::Pubkey, program_error::ProgramError, msg, }; // Derive PDA for user state pub fn derive_user_state_pda(user: &Pubkey, program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address( &[ b"user_state", user.as_ref(), ], program_id, ) } // Validate PDA in instruction processing pub fn validate_pda( pda: &Pubkey, seeds: &[&[u8]], program_id: &Pubkey, bump: u8, ) -> Result<(), ProgramError> { let (expected_pda, expected_bump) = Pubkey::find_program_address(seeds, program_id); if pda != &expected_pda { msg!("Invalid PDA"); return Err(ProgramError::InvalidAccountData); } if bump != expected_bump { msg!("Invalid bump"); return Err(ProgramError::InvalidAccountData); } Ok(()) } fn main() { let program_id = Pubkey::new_unique(); let user_pubkey = Pubkey::new_unique(); // Derive PDA let (user_state_pda, bump) = derive_user_state_pda(&user_pubkey, &program_id); println!("User state PDA: {}", user_state_pda); println!("Bump seed: {}", bump); // Validate PDA let seeds = &[b"user_state", user_pubkey.as_ref()]; match validate_pda(&user_state_pda, seeds, &program_id, bump) { Ok(_) => println!("PDA is valid"), Err(e) => println!("PDA validation failed: {:?}", e), } }
CPI (Cross-Program Invocation)
use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, program::invoke, program_error::ProgramError, pubkey::Pubkey, system_instruction, msg, }; entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], ) -> ProgramResult { let accounts_iter = &mut accounts.iter(); let payer = next_account_info(accounts_iter)?; let recipient = next_account_info(accounts_iter)?; let system_program = next_account_info(accounts_iter)?; // Make a CPI to System Program let transfer_instruction = system_instruction::transfer( &payer.key, &recipient.key, 1_000_000, // 0.001 SOL in lamports ); invoke( &transfer_instruction, &[ payer.clone(), recipient.clone(), system_program.clone(), ], )?; msg!("Transfer completed via CPI"); Ok(()) } fn main() { println!("CPI Example Program"); }
Rent Calculation
use solana_program::{ rent::Rent, pubkey::Pubkey, msg, }; fn calculate_rent_for_account(data_size: usize) -> u64 { let rent = Rent::default(); rent.minimum_balance(data_size) } fn is_rent_exempt(account_balance: u64, data_size: usize) -> bool { let rent = Rent::default(); rent.is_exempt(account_balance, data_size) } fn main() { // Calculate rent for different account sizes let small_account = 100; // bytes let medium_account = 10_000; // 10KB let large_account = 1_000_000; // 1MB println!("Rent for 100 bytes: {} lamports", calculate_rent_for_account(small_account)); println!("Rent for 10KB: {} lamports", calculate_rent_for_account(medium_account)); println!("Rent for 1MB: {} lamports", calculate_rent_for_account(large_account)); // Check rent exemption let account_balance = 1_000_000; // 0.001 SOL println!("Is exempt? {}", is_rent_exempt(account_balance, small_account)); }
Key Solana Concepts for Interviews
Architecture
- Proof of History - Cryptographic clock for transaction ordering
- Proof of Stake - Validators stake SOL to participate
- Tower BFT - Consensus algorithm built on PoH
- Turbine - Block propagation protocol
- Gossip - Validator communication network
- SeaLevel - Parallel transaction runtime
- Clusters - Devnet, Testnet, Mainnet Beta
Performance
- 50,000+ TPS - Transactions per second
- 400ms - Block time
- Sub-second finality - Transaction confirmation
- Parallel execution - Multiple transactions simultaneously
- No gas - Compute units instead
Development
- Rust + BPF - Primary language for programs
- Anchor Framework - TypeScript/Rust framework for easier development
- Solana CLI - Command-line tools
- JSON RPC API - Web3-compatible API
- Web3.js - JavaScript SDK
Accounts Model
- All data in accounts - Programs, state, configuration
- Rent-exempt - Must deposit SOL to keep accounts alive
- PDAs - Program-controlled addresses
- Signers - Accounts that must sign transactions
- Writers - Accounts that are writable
Security
- BPF sandbox - Safe execution environment
- Compute units - Resource limits per transaction
- Account ownership - Programs own their accounts
- Instruction validation - Programs verify all accounts
- Reentrancy protection - Programs control their own execution