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.
26 KiB
AERE Parallel Execution: Block-STM Executor and On-Chain Hint Registry
Technical specification. Chain 2800 (AERE Network mainnet).
Grounding: parallel-executor/ (Rust), rollup-sequencer/src/blockstm/ (TypeScript),
contracts/contracts/parallel/AereBlockSTMRegistry.sol,
contracts/contracts/parallel/AereRollupValidity.sol,
contracts/deployments/block-stm-registry.json, and
sdk-js/src/addresses.ts.
1. Executive summary and honest scope
AERE's parallel-execution work has three distinct pieces at three different levels of maturity. Stating them plainly up front, because the boundary between "shipped" and "roadmap" is the whole point of this document:
-
A real, self-verifying Block-STM parallel executor (shipped, benchmarked).
aere-block-stmis a from-scratch Rust implementation of the Block-STM optimistic-concurrency algorithm (Gelashvili et al., arXiv:2203.06871, 2022, the same design behind Aptos and the family Sui and Monad build on). It executes a batch of transactions across many CPU cores and commits a single keccak256 state root. Every run also executes the batch sequentially and refuses to emit a root unless the two results match, so a concurrency bug cannot silently produce a bad commitment. Correctness is proven over 6000 randomized adversarial comparisons with zero mismatches, and measured speedup on a 16-core box is roughly 8.6x to 9.3x on low and medium-conflict batches. -
An on-chain advisory registry (shipped, live).
AereBlockSTMRegistryat0x98E2C3e615841919d173D8FF642514c5902E8A28is an opt-in registry where a contract publishes the storage-slot read/write hints for its own function selectors. It is advisory metadata for a scheduler, holds no funds, changes no execution semantics, and has no admin over anyone else. -
Moving parallelism into base consensus (roadmap, not built). AERE's Layer-1 base layer runs Hyperledger Besu and executes transactions sequentially. Nothing here changes that. Making Besu execute in parallel is a fork of a Java execution client, a multi-month effort, and is explicitly out of scope for what exists today. The registry is metadata for that eventual fork; the concrete parallel path that runs today lives in the rollup layer, off-chain in the sequencer.
The honest one-line framing, taken verbatim from the executor README, is: "Real Block-STM parallel executor, wired into the AERE rollup sequencer; the L1 base layer stays Besu-sequential."
Everything in Section 2 through Section 5 is code that exists and runs. Section 6 (base-consensus promotion) and the fraud-proof / full-EVM items in Section 7 are roadmap and are labeled as such.
2. The parallel executor (aere-block-stm)
The executor is a standalone Rust crate under parallel-executor/ with zero external
dependencies: the scheduler, multi-version memory, PRNG and keccak256 are all built on std.
The build is fully offline and reproducible.
2.1 State and transaction model
The state model is intentionally EVM-shaped but minimal (src/types.rs):
Key::Balance(account): the native-token balance of an account.Key::Storage(contract, slot): a single key/value contract storage slot.
Values are u128; an absent key reads as 0. A transaction version is (txn_index, incarnation), where incarnation counts how many times a transaction has been re-executed
after an abort.
The VM understands four transaction kinds (TxKind), each chosen because its control flow or its
written values depend on what it reads, so a stale read is silently wrong unless caught:
- Transfer
{from, to, amount}: moveamountiffromcan afford it. Control flow depends on reading the sender balance. - Sweep
{from, to}: move the entire balance offrom. The written values depend directly on the read balance, a strong serializability probe. - Increment
{contract, slot}:storage[slot] += 1. When many transactions hit the same slot this is the classic all-conflict counter: the sequential result isstart + count, and a correct parallel executor must reproduce exactly that despite reordering. - AmmSwap
{contract, x_slot, y_slot, dx}: a constant-product swap against a two-slot pool, modelling a hot DeFi pool that conflicts across concurrent swaps.
Each Txn also carries a gas field, a simulated per-transaction CPU cost measured in
keccak rounds. It does not affect the resulting state (correctness is independent of gas); it
exists so benchmarks charge each transaction a realistic amount of interpreter-like work instead
of measuring only scheduling overhead. This is discussed honestly in Section 4.
2.2 One transaction semantics, two drivers
The single most important design decision is in src/vm.rs: there is exactly one
implementation of what a transaction does, execute_txn, and it runs against an abstract
VmView trait with two methods, read(key) and write(key, value). All arithmetic is
saturating so both paths behave identically and never panic on overflow.
- The sequential oracle (
src/sequential.rs) drivesexecute_txnwith a view backed directly by a liveHashMapstate, applying transactions in strict index order. This is the definition of the correct answer. - The parallel executor (
src/parallel.rs) drives the sameexecute_txnwith a view backed by multi-version memory that records the read set and can signal an abort.
Because both paths share the exact same semantics function, the parallel result can differ from the sequential result only if the concurrency control is wrong. That is precisely the property the correctness harness (Section 3) exercises.
2.3 Multi-version memory (MVMemory)
src/mvmemory.rs keeps, for every Key, an ordered BTreeMap<txn_index, cell>, where a cell is
either a concrete Value(incarnation, value) written by a transaction, or an Estimate
placeholder (a marker that a lower-indexed transaction previously wrote here but is currently
being re-executed, so its value is not trustworthy).
A read by transaction i for key k returns the entry of the highest transaction j < i
that wrote k (versions.range(..txn_idx).next_back()). That is what makes speculative parallel
execution serial-equivalent: transaction i always observes the writes of all lower-indexed
transactions that have run, and nothing from higher ones. If the highest lower entry is an
Estimate, the read returns Blocked(j) and the reader must abort and park on j.
Storage is sharded across 256 independent mutexes keyed by an FNV-1a hash of the Key, so
non-conflicting transactions touch disjoint locks and run without contention. Hot keys (the
shared counter, the AMM pool) serialize on one shard, which is the honest source of the
high-conflict slowdown, not an implementation defect.
Three operations matter:
apply_write_setinstalls a completed incarnation's writes, deletes stale entries the previous incarnation wrote but this one did not, and returns whether the set of written keys changed (Block-STM calls this "wrote a new path" and uses it to trigger suffix revalidation).mark_estimatesconverts an aborted transaction's writes intoEstimateplaceholders the instant it is validation-aborted, so any concurrent higher transaction that reads one of those keys blocks on it instead of consuming a soon-to-be-wrong value.snapshot_finalmaterializes the committed state once the block is fully executed and validated: base overlaid with, for each key, the value written by the highest transaction index that wrote a concrete value.
2.4 The collaborative scheduler
src/scheduler.rs implements the Block-STM collaborative scheduler (Algorithm 3 of the paper). It
hands worker threads two kinds of task:
- Execution tasks: run incarnation
kof transactioni. - Validation tasks: re-check that an already-executed transaction's recorded read set is still valid against current MVMemory.
Two atomic cursors, execution_idx and validation_idx, drive it. Validation of a lower index
always takes priority over execution of a higher one. When a transaction aborts (validation
fails) or writes a new path, the relevant cursor is decreased so the affected range is
revisited, and a decrease_cnt counter is bumped. The block is done only when both cursors have
passed the end, no task is in flight, and no decrease happened during the check.
Dependencies are handled without condvars: when execution of i reads an Estimate from
j, i is parked on j's dependency list and its task dropped; when j finishes it re-arms i
(back to ReadyToExecute) and decreases execution_idx so a worker re-picks it. add_dependency
holds the blocking transaction's status lock across the whole check-then-park critical section, so
j cannot transition to Executed and drain its dependency list between the check and the push
(which would lose the wakeup). try_validation_abort uses a compare-and-set on status so only one
validator can abort a given incarnation.
The src/parallel.rs driver spawns num_threads workers, each looping next_task -> run ->
feed result back -> repeat, until the scheduler reports Done. It tracks aggregate stats:
total_executions (>= num_txns; the excess is re-executions), total_validations, and
total_aborts.
2.5 keccak256 state root
src/keccak.rs is a from-scratch keccak256 (standard Keccak-f[1600], rate 1088 bits, original
Keccak 0x01 .. 0x80 padding, i.e. keccak256 not SHA3-256), verified in tests against the known
vectors for "" and "abc". state_root folds a materialized state into one 32-byte root by
sorting non-zero entries into a canonical order (balances tagged 0x00, storage tagged 0x01,
big-endian fields) and hashing. The canonical sort makes the root deterministic and independent of
hashmap iteration order, which is exactly what a rollup state commitment requires, and it is
EVM-compatible with zero external crates.
3. Correctness guarantee (proven, not asserted)
The whole point of Block-STM is that the parallel result must equal sequential execution. The
harness (aere-block-stm harness, in src/main.rs) proves it empirically.
It runs 6 workload profiles x 200 randomized batches x {1,2,4,8,16} threads = 6000
comparisons (1200 distinct randomized batches, each compared at five thread counts) and asserts
the Block-STM committed state equals the sequential oracle for every single case. The profiles
include deliberately adversarial fully-conflicting cases: all-hot (400 transactions, 200 hot
accounts, conflict probability 1.0) and brutal-single-counter (300 transactions on 64 accounts,
one contract, one slot, conflict probability 0.9). Reported result:
total (batch x threads) comparisons: 6000
mismatches : 0
RESULT: PASS -- parallel Block-STM == sequential on ALL cases
Workloads are generated by a seedable SplitMix64 PRNG (src/workload.rs) with no external RNG
crate, so any failing case can be replayed exactly. The default batch mix is 60% transfers, 15%
sweeps, 15% shared-counter increments, and 10% AMM swaps.
cargo test --release adds targeted unit checks:
shared_counter_is_serialized: 500 transactions all incrementing one slot yield exactly 500 at 1, 2, 4, 8, and 16 threads (naive parallelism would lose updates).parallel_equals_sequential_random: 40 seeds x 4 thread counts.keccak_known_answer: keccak256 of""and"abc"match the standard vectors.
4. 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 simulated EVM execution cost of gas = 80 keccak-rounds. Speedup is versus
the single-threaded sequential oracle, median of 7 runs. Reproduce with
cargo run --release -- bench.
| 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% |
The "8 to 10x" headline is the low and medium-conflict range (8.59x and 9.31x at 16 cores). The honest reading, including the cases that do not look good, is stated in the README and repeated here because it is a credibility asset:
- Low and medium-conflict batches scale nearly linearly.
- The high-conflict batch still reaches roughly 4.6x because only about 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 worst case: no correct parallel executor can beat sequential when there is nothing to parallelize.
- There is also a floor case: if transactions do near-zero compute (
gas = 0), concurrency-control overhead dominates and the parallel executor is slower than sequential. Real EVM transactions are compute-heavy (opcode interpretation, keccak, ecrecover), which is the regime Block-STM targets, which is why the benchmark charges a realistic per-transaction cost. Runbench 0to see the floor andbench 200for heavier transactions.
These figures are single-machine microbenchmarks of the executor. They are not a network throughput claim, not a TPS figure, and say nothing about AERE L1, whose base layer remains Besu-sequential.
5. Integration into the rollup sequencer
The concrete place this executor runs today is AERE's rollup layer, off-chain in a sequencer.
5.1 The wrapper
rollup-sequencer/src/blockstm/BlockSTMExecutor.ts exposes:
executeBatch(base: Map<StateKey, bigint>, txns: RollupTx[], opts?) : BlockSTMResult
It has no npm dependencies (only Node's built-in child_process). It serializes the prior L2
state slice and the ordered batch into the executor's line protocol and shells out to the
aere-block-stm execute subcommand, which runs the batch across cores, computes the new state,
and returns a keccak256 state root as JSON. The execute path runs the batch both in parallel
and sequentially and sets parallelEqualsSequential; the binary exits non-zero and the wrapper
throws if they diverge, so a bad root can never reach the chain. BlockSTMResult carries
{ stateRoot, numTxns, threads, executions, validations, aborts, parallelEqualsSequential, state }.
The execute stdin wire protocol (src/main.rs) is one record per line: THREADS n, GAS n,
BASE <key> <value> (key is B:<acct> or S:<contract>:<slot>), then TX TRANSFER|SWEEP|INC|AMM .... Output is a single JSON object with the committed root and self-check.
5.2 The on-chain commitment flow
The rollup layer is AERE's Rollup-as-a-Service stack. The canonical factory is
AereRaaSFactoryV2 at 0xB7F8c754AC3155197d76f01857172bBd5a5F39Ab (bond token
WAERE 0x7e84d7d66d5da4cfE46Da67CDEeB05B323e1f5e8, sink
AereSink 0x69581B86A48161b067Ff4E01544780625B231676), which deploys a fixed
AereRollupSettlementV2 instance per registered rollup. That per-rollup settlement contract, not
a single global address, is where a sequencer proposes roots. Its call is
proposeStateRoot(uint256 epoch, bytes32 root, bytes32 l2BlockHash), callable only by the
rollup's sequencer, with epoch == latestEpoch + 1, followed by an optimistic challenge window.
The flow is therefore:
batch of L2 txns -> BlockSTMExecutor.executeBatch() -> { stateRoot, ... }
|
v
settlement.proposeStateRoot(epoch, stateRoot, l2BlockHash)
Because the executor guarantees the parallel result equals sequential execution, the committed
root is deterministic and reproducible, which is what makes it safe as an optimistic-rollup
commitment: a Phase-2 fraud proof would re-derive it by re-running the same deterministic
executor over the disputed batch. See rollup-sequencer/src/blockstm/example.ts for an
end-to-end run.
5.3 Integration status (honest)
- Executor: real Block-STM, correctness-proven, benchmarked. Shipped.
- Integration: the
executeJSON boundary is live and tested; the TypeScript wrapper drives it and returns a root ready forproposeStateRoot. Shipped as the batch-execution core. - Not yet built: the wiring from a live L2 mempool into
executeBatch(this module is the batch-execution core the full sequencer loop plugs into, not a running production sequencer), and an on-chain optimistic fraud-proof verifier (settlement is optimistic Phase 1 today). These are roadmap.
6. The on-chain hint registry: AereBlockSTMRegistry
6.1 Purpose
For Block-STM to schedule efficiently it benefits from hints about which storage slots a
transaction is likely to read or write; a good hint reduces the abort rate. Solidity does not
expose this metadata. AereBlockSTMRegistry (contracts/contracts/parallel/AereBlockSTMRegistry.sol)
is an opt-in registry where a contract publishes its likely read/write set at the function-selector
grain, so an eventual scheduler can consult it. It is advisory only: hints cannot change
execution semantics, and a scheduler always re-validates the actual read/write set after execution.
A wrong hint costs at most one extra abort; it can never make a transaction succeed when it should
fail.
6.2 Data model and API
SlotHint { bytes32 slotPrefix; Mode mode; }whereModeisREAD,WRITE, orREADWRITE.HintSet { address publisher; SlotHint[] hints; uint64 publishedAt; }, stored per(target, selector)keykeccak256(abi.encodePacked(target, selector)).
Functions:
publish(address target, bytes4 selector, SlotHint[] hints): publish or replace the hint set. It caps hints at 64 (TooManyHintsabove that) and replaces rather than appends.clear(address target, bytes4 selector): clear the set; publisher only.hintsFor(target, selector) -> (publisher, hints, publishedAt): the view a scheduler reads.publisherOf(target, selector) -> address.
6.3 Access control (audit fix HIGH #27)
The first publisher for a (target, selector) must be the target itself or the target's
Ownable owner, checked best-effort via a staticcall to owner() (_tryOwner). This closes a
front-run squatting vector: without it, an attacker could publish poisoned hints for a victim
contract's selector before the real owner did. After the first publisher claims a key, only that
publisher can update or clear it (NotPublisher otherwise). There is no admin override and no
Foundation gate: the contract author knows its own access pattern best. The registry test
(contracts/test/block-stm-registry.test.js) exercises the claim rule, the replace-not-append
behavior, the 64-hint cap, and publisher-only clear.
6.4 Deployment facts (live)
- Address:
AereBlockSTMRegistry 0x98E2C3e615841919d173D8FF642514c5902E8A28(roadmap item #33). - Deploy tx:
0x5646445f5a6c8a3d9d06db7a6becd95f6227608b64291319fd19d7d928beee72, block 8877969. - Deployer:
0xbeB33D20dFBBD49eC7AC1F617667f1f02dfd6465. Code size 1753 bytes. Deployed 2026-07-10. - Read-verified at deploy:
hintsFor(unpublished)returns(0x0, [], 0)andpublisherOf(unpublished)returns the zero address. - No owner, no admin, holds no funds.
6.5 What the registry does NOT do (honest scope)
The AERE L1 (Besu) executes sequentially today. The registry is metadata for the eventual
Block-STM Besu fork; it does not make L1 parallel, and publishing hints has no effect on how
any transaction executes on mainnet right now. Its hintsFor(target, selector) view is
intentionally shaped to be the interface a future consensus precompile (planned at 0x103 in the
Phase-2 Besu fork) would read. That precompile and fork do not exist. The contract is deliberately
inert-but-ready: real, correct metadata storage today, waiting for a consumer that is roadmap.
7. Related on-chain anchor: AereRollupValidity (validity proofs of the same executor)
There is a second, stronger settlement path already anchored on-chain for the exact same executor.
AereRollupValidity at 0x38772063572DF94E90351e44ccbBEefD5F497fbd verifies SP1 zkVM Groth16
validity proofs that AERE's deterministic rollup executor transitioned a prior state root to a
new one by applying a specific batch. Its PROGRAM_VKEY binds the SP1 verification key of the
guest ELF that ports execute_txn, keccak256 and state-root folding verbatim from
parallel-executor/src/{vm,keccak}.rs, so the proven computation is byte-identical to what the
production aere-block-stm binary self-checks and what BlockSTMExecutor commits. Public values
are exactly 160 bytes: abi.encode(chainId, prevRoot, postRoot, batchHash, numTxns). Submission is
permissionless; an epoch is final the instant its proof verifies (no challenge window), gated by a
prevRoot == latestRoot continuity check. It routes through the SP1 gateway
SP1VerifierGateway 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628.
Per the SDK registry, epoch 0 has already been recorded from a real proof
(submitEpoch 0xd4f8d858...12a4b0), advancing latestRoot to 0x256fa277...25e3ac with
epochCount = 1, under an immutable vkey 0x00c39737...84ad (these three values are cited in the
abbreviated form they appear in sdk-js/src/addresses.ts).
Honest scope, stated in the contract itself: this proves the state transition of AERE's current rollup executor, whose VM implements a bounded set of transaction kinds (Transfer, Sweep, Increment, AmmSwap) over a balance/storage map. It is a real validity proof of that executor. It is not a proof of arbitrary EVM bytecode. The path to a full validity rollup is replacing the executor's VM with a real EVM (revm) inside the zkVM, a separate multi-quarter effort. No claim built on this contract should imply it proves a full EVM.
8. Roadmap: moving parallelism into base consensus
What exists today is a proven off-chain executor plus a live on-chain hint registry. Promoting Block-STM into AERE's base consensus is the roadmap, and it needs work that is not done:
-
The Besu execution-client fork (the hard blocker). AERE L1 is Hyperledger Besu, a Java client that executes block transactions sequentially. Running Block-STM at L1 requires forking Besu's transaction-processing pipeline to execute speculatively with multi-version memory, validation, and abort/retry, then reconcile with QBFT block production. This is a multi-month Java engineering effort and is not mainnet-active. A first prototype now exists and is benchmarked: a parallel state-root commit was added to the forked Besu client's Bonsai world-state (
aerenew/parallel/,parallel_commit.patch), self-checked to produce a commit root bit-identical to the sequential-commit root across independent-transfer, commit-heavy SSTORE, deletion, and mixed workloads. The honest finding is a boundary, not a win: the commit phase is allocation and memory-bandwidth bound, so adding commit worker threads (widths 2 to 16) is slower than a single worker, not faster. The improvement that does hold is a single-threaded commit rewrite that is roughly 2x cheaper than the stock parallel-trie commit (for example, on medium tries about 316.98 ms stock versus 142.38 ms at width 1); end-to-end, Block-STM execution plus the width-1 commit gives about 1.27x to 1.42x on commit-heavy load versus stock sequential exec plus sequential commit, and the real execution-side speedup remains the ~8.6x to 9.3x of Section 4. A network throughput win is gated on real execution-heavy load. This is a research/benchmark control surface (a process-global flag), not a mainnet-active feature; until the full pipeline lands, the registry's0x103precompile consumer does not exist and hints have no runtime effect on L1. -
Fraud proofs for the optimistic rollup path. The per-rollup
AereRollupSettlementV2settlement is optimistic Phase 1: propose plus challenge window, no on-chain fraud-proof verifier yet. The deterministic executor makes a fraud proof constructible (re-run the same binary over the disputed batch), but the on-chain verifier is not built. -
Full-EVM validity.
AereRollupValidityproves the bounded four-kind VM, not arbitrary EVM. A full validity rollup needs a real EVM inside the zkVM. -
Production sequencer loop. The live-mempool ->
executeBatch->proposeStateRootloop is not wired end-to-end; today the module is the batch-execution core, not a running sequencer.
9. Honest limitations summary
- The AERE L1 mainnet executes sequentially on Besu. No parallel execution runs at L1 today, and the hint registry, while live, has no L1 consumer yet.
- The parallel executor's VM is a bounded four-kind model (Transfer, Sweep, Increment, AmmSwap) over a balance/storage map, not a full EVM. Its correctness proofs and validity proofs are proofs of that executor.
- The measured speedup figures are single-machine microbenchmarks (16 cores, gas=80), not a network throughput or TPS claim.
- The rollup settlement path is optimistic Phase 1 with no on-chain fraud proof yet; the validity-proof anchor covers only the bounded VM.
- Broader chain caveats apply and are not hidden: AERE runs seven validators under one operator, a single client, and has had no external audit; usage is thin. The self-verifying executor and the advisory registry are real and proven, but they sit on that foundation.
10. Address reference (verbatim, chain 2800)
| Contract / entity | Address |
|---|---|
| AereBlockSTMRegistry | 0x98E2C3e615841919d173D8FF642514c5902E8A28 |
| AereRollupValidity | 0x38772063572DF94E90351e44ccbBEefD5F497fbd |
| AereRaaSFactoryV2 | 0xB7F8c754AC3155197d76f01857172bBd5a5F39Ab |
| AereSink | 0x69581B86A48161b067Ff4E01544780625B231676 |
| WAERE (RaaS bond token) | 0x7e84d7d66d5da4cfE46Da67CDEeB05B323e1f5e8 |
| SP1VerifierGateway | 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 |
| Foundation (owner of Ownable contracts) | 0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3 |
| Deployer (registry / validity) | 0xbeB33D20dFBBD49eC7AC1F617667f1f02dfd6465 |
AereRollupSettlementV2 instances are deployed per rollup by AereRaaSFactoryV2, so no single
global settlement address exists; the sequencer commits to the per-rollup instance's
proposeStateRoot.