Docs

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

  1. 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.

  2. 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.

  3. 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.

  4. 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 typeParallelismApproximate speedup (16 cores)
Independent token transfersVery high10-16x
DEX swaps across different poolsHigh6-12x
DEX swaps on the same poolLow1-3x
Sequential state machine updatesNone1x (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:

move
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:

move
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.

move
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.

move
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

AspectSerial executionBlockSTM parallel execution
ThroughputLimited by single-core speedScales with available cores
CorrectnessTrivially correct (sequential)Serial-equivalent via validation
Developer effortNoneNone (automatic), but storage design matters
Worst caseBaselineSame as serial (fully dependent workloads)
Best caseBaselineNear-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.

Bash
# 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_amount if re-execution burns extra gas.
  • Block inclusion time -- Consistently slow inclusion may indicate your transactions are hitting hot storage locations.

Best Practices Summary

  1. Prefer per-user resources over global singletons for data that is written frequently.
  2. Separate read-heavy and write-heavy data into distinct Move resources.
  3. Use events for coordination instead of shared mutable state where possible. Events are append-only and do not create write conflicts.
  4. Test under realistic load by simulating concurrent transactions that mirror expected production patterns.
  5. Design for the common case -- optimize the most frequent transaction types for parallelism, even if rare administrative operations must serialize.
  • 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.