Skip to content

5. Transactions and runtime

You now know that accounts hold state. A transaction is the envelope that asks programs to change that state. This chapter opens the envelope and follows it through the runtime.

An instruction describes one program call:

program id
account list with signer/writable metadata
instruction data bytes

A transaction contains one or more instructions plus a message and signatures.

Transaction
├── signatures
└── message
├── account addresses and permissions
├── recent blockhash
└── compiled instructions

The message, not an informal description in the UI, is what gets signed.

Why multiple instructions share one transaction

Section titled “Why multiple instructions share one transaction”

Suppose Alice needs to create a profile account and initialize it. The transaction can include:

  1. a System Program instruction to create and fund the account;
  2. a Profile Program instruction to initialize its data.

The transaction is atomic. If initialization fails, state changes from the create step are rolled back with the transaction. A processed failure can still incur a fee.

A recent blockhash limits the validity window of an ordinary transaction.

fetch blockhash
build exact message
collect signatures
submit and confirm before expiry

If the blockhash expires, the transaction must be rebuilt and signed again. Changing only the blockhash still changes the signed message.

Account metadata is both permission and schedule

Section titled “Account metadata is both permission and schedule”

Each instruction marks accounts as signer or non-signer, and writable or read-only. These flags let the runtime enforce privileges and detect conflicts.

Tx A writes Market X
Tx B reads Market X
=> conflict
Tx A writes User A
Tx B writes User B
=> no direct account conflict

Programs cannot promote an account to privileges the transaction did not provide. CPI also cannot invent signer or writable authority.

A simplified pipeline is:

  1. deserialize and sanitize the transaction;
  2. verify signatures and freshness;
  3. load and lock accounts;
  4. charge applicable fees;
  5. execute instructions in order;
  6. meter compute and enforce runtime limits;
  7. commit changes only if the transaction succeeds;
  8. record status and logs for observation.

The runtime enforces protocol rules. The program enforces business rules. The runtime can know that Alice signed; only the program can know whether Alice is the authority allowed to update this particular profile.

Do not translate Ethereum gas too literally. Solana separates several ideas:

  • a base transaction fee associated with signatures;
  • an optional prioritization fee;
  • compute-unit limits that bound execution work;
  • account storage balances for persistent state.

A client can add Compute Budget instructions to request a limit and set a compute-unit price. A practical production process is:

  1. simulate the final instruction set;
  2. observe compute consumption;
  3. add a measured safety margin;
  4. select a priority price appropriate to current conditions;
  5. rebuild and sign the final message.

Check current official formulae and limits before production. Hard-coded protocol constants age badly.

A program can invoke another program inside the same transaction.

User transaction
Swap Program
├── CPI to Token Program: debit input vault
└── CPI to Token Program: credit output account

This is composability. The calling program passes accounts and privileges to the callee. For a PDA authority, the controlling program supplies the correct seeds and bump through runtime-supported signing.

A program should verify the target program ID instead of accepting an arbitrary executable account. It should also verify resulting balances or state when business logic depends on them.

Simulation can expose logs, custom errors, account-loading problems, and compute use before submission. It is not a settlement guarantee. State can change between simulation and execution.

Use simulation to answer “would this likely execute against this snapshot?”, not “has this transaction happened?”

Versioned messages and address lookup tables

Section titled “Versioned messages and address lookup tables”

Complex transactions may need many account addresses. Versioned transactions can reference Address Lookup Tables to encode addresses more compactly.

Lookup tables help message capacity. They do not remove account locks, compute limits, or the requirement to validate every account.

A backend should model transaction progress explicitly:

CREATED -> SIGNED -> SUBMITTED -> CONFIRMED -> FINALIZED
| |
v v
EXPIRED FAILED

Store:

  • transaction signature;
  • blockhash validity context;
  • business operation ID;
  • cluster and RPC provider;
  • observed error and logs;
  • latest commitment status.

Idempotency prevents duplicate callbacks or retries from releasing goods twice.

  • Signature error: wrong signer or mutated message.
  • Blockhash error: transaction expired or context mismatched.
  • Account in use: writable-state contention.
  • Compute exceeded: inefficient program or insufficient limit.
  • Invalid owner/PDA: incorrect or substituted account.
  • Custom program error: inspect program-specific logs and mapping.
  • Insufficient funds: distinguish fee, transfer value, and storage funding.
  • Message too large: reduce accounts/instructions or evaluate versioned messages.

Describe the difference between these four objects:

instruction data
transaction message
transaction signature
transaction status

Then explain why a successful simulation does not prove confirmation.

Next, turn this execution model into application code in Programs, SVM, and development.