aere-research/parallel-executor/README.md
Aere Network 37f55e4f71 Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:31:55 +03:00

178 lines
8.0 KiB
Markdown

# aere-block-stm
A **real Block-STM optimistic-concurrency parallel EVM-transaction executor** for
AERE, implemented from scratch in Rust with zero external dependencies.
It executes a batch of transactions across many CPU cores and commits a single
state root that is **provably identical to sequential execution** (deterministic
serializability), which is exactly what makes the resulting root a valid rollup
commitment.
Reference algorithm: Gelashvili, Spiegelman, Xiang, Danezis, Li, Malkhi, Xia,
Zhou, *"Block-STM: Scaling Blockchain Execution by Turning Ordering Curse to a
Performance Blessing"* (arXiv:2203.06871, 2022) - the same design that powers
Aptos, and the family Sui and Monad build on.
---
## Honest scope (read this first)
**What this is NOT:** this does **not** make AERE's Layer-1 base execution
parallel. AERE's L1 runs Hyperledger Besu, which executes transactions
**sequentially**. Parallelizing Besu itself would be a fork of a Java execution
client, a multi-month effort, and is explicitly out of scope here. Nothing in
this module changes how the AERE L1 mainnet executes blocks. The L1 stays
Besu-sequential.
**What this IS:** a standalone, correct, benchmarked parallel executor that
AERE's **rollup layer** can use. AERE already ships a Rollup-as-a-Service stack
(`contracts/contracts/raas/AereRaaSFactory.sol` +
`AereRollupSettlement.sol`). A rollup sequencer executes a batch of L2
transactions off-chain, computes the resulting state root, and commits it via
`AereRollupSettlement.proposeStateRoot(epoch, root, l2BlockHash)`. That batch
execution is a pure, well-defined, embarrassingly-parallelizable workload, and
it is the concrete, real path to parallel execution on AERE. This module is that
executor, wired into the sequencer (see `../rollup-sequencer/`).
So the honest one-line framing is:
> Real Block-STM parallel executor, wired into the AERE rollup sequencer; the L1
> base layer stays Besu-sequential.
---
## What it implements (the real algorithm)
- **Multi-version memory (MVMemory)** - `src/mvmemory.rs`. For every state key,
an ordered `txn_index -> cell` map. A read by txn `i` returns the write of the
highest txn `j < i`, giving each speculative execution a serial-equivalent
view. Sharded across 256 mutexes so non-conflicting txns never contend.
- **Read-set / write-set tracking** - `src/parallel.rs`. Each execution records
exactly what it read (and the version it read) and what it wrote.
- **Dynamic dependency detection** - a read that hits an `ESTIMATE` placeholder
(left by a lower txn that is currently being re-executed) aborts the reader and
parks it on the blocking txn; it is re-armed when the blocker finishes. No
static dependency analysis, no condvars.
- **Abort + re-execution** - `src/scheduler.rs`. Collaborative validation:
validating txn `i` re-checks its read set against current MVMemory; on mismatch
it is aborted, its incarnation bumped, its writes turned into ESTIMATEs, and it
is rescheduled. Cursors are decreased so the affected suffix is revalidated.
- **Serial-equivalent commit** - the block is done only when every txn has been
validated against the final memory. The committed state provably equals
sequential execution.
- **State model** - account balances + a key/value contract store
(`src/types.rs`), with genuine EVM-style transactions whose control flow and
written *values* depend on reads: `Transfer`, `Sweep` (value-dependent),
`Increment` (the canonical shared-counter serializability probe), and
`AmmSwap` (two-slot DeFi pool conflict).
- **keccak256 state root** - `src/keccak.rs`, a from-scratch keccak256 (verified
against known-answer vectors) folds the final state into a real,
EVM-compatible 32-byte root for on-chain commitment.
Zero external crates: the scheduler, MVMemory, PRNG and keccak are all built on
`std`. Build is fully offline and reproducible ($0).
---
## Correctness guarantee (the hard part, proven)
The whole point of Block-STM is that the parallel result **must** equal
sequential execution. Both paths call the exact same transaction-semantics
function (`src/vm.rs::execute_txn`), so any divergence can only come from a
concurrency-control bug. The harness proves there are none:
```
cargo run --release -- harness
```
runs 6 workload profiles x 200 randomized batches x {1,2,4,8,16} threads =
**6000 comparisons** (1200 distinct randomized batches), and asserts the
Block-STM committed state equals the sequential oracle for every one. Profiles
include adversarial 100%-conflict cases (`all-hot`, `brutal-single-counter`).
Result:
```
total (batch x threads) comparisons: 6000
mismatches : 0
RESULT: PASS -- parallel Block-STM == sequential on ALL cases
```
`cargo test --release` additionally checks:
- `shared_counter_is_serialized`: 500 txns all incrementing one slot yield
exactly 500 at 1/2/4/8/16 threads (naive parallelism would lose updates).
- `parallel_equals_sequential_random`: 40 seeds x 4 thread counts.
- `keccak_known_answer`: keccak256("") and keccak256("abc") match the standard
vectors.
---
## Measured speedup (honest numbers)
Measured on the AERE infra box: **16 physical cores**, Linux 6.8, Rust 1.96.
20,000 transactions per batch, each carrying a realistic simulated EVM execution
cost (`gas = 80` keccak-rounds, a stand-in for the microseconds a real EVM tx
spends in the interpreter). Speedup is versus the single-threaded **sequential**
oracle. Median of 7 runs.
| Workload | 2 cores | 4 cores | 8 cores | 16 cores | abort % @16 |
|---|---|---|---|---|---|
| Low-conflict (~2% hot-touch) | 1.78x | 3.22x | 5.71x | **8.59x** | 0.3% |
| Medium-conflict (~15%) | 1.87x | 3.54x | 6.24x | **9.31x** | 1.9% |
| High-conflict (single hot counter, 15% of txns) | 1.70x | 3.04x | 4.87x | **4.62x** | 24% |
| Pathological (EVERY tx hits ONE slot) | 0.85x | 0.85x | 0.82x | **0.73x** | 19% |
Honest reading of these numbers:
- Low/medium-conflict batches scale nearly linearly: **~8.6-9.3x on 16 cores**.
- The high-conflict batch still gets ~4.6x because only ~15% of its
transactions touch the single hot slot; the rest are independent transfers.
- The **pathological** batch (every transaction increments the same slot) has
**zero available parallelism**. Block-STM correctly serializes it and is
actually *slower* than sequential (0.73x) because it pays for aborted
speculation. This is the expected, honest worst case: parallelism collapses
under total conflict, and no correct parallel executor can beat sequential
when there is nothing to parallelize.
There is also an honest floor case worth stating: if transactions do
near-**zero** compute (`gas = 0`), the concurrency-control overhead dominates and
the parallel executor is slower than sequential. That is why the benchmark
charges each transaction a realistic execution cost - real EVM transactions are
compute-heavy (opcode interpretation, keccak, ecrecover), which is precisely the
regime Block-STM is designed for. Run `bench 0` to see the floor, `bench 200`
for heavier txs.
Reproduce:
```
cargo run --release -- bench # gas=80, 20k txns (default)
cargo run --release -- bench 200 # heavier txs
cargo run --release -- bench 0 # trivial txs: scheduler-overhead floor
```
---
## Sequencer integration
The rollup sequencer calls the `execute` subcommand (a JSON-out CLI) with a batch
and gets back a self-checked keccak256 state root to commit on-chain. See
`../rollup-sequencer/README.md` and `../rollup-sequencer/src/blockstm/` for the
TypeScript wrapper and the exact `proposeStateRoot` flow. The library API
(`execute_block_parallel`, `execute_block_sequential`, `state_root`) is also
usable directly from Rust.
Every `execute` run computes the batch **both** sequentially and in parallel and
refuses to emit a root unless they match (`parallelEqualsSequential: true`), so a
concurrency bug can never silently produce a bad rollup commitment.
---
## Build
```
source ~/.cargo/env # on the AERE infra box
cargo build --release
cargo test --release
cargo run --release -- harness
cargo run --release -- bench
```