Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Sealevel is Solana's parallel runtime. It reads each transaction's account list, finds groups of transactions that touch non-overlapping accounts, and executes those groups in parallel across cores. A validator with 128 cores can process hundreds of transactions per millisecond when they don't collide. This is why you are forced to declare accounts up front — the runtime needs the declaration to schedule. Ethereum's sequential EVM cannot do this. The cost: you must write programs that don't serialize on shared state unnecessarily (e.g., a global counter account is a parallelism bottleneck).
Two transactions — one incrementing account A, one incrementing account B — execute in parallel on Sealevel. Two transactions both incrementing account A execute serially. A program that centralises state into one account destroys parallelism; a program that spreads state across per-user PDAs preserves it. This design choice is the single biggest Solana-program-architecture decision.
// main.go — Sealevel contention — worked example
//
// Scenario A: 1000 users each call increment() on their own PDA
// All 1000 txs have disjoint account sets.
// Sealevel: parallel execution, all 1000 commit in ~400ms.
//
// Scenario B: 1000 users all call increment() on a shared GlobalCounter
// All 1000 txs touch the same account with write intent.
// Sealevel: SERIAL execution, 1000 txs take 1000 × per-tx latency.
//
// Moral: parallelism is a function of your account architecture, not of Solana.
package main
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
const txLatency = time.Microsecond * 400 // simulated per-tx processing time
// simulateDisjoint models Sealevel Scenario A:
// Each user has a personal counter (their own PDA). No shared mutable state.
// All goroutines run concurrently — disjoint accounts.
func simulateDisjoint(users int) time.Duration {
var wg sync.WaitGroup
start := time.Now()
for i := 0; i < users; i++ {
wg.Add(1)
go func(uid int) {
defer wg.Done()
// Each user's counter is independent — no lock needed.
var personalCounter int64
time.Sleep(txLatency) // simulate tx execution
atomic.AddInt64(&personalCounter, 1)
}(i)
}
wg.Wait()
return time.Since(start)
}
// simulateShared models Sealevel Scenario B:
// All users write to one shared GlobalCounter. They must be serialised.
func simulateShared(users int) time.Duration {
var mu sync.Mutex
var globalCounter int64
start := time.Now()
for i := 0; i < users; i++ {
mu.Lock() // acquire write lock — only one tx at a time
time.Sleep(txLatency) // simulate tx execution (serial)
globalCounter++
mu.Unlock()
}
_ = globalCounter
return time.Since(start)
}
func main() {
users := 1000
dA := simulateDisjoint(users)
dB := simulateShared(users)
fmt.Printf("Scenario A — disjoint PDAs (%d users): %v\n", users, dA.Round(time.Millisecond))
fmt.Printf("Scenario B — shared counter (%d users): %v\n", users, dB.Round(time.Millisecond))
fmt.Printf("Speedup: %.1fx\n", float64(dB)/float64(dA))
fmt.Println()
fmt.Println("Anchor pattern that preserves parallelism:")
fmt.Println(" #[derive(Accounts)]")
fmt.Println(" pub struct Increment<'info> {")
fmt.Println(" #[account(mut, seeds = [b\"counter\", user.key().as_ref()], bump)]")
fmt.Println(" pub counter: Account<'info, Counter>,")
fmt.Println(" pub user: Signer<'info>,")
fmt.Println(" }")
fmt.Println("Each user's PDA is keyed by their pubkey → disjoint → parallel.")
}go run main.goUse these three in order. Each builds on the one before.
In one paragraph, explain Sealevel — how it parallelises transaction execution, what constraint makes parallelism possible, and what kind of account layout maximises throughput.
Walk me through Sealevel's scheduler step by step: how does it read the transaction's account list, how does it detect conflicts, and how does it assign non-conflicting groups to cores?
I'm designing a lending protocol where users repay loans and this affects a global utilisation metric. How do I structure the accounts so repayments don't serialise on a global state account — and what's the trade-off in eventual consistency I'm accepting?