Revision — The Account Model
This part opened with a single decision and spent five pages proving it was the right one: separate code from state. Accounts hold state; programs hold only code. Before you move on to the runtime that consumes what this part built, it is worth compressing the whole argument back down to the one line it started from — and making sure every piece of it earns its place in that line.
The book’s question has not changed: how do you build a single global state machine that runs at hardware speed without falling apart? The account model is the first real answer. It is a data-layout decision, and like most good data-layout decisions its payoff is not visible where you make it — it shows up two parts later, when the scheduler runs thousands of transactions side by side because this part made their footprints knowable. This page walks back through the argument so that payoff is no longer a surprise.
The flat map, restated
Section titled “The flat map, restated”Solana’s global state is not a forest of contracts each guarding its own private storage. It is one flat map: a 32-byte public key (Pubkey) points at an Account. That is the entire shape of on-chain state.
one global namespace ───────────────────── Pubkey ─────► Account (32-byte key) (state only)
Alice's wallet ─► { lamports, owner=System, data=[] } a token balance ─► { lamports, owner=Token, data=<balance> } a Counter's state ─► { lamports, owner=Counter, data=<count> } the Counter program ► { lamports, owner=Loader, data=<bytecode>, executable=true }Everything lives here. Wallets, token balances, governance proposals, the state of your own program — and, the detail that trips up newcomers, the programs themselves. Code is not a special citizen of the system; it is an account whose data happens to be compiled bytecode and whose executable flag is set. There is exactly one namespace for all state on the chain, and every question about “where does X live?” has the same answer: at some key in the map.
The five fields, in prose
Section titled “The five fields, in prose”An account is deliberately dumb. It is five fields, and the discipline of the whole model comes from how little each one does:
lamports— the native balance, in Solana’s smallest unit (1 SOL = 1,000,000,000 lamports). This is the only field the runtime itself understands and can reason about across every account. Everything else on the account is, as far as the runtime is concerned, opaque.owner— thePubkeyof the program allowed to change this account’sdataand to debit itslamports. This one field is the access-control rule of the entire system. Anyone may name any account and read it; only the owner may mutate it. That is what keeps a single shared map safe without a lock manager or a permissions table — the rule is one field, checked everywhere.data— a raw byte buffer. The runtime never looks inside it. It is just bytes until the owner program decides those bytes are a counter, a balance, or a proposal. Meaning lives in the owner, never in the runtime.executable— a boolean: is this account itself a program? This is how “code is an account too” is expressed. Flip it on and the account’sdatais treated as loadable code rather than mutable state.rent_epoch— a leftover of the old rent-collection accounting, largely vestigial now that accounts must be funded to be rent-exempt to exist at all.
The book’s companion crate, solmini, strips this to the three fields a miniature runtime actually needs — it never loads bytecode and never charges rent — so its Account is exactly:
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]pub struct Account { pub lamports: u64, // native balance (Solana's smallest unit) pub data: Vec<u8>, // opaque buffer a program interprets however it likes pub owner: Pubkey, // the program id allowed to mutate this account's data}executable and rent_epoch are dropped because the demo has no use for them, but lamports, data, and owner are non-negotiable: they are the account model. If you can look at a real Anchor #[account] struct and see that it is just a typed view over the data field of one of these, you have the field-level model. That was the job of Everything Is an Account.
Programs are stateless
Section titled “Programs are stateless”If accounts hold all the state, what is left for a program to be? Pure code. A program on Solana holds no state of its own. It is a function, and its whole signature is:
fn process(accounts: &mut [Account], data: &[u8]) -> Result<()>That is not a simplification for teaching — it is the model. A program is handed a slice of accounts, in the exact order the transaction declared them, plus an opaque data blob that is the instruction (which handler to run, with which arguments). It reads and writes only those accounts and returns. It cannot reach out and touch state that was not passed to it. It has no hidden globals, no private storage, nothing to remember between calls. Everything it may act on arrived through the front door.
This is why the data field’s opacity matters so much. The runtime does not know what a token balance is; the Token program does. The runtime does not know what a counter is; the Counter program does. The type of the bytes in data is knowledge that lives in exactly one place — the owner program — and the owner is the only thing allowed to write those bytes back. State is typed, but the type is interpreted by code that lives entirely outside the account. In solmini this is literally a trait every program is a zero-sized unit struct implementing:
pub trait Program: Send + Sync { fn process(&self, accounts: &mut [Account], data: &[u8]) -> Result<()>;}The Send + Sync bound is not incidental — it is a promise the compiler enforces. Because a program is immutable code with no state, one &dyn Program can be shared across worker threads safely, and the marker traits prove it. Statelessness is not just a mental tidiness; it is what makes the code safe to run in parallel. That was the argument of Stateless Programs Act on Accounts.
PDAs give stateless code stable storage
Section titled “PDAs give stateless code stable storage”Statelessness raises an obvious problem. If a program owns no state and can only act on accounts a transaction passes it, how does it keep its own durable data — one account per user, one vault per market — that it fully controls? A normal account is created by whoever holds its private key. A program has no private key. So how does it own storage it can reliably find again?
The answer is the program-derived address (PDA). A PDA is an address computed deterministically from a program’s id plus a set of seeds:
PDA = derive( program_id, seeds, bump ) │ │ │ │ │ └─ nudges the result off the ed25519 curve │ └────────── e.g. ["vault", user_pubkey] └──────────────────── the program that will own the account
→ no private key exists for this address (it is OFF the signature curve) → same program + same seeds ⇒ same address, every time, from anywhereTwo properties make PDAs exactly the right tool. First, they are keyless: the derivation is deliberately pushed off the ed25519 curve, so no private key can ever sign for the address. That is a feature — it means no external party can hijack the account, and the owning program can sign for it programmatically instead. Second, they are deterministic: the same program and the same seeds always yield the same address, so a program can re-derive the address of “this user’s vault” on any machine, in any transaction, without storing a lookup table anywhere. A PDA is how a stateless program gets a stable, program-owned home for state it fully controls — the missing piece that makes statelessness livable in practice. That was Program Derived Addresses (PDAs).
The Ethereum contrast, and why the split buys parallelism
Section titled “The Ethereum contrast, and why the split buys parallelism”Every page in this part leaned on the same comparison, because it is the fastest way to see why the account model is shaped the way it is. Name it one last time.
Ethereum Solana ──────── ────── contract account program account (code only, STATELESS) = code + its OWN storage owns ▼ data accounts (typed buffers + lamports)
state hidden INSIDE the contract state in NAMED, EXTERNAL accounts → footprint unknown until you run → footprint DECLARED up frontOn Ethereum a contract is its code and it holds its own storage inside itself. That is intuitive and it works, but it has a hidden cost: you cannot know which storage a contract call will touch until you actually execute it, because that storage is the contract’s private business. The runtime is therefore forced to execute calls one at a time — it has no way to tell in advance whether two calls will collide, so it must assume they might.
Solana pulls code and state apart precisely to remove that uncertainty. A program owns no state; all mutable state lives in separate, externally named accounts. Because state lives in named, external accounts, a transaction can list every account it will read and write before it runs. That list is not bookkeeping — it is exactly the information a scheduler needs to decide which transactions are independent. Two transactions whose account lists are disjoint touch different memory and can run at the same time on different cores; two that share a writable account cannot, and must be ordered. The account model is not merely a different data layout. It is the precondition for parallel execution. That was Contrast: Contract-Owned Storage vs External Accounts.
The one thread through all of it
Section titled “The one thread through all of it”Pull the four ideas together and they collapse into a single sentence:
Separating state from code makes a transaction’s footprint knowable before it runs — and knowability is what lets independent transactions run in parallel at hardware speed.
Trace it once more. The flat Pubkey → Account map puts every piece of state at a nameable address. Stateless programs guarantee that code can only touch state that was passed in, so nothing is hidden. PDAs let stateless programs own stable state without breaking that rule. And the Ethereum contrast shows the payoff: when state is external and named, a transaction can declare its footprint up front, which is the one fact a parallel scheduler cannot run without. Every field, every design choice in this part exists to make that declaration possible and trustworthy.
Knowability is the whole game. Ethereum hides state inside contracts and pays for it with serial execution. Solana exposes state as named accounts and buys the right to run transactions side by side. This part did not build the scheduler — it built the guarantee the scheduler depends on.
Where this goes next
Section titled “Where this goes next”Everything from here is payoff. The runtime takes the account lists this part made possible and turns them into a schedule: it groups transactions whose footprints do not overlap into batches and runs each batch across cores, one batch after another. That is the Sealevel idea, and it is only possible because this part refused to let any state hide. The footprint you learned to declare here is the input to the parallelism you will learn to exploit there.
One responsibility crosses that boundary with you, and it is worth stating plainly because it is easy to forget once the runtime is doing the exciting work. Account validation is now yours. Because the runtime treats data as opaque bytes and only enforces the owner rule, it will happily hand your program any account a transaction names — including the wrong account, an account owned by someone else, or an account whose bytes are not the type you expect. The runtime checks that the owner may write; it does not check that the account is the one your program meant to operate on. Confirming that an account is the right account, owned by the right program, holding the right type of data, is program-level work. Anchor generates much of that checking for you from a #[derive(Accounts)] context, but the obligation is yours — a whole class of Solana exploits is nothing more than a program that trusted an account it was passed. Carry that forward: the account model gives you a footprint you can declare, and in return it asks you to prove every account in that footprint is the one you intended.