← Solana Builder

Intermediate · LESSON 02 OF 06

2. Programs and explicit account state

By the end, you can model an instruction and its required accounts.

In this lesson8 field notes · practice · sources · checkpoint
The caller supplies accounts; the program checks ownership, type, address, signer, authority, and revision before changing or rejecting state.
The caller supplies accounts; the program checks ownership, type, address, signer, authority, and revision before changing or rejecting state.Open full-size visual ↗
Read a text version of this diagram

The caller chooses the accounts passed to an instruction. Before using them, the program verifies expected owners, account types, and canonical addresses; it separately checks transaction signers and stored authority relationships. The course or claim revision is also checked before updating state. Invalid combinations must be rejected without changing protected state.

  1. CALLER SELECTS ACCOUNTS
  2. OWNER + TYPE + PDA CHECKS
  3. SIGNER + AUTHORITY + REVISION
  4. STATE UPDATE OR REJECT
FIELD NOTE 01

Code and state are separate

Solana program code lives in an executable account, but programs are stateless: mutable application state lives in separate accounts passed to instructions. A program does not privately store each user's mutable balance. Persistent state layouts and authority rules must be designed explicitly.

FIELD NOTE 02

Design a learner record

A fictional learner record can contain an authority public key, a course identifier, a revision, and completion flags. Before changing it, validate the account owner, data layout, expected address, and authorized signer. A client selecting the correct account is helpful, but a malicious client can submit different accounts.

FIELD NOTE 03

Start with invariants

Write invariants before implementation: only the designated issuer can attest an exam result; a learner cannot overwrite another learner’s record; a record belongs to one curriculum revision; duplicate certification does not create contradictory state. Derive negative tests directly from these statements.

FIELD NOTE 04

Programs validate state they did not create

A program receives account addresses from a caller. The runtime lets only the owner program change account data or debit its lamports; any program may credit a writable account. Marking an account writable does not grant ownership. Before interpreting data, the instruction must still verify that each supplied account is the one the application intended. A client-side label or type annotation does not enforce that relationship on chain. Bind the address to canonical seeds, check stored authorities and expected mints, and validate signer and writable privileges. Keep initialization rules separate from update rules, and define who may create, change, close, or reassign each piece of state. Then test a valid account of the wrong type, an unrelated authority, and a writable account where read-only access is sufficient.

FIELD NOTE 05

Treat an instruction interface as a contract

For each instruction, write the account list, data fields, signer requirements, writable flags, and state transition before implementing the handler. A learner-record update might require a program-owned record, the learner authority, and a trusted issuer or assessment authority. Say which account must match each relationship; “valid account” is not a precise rule. Mark fields that are immutable after initialization, calculate allocated data space for the chosen serialization, and define how a curriculum revision is stored. This interface is part of the program’s public behavior, so document how clients can discover it and what errors they should expect.

FIELD NOTE 06

Budget the serialized account data

Use a concrete fixed-size, non-zero-copy Anchor example to budget storage. A `LearnerRecord` account could store a learner `Pubkey` (32 bytes), a course ID `[u8; 16]` (16 bytes), a revision hash `[u8; 32]` (32 bytes), and six completion flags `[bool; 6]` (6 bytes). With Anchor's default 8-byte account discriminator, its serialized data needs 94 bytes: `8 + 32 + 16 + 32 + 6`. The account's 32-byte address is separate from `data`; the runtime account also has `lamports`, `owner`, `executable`, and the deprecated `rent_epoch` fields. The owner is a program ID and controls data writes. An Anchor `mut` constraint asks for writable access in the instruction; it does not grant ownership or application authority. Variable-length strings and vectors need space for their length prefixes and maximum contents. Recalculate when the schema changes, and use the different layout rules for zero-copy accounts. This `LearnerRecord` is only a teaching example, not the academy's credential-account schema.

FIELD NOTE 07

Validate every supplied account at the boundary

A caller chooses the accounts it passes to an instruction. On-chain code must check the program owner, expected PDA derivation, stored authority, mint or course identity, and writable or signer requirements before it trusts data. Do not rely on an interface type, a hidden form field, or the order selected by a friendly client. A negative test should substitute a real account of the wrong type, another learner’s record, the wrong authority, and an unexpected writable account. Check not only that the instruction returns an error, but also that every protected account remains unchanged.

FIELD NOTE 08

Plan how state survives course revisions

