Research · Solana
Sealevel: parallel execution and account access
Last reviewed 2026-09-21Source: Solana documentation and the Sealevel design postMechanism described as deployed; scheduler details reflect the current Agave client.
Why a runtime is usually serial
A smart contract holds state, and a transaction changes it. If two transactions both change the same state, the runtime has to decide which one goes first, because the second one's result depends on the first. The simplest way to guarantee that is to execute one transaction at a time. That is what a single-threaded runtime does, and it is why the throughput of such a runtime is bounded by the speed of one core.
The obvious fix is to run transactions in parallel when they do not conflict. The difficulty is knowing whether they conflict. If the runtime only discovers which state a transaction touches while it is executing, it cannot safely start two transactions at once, because it does not yet know whether they will collide. It would have to either lock everything or detect conflicts after the fact and roll back, both of which are expensive.
Solana's answer is to require the information up front. A transaction's instructions name every account they will read and every account they will write, and that declaration is part of the transaction itself. The runtime therefore knows the full access set before it executes anything, and it can schedule accordingly.
The account-access declaration
Each instruction in a Solana transaction carries a list of accounts. Every entry in that list is marked as either read-only or writable, and the transaction as a whole carries the union of those accounts. The design post describes the interface as inspired by low-level operating-system calls such as readv and writev, which tell the kernel ahead of time which memory the caller intends to touch so the kernel can prepare and, where possible, execute concurrently.
The declaration is not merely a hint. The runtime enforces it: a program that attempts to modify an account it declared read-only fails, and the runtime checks this when the instruction returns. That enforcement is what makes the declaration trustworthy enough to schedule against. If programs could write to accounts they had not declared, the access set would be meaningless and the scheduler could not rely on it.
The declaration also has a cost. A transaction must list every account it touches, and the list is part of the transaction's size, which is bounded. A program that needs to touch many accounts in one transaction can run out of room. Address lookup tables exist to compress that list by referencing accounts already recorded on-chain, but the underlying constraint — the access set must be stated in advance — remains.
Read locks, write locks and conflicts
With the access set known, the runtime can classify any two transactions as conflicting or not. Two transactions conflict if they both write the same account, or if one writes an account the other reads. Two transactions that only read the same account do not conflict, because neither changes what the other sees. The design post makes this explicit: non-overlapping transactions can execute concurrently, and so can transactions that only read the same state.
In practice the runtime takes locks on accounts. A transaction that reads an account takes a read lock; one that writes takes a write lock. Multiple readers can hold a read lock at once; a write lock is exclusive. When a transaction needs a lock that is not available, it is set aside and retried rather than executed, which is how the runtime avoids executing a transaction against state that is about to change underneath it.
This is the mechanism behind what is usually called a local fee market. When many transactions want to write the same account, they cannot all run at once no matter how much parallel capacity the machine has, because the account's write lock serialises them. The contention is local to that account rather than spread across the network, and it is a direct consequence of the locking model described here. The local fee markets page takes that up in detail.
How the scheduler uses the declaration
The scheduler's job is to take the pool of pending transactions and decide what to run, in what order, on which threads. The design post describes the original approach as sorting the pending transactions and scheduling the non-overlapping ones in parallel. A later refinement, described in the client's own documentation, replaced per-thread queues with a central scheduler that builds a dependency graph over the pending transactions.
The graph is a directed acyclic graph in which conflicting transactions are ordered relative to one another and non-conflicting ones are free to run concurrently. Transactions are grouped into execution chains that are processed in priority order, and for transactions that conflict, the priority fee decides the insertion order. The point of the graph is to minimise lock contention: by ordering conflicting transactions deliberately rather than letting threads discover the conflict at lock time, the scheduler reduces the number of transactions that have to be set aside and retried.
The scheduler also ranks transactions by a priority derived from the fee the validator will actually keep against the estimated cost of executing the transaction. The cost estimate accounts for signature verification, write locks, instruction data and program execution, which means a transaction that touches many accounts or requests a large compute budget is treated as more expensive than a simple transfer. That is why a transaction that does not set its own compute limit can be ranked below one that does, even at the same fee.
What the model does not solve
Parallel execution helps only when transactions do not conflict. A workload in which every transaction writes the same account — a popular mint, a single order book, a shared counter — cannot be parallelised by any scheduler, because the account's write lock serialises it. The runtime can execute the rest of the block in parallel, but the contended account is a bottleneck regardless of how many cores are available.
The declaration also constrains how programs are written. A program cannot decide at runtime to touch an account it did not declare, and it cannot hold two mutable borrows of the same account at once. These are real constraints on developers, and they exist because the scheduler's guarantees depend on the declaration being complete and accurate. The trade is deliberate: the runtime gives up some flexibility in how programs are written in exchange for the ability to schedule them in parallel.
Sources and references
The description of the account-access declaration, the read and write locking model, and the parallel scheduling argument is taken from the Sealevel design post and the project's own documentation. The scheduler description reflects the current Agave client; no throughput figure is asserted.
- The runtime design. Anatoly Yakovenko, Sealevel — Parallel Processing Thousands of Smart Contracts: introduces the account-access declaration, the read and write model, and the argument that non-overlapping transactions can run concurrently.
- Account loading and validation. Solana, Account Runtime: describes how accounts are loaded before execution, the fee-payer validation, and the borrow rules that prevent two mutable borrows of the same account.
- Program execution and compute metering. Solana, Program Execution: covers the instruction pipeline, the compute-unit budget, and the checks the runtime performs when an instruction returns.
- The scheduler and its priority calculation. Solana, Fee Structure: documents the priority formula and the cost components — signature costs, write-lock costs, instruction data and program execution — the scheduler uses to rank transactions.
Related reading
- Research HubEvery dataset on the site, with methodology and provenance.
- Altcoin ResearchAltcoins measured against Bitcoin: design intent, consensus, execution, scaling and market structure.
- The ETH-BTC Correlation RecordHow the correlation is measured, how it behaves across windows, and where it breaks down.
- The ETH/BTC RatioWhat the ratio measures, how to read its trend, and why it is not a forecast.
- ETH During Bitcoin Bull PhasesAssociation within a common market factor, and what co-movement cannot establish.
- ETH During Bitcoin Bear PhasesDrawdown depth and duration compared over identical windows, and the limits of the comparison.