Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Transaction analysis - Runtime analyzes which accounts each transaction reads/writes
  2. Conflict detection - Transactions writing to the same account must execute sequentially
  3. Parallel execution - Transactions touching disjoint accounts execute in parallel
  4. 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