Account layouts and course policies evolve. A record should identify the schema version and the curriculum revision it represents, while the program defines whether a newer lesson revision updates the same record or creates a separate one. Avoid changing seed rules casually: a new address can make an existing learner appear to have no record, or permit a second claim. When layouts change, specify migration or compatibility behavior and test reading both old and current records. A browser database may hold convenient progress data, but an on-chain program must verify any claim that depends on it rather than trusting a client’s copy.

MAKE THE CONNECTION

A detail worth keeping.

A Solana program receives candidate accounts from a caller. Distinguish the runtime owner field from an application authority field: the owner program controls data writes, while an authority public key inside that data may decide which signer can request a permitted change. A PDA, expected data type, stored authority, writable status, and associated mint are separate checks. A well-typed account is not automatically the one your product intended to update.

Work through an example

Read the example as several independent gates: `Account<'info, LearnerRecord>` checks program ownership and deserializes the expected Anchor account type; `seeds` and `bump` check the learner/course PDA; `Signer` requires the learner to sign; `has_one` compares that signer with the record’s stored learner key; `mut` allows the program to persist an update; and the revision constraint rejects stale work. None of these proves that an off-chain exam was honestly graded: a trusted issuer or verified claim must establish that separate fact. If the course changes, retain the exact revision the record assessed.

Illustrative Anchor account-validation excerpt. The handler, account type, and custom error are omitted; use the project’s pinned Anchor version when compiling.
#[derive(Accounts)]
#[instruction(course_id: [u8; 16], revision: [u8; 32])]
pub struct CompleteCourse<'info> {
    #[account(
        mut,
        seeds = [b"learner", learner.key().as_ref(), course_id.as_ref()],
        bump,
        has_one = learner,
        constraint = record.revision == revision @ AcademyError::RevisionMismatch
    )]
    pub record: Account<'info, LearnerRecord>,
    pub learner: Signer<'info>,
}

Design a learner-record update for one wallet and one course revision. State the account owner, canonical PDA seeds, stored authority field, signer, writable accounts, revision check, and state transition. Then predict which validation rejects a second learner’s record, a missing signature, and a stale revision before changing code.

GUIDED PRACTICE · INTERMEDIATE

Do the work, one step at a time.

Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.

  1. On paper, define a fictional `LearnerRecord` with a learner public key, fixed-size course identifier, curriculum revision, and completion state. Do not include a name, email, score, or answer data.
  2. Write the update interface: record PDA seeds, stored authority, signer, which account is writable, required course/revision inputs, and the one allowed state transition.
  3. Walk through the Anchor excerpt. For every constraint, write the invariant it enforces and one request an attacker could try if that check were removed.
  4. Create five local negative cases: another learner’s valid PDA, a record with a different stored authority, a missing signer, a stale curriculum revision, and the wrong account type or owner.
  5. For each rejected case, assert both the expected error and that the record bytes and completion state are unchanged. Add a successful control case, then review initialization and account sizing separately: record who pays storage, who can close the record, and how a future schema migration preserves the assessed revision.

Before you move on, check your reasoning

  • Describe the learner record fields without adding personal information.
  • Write preconditions and postconditions for issue_credential.
  • Test wrong authority, wrong course revision, and repeated issue.

OPEN SELF-CHECK · UNGRADED

Pause and test your reasoning

A writable account has the expected bytes but an unexpected runtime owner. Should the program update it?

  1. Yes; writable proves the account belongs to the program.
  2. Yes; matching bytes are sufficient if the client selected the address.
  3. No; validate the expected address, runtime owner, account type, and relevant authority before using its data.
Reveal one best answer and its explanation

One best answer: No; validate the expected address, runtime owner, account type, and relevant authority before using its data.

Writable is an instruction access privilege, not proof of account ownership or identity. A robust handler validates the PDA or expected address, owning program, decoded type, signer, and stored authority before changing state.

Sources: Account Structure · Account Constraints

This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.

Never enter a recovery phrase or private key into a learning site or lesson exercise.

READ THE SOURCE

Go deeper in the official docs

Reviewed on 2026-09-29. Protocol documentation can change; check the linked page’s current version as you use this material.

  1. Programs ↗
  2. Accounts ↗
  3. Account Structure ↗
  4. Account Types ↗
  5. Account Constraints ↗
  6. Account Space ↗

Report an issue with this lesson

OPEN LEARNING

Keep learning at your own pace.

This lesson, its guided practice, and all six course lessons are open to everyone. Account-based checkpoints, exams, and saved progress will open when free accounts are ready.

Continue to the next lesson ↗