Solana Account Data Mapping in Program Execution


When developing Solana programs, you might notice something interesting: you only provide account addresses in your instructions, yet inside your program, you magically have access to all the account data. Let’s dive deep into how this works.

Step 1: Transaction Submission

When you create a transaction, you include two pieces of information:

  • The addresses of all accounts your instruction will interact with
  • Metadata about these accounts (is it writable? signable?)

Here’s what a typical client-side instruction looks like:

const instruction = new TransactionInstruction({
   keys: [
       { pubkey: payer.publicKey, isSigner: true, isWritable: true },
       { pubkey: accountToRead.publicKey, isSigner: false, isWritable: false },
   ],
   programId: YOUR_PROGRAM_ID,
   data: Buffer.from([/* your instruction data */]),
});

Step 2: Pre-execution Account Loading

Before your program’s code starts executing, the SVM performs a preprocessing step. During this phase:

  1. The runtime identifies all accounts specified in the transaction
  2. It loads the current state of these accounts from the blockchain
  3. The account data is cached in memory for quick access
  4. Necessary runtime checks are performed (ownership, permissions, etc.)

This preprocessing ensures that when your program starts executing, all required data is readily available.

Step 3: Runtime Data Mapping

Once preprocessing is complete, your program receives the account data through its entrypoint. In Rust, this looks like:

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo], // Your account data is available here
    instruction_data: &[u8],
) -> ProgramResult {
    
}

The accounts parameter contains all the account information, mapped in the same order as specified in your transaction. Each AccountInfo structure provides:

  • Account data
  • Owner
  • Lamport balance
  • Various account flags and metadata

Working with Mapped Accounts Inside your program, you can access the mapped account data easily:

fn process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
    // Get an iterator for the accounts
    let account_iter = &mut accounts.iter();
    
    // Access specific accounts in order
    let account_a = next_account_info(account_iter)?;
    let account_b = next_account_info(account_iter)?;
    
    // Access account data
    let data = account_a.data.borrow();
    
    // Do something with the data...
}