BlockSTM — Parallel Execution on Movement
Deep dive into Movement's BlockSTM parallel execution engine: how optimistic concurrency works, performance characteristics, and how to write Move smart contracts that maximize parallel throughput.
Overview
BlockSTM is the parallel transaction execution engine at the heart of Movement's high-throughput architecture. Originally developed within the Aptos ecosystem, BlockSTM uses optimistic concurrency control to execute multiple transactions simultaneously while preserving deterministic, serial-equivalent results. This means Movement can process thousands of transactions per second without sacrificing correctness.
For developers building on Movement, BlockSTM is largely transparent. You write standard Move code and the runtime parallelizes execution automatically. However, understanding how BlockSTM works allows you to design contracts and transaction patterns that maximize throughput and minimize costly re-executions.
How BlockSTM Works
BlockSTM operates on a simple but powerful principle: assume transactions do not conflict, execute them in parallel, then validate the assumption.
The Execution Pipeline
-
Optimistic execution -- All transactions in a block are dispatched to worker threads simultaneously. Each transaction reads from a multi-version data structure that tracks writes from other transactions.
-
Validation -- After a transaction finishes executing, BlockSTM checks whether any value it read was subsequently written by a lower-indexed transaction (one that should have executed "before" it in serial order). If no conflicts are found, the result is committed.
-
Re-execution on conflict -- If a validation failure is detected (a read dependency was modified), the transaction is re-executed with the updated values. This process repeats until the transaction validates successfully.
-
Commit -- Once all transactions in the block have been validated, the final state is committed as if the transactions had executed sequentially.
Multi-Version Data Structure
BlockSTM maintains a multi-version data store where each write is tagged with the index of the transaction that produced it. When a transaction reads a storage location, it sees the latest write from a lower-indexed transaction. This is the key mechanism that allows parallel execution to produce serial-equivalent results.
Collaborative Scheduling
BlockSTM uses a collaborative scheduler that dynamically assigns execution and validation tasks to worker threads. Threads do not wait idly. If a transaction needs re-execution, the scheduler ensures dependent transactions are also re-validated, creating a wavefront of correct execution that propagates through the block.
Performance Characteristics
BlockSTM performance scales with the degree of independence among transactions in a block. The two extremes are:
-
Fully independent transactions (no shared state) -- Near-linear speedup with the number of cores. A block of 1,000 independent transfers can execute across 16 cores roughly 16 times faster than serial execution.
-
Fully dependent transactions (every transaction reads the previous one's writes) -- Degrades to serial execution, since every transaction after the first must be re-executed once.
Real-world workloads fall between these extremes. Typical DeFi activity on Movement shows significant parallelism because different users interact with different accounts and resources.
Throughput Expectations
| Workload type | Parallelism | Approximate speedup (16 cores) |
|---|---|---|
| Independent token transfers | Very high | 10-16x |
| DEX swaps across different pools | High | 6-12x |
| DEX swaps on the same pool | Low | 1-3x |
| Sequential state machine updates | None | 1x (serial) |
The actual throughput you observe depends on the mix of transactions in each block and the number of execution cores available on Movement validators.
Writing Parallel-Friendly Move Code
You do not need to add special annotations or parallel primitives to your Move code. BlockSTM parallelizes automatically. However, the way you structure your storage determines how much parallelism is possible.
Principle: Minimize Write Conflicts
Two transactions conflict when one writes to a storage location that the other reads or writes. The fewer conflicts in a block, the more parallelism BlockSTM achieves.
Conflict-prone pattern -- single global counter:
module 0xCAFE::counter {
struct GlobalCounter has key {
value: u64,
}
/// Every call writes to the same resource, serializing all transactions.
public entry fun increment(account: &signer) acquires GlobalCounter {
let counter = borrow_global_mut<GlobalCounter>(@0xCAFE);
counter.value = counter.value + 1;
}
}Every invocation of increment reads and writes the same GlobalCounter resource. In a block with 100 calls to this function, BlockSTM must re-execute nearly every transaction, effectively running them serially.
Parallel-friendly pattern -- per-user counters:
module 0xCAFE::counter_v2 {
struct UserCounter has key {
value: u64,
}
/// Each user writes to their own resource -- no conflicts across users.
public entry fun increment(account: &signer) acquires UserCounter {
let addr = signer::address_of(account);
let counter = borrow_global_mut<UserCounter>(addr);
counter.value = counter.value + 1;
}
}Because each user's counter lives at a different address, transactions from different users execute in parallel with zero conflicts.
Principle: Batch Reads, Defer Writes
When your contract must access shared state, structure the logic so that reads happen early and writes happen late. This reduces the window in which conflicts can occur.
module 0xCAFE::pool {
/// Read pool state, compute locally, then write once.
public entry fun swap(
account: &signer,
amount_in: u64,
) acquires Pool, UserBalance {
// 1. Read shared pool state
let pool = borrow_global<Pool>(@0xCAFE);
let price = compute_price(pool, amount_in);
// 2. Read user balance (per-user, no conflict with other users)
let user_addr = signer::address_of(account);
let balance = borrow_global_mut<UserBalance>(user_addr);
// 3. Single write to shared state at the end
let pool_mut = borrow_global_mut<Pool>(@0xCAFE);
apply_swap(pool_mut, balance, amount_in, price);
}
}Principle: Use Separate Resources for Independent Data
If a module tracks multiple independent pieces of data, store them in separate resources rather than a single struct. This way, transactions that only touch one piece of data do not conflict with transactions that touch another.
module 0xCAFE::marketplace {
/// Separate resources for separate concerns
struct Listings has key { items: vector<Listing> }
struct BidHistory has key { bids: vector<Bid> }
struct FeeAccumulator has key { total: u64 }
/// Adding a listing only touches Listings -- no conflict with bid queries.
public entry fun add_listing(account: &signer, item: Listing) acquires Listings {
let listings = borrow_global_mut<Listings>(signer::address_of(account));
vector::push_back(&mut listings.items, item);
}
}Comparison with Serial Execution
| Aspect | Serial execution | BlockSTM parallel execution |
|---|---|---|
| Throughput | Limited by single-core speed | Scales with available cores |
| Correctness | Trivially correct (sequential) | Serial-equivalent via validation |
| Developer effort | None | None (automatic), but storage design matters |
| Worst case | Baseline | Same as serial (fully dependent workloads) |
| Best case | Baseline | Near-linear speedup |
Traditional EVM chains execute transactions strictly one after another. Even high-performance EVM chains that attempt parallel execution face challenges because Solidity's account model creates implicit dependencies through shared storage slots. Move's resource model, where data is stored at specific addresses with explicit ownership, naturally maps to BlockSTM's conflict detection, making parallelism more effective.
Observability and Debugging
Detecting Contention
When transactions in your application are frequently re-executed due to conflicts, you will observe:
- Higher gas usage than expected for the transaction complexity.
- Increased latency during high-traffic periods compared to low-traffic periods.
- Simulation gas estimates that differ from on-chain gas because simulation runs in isolation without contention.
Load Testing for Parallelism
Use the transaction simulation endpoint to estimate gas costs under different conditions, then compare with on-chain execution during load tests.
# Simulate a batch of transactions to baseline gas costs
curl -X POST https://api-movement-mainnet.n.dwellir.com/YOUR_API_KEY/v1/transactions/simulate \
-H "Content-Type: application/json" \
-d '[{
"sender": "0xYOUR_ADDRESS",
"sequence_number": "0",
"max_gas_amount": "10000",
"gas_unit_price": "100",
"expiration_timestamp_secs": "9999999999",
"payload": {
"type": "entry_function_payload",
"function": "0xCAFE::counter_v2::increment",
"type_arguments": [],
"arguments": []
}
}]'Monitoring in Production
Track these metrics to understand how BlockSTM is handling your workload:
- Transaction gas used vs. simulated gas -- A large gap suggests contention-induced re-execution.
- Transaction success rate during traffic spikes -- Contention does not cause failures, but it can cause transactions to exceed their
max_gas_amountif re-execution burns extra gas. - Block inclusion time -- Consistently slow inclusion may indicate your transactions are hitting hot storage locations.
Best Practices Summary
- Prefer per-user resources over global singletons for data that is written frequently.
- Separate read-heavy and write-heavy data into distinct Move resources.
- Use events for coordination instead of shared mutable state where possible. Events are append-only and do not create write conflicts.
- Test under realistic load by simulating concurrent transactions that mirror expected production patterns.
- Design for the common case -- optimize the most frequent transaction types for parallelism, even if rare administrative operations must serialize.
Related Pages
- Fast Finality Settlement -- How Movement's consensus delivers 1-3 second finality alongside parallel execution.
- MEV Protection -- Transaction ordering fairness in Movement's parallel execution environment.
- Transaction Simulation -- Simulate transactions to estimate gas and detect potential issues before submitting.
- Transactions -- Submit and query transactions on the Movement REST API.
Movement Move REST API Guide
Production-ready docs for Movement's MoveVM interface. Movement exposes Aptos-compatible REST endpoints under /v1 for querying data and submitting transactions.
Fast Finality Settlement (1-3s) on Movement
How Movement achieves 1-3 second transaction finality, what this means for dApp UX and architecture, and how to build applications that leverage fast settlement times.