aere-research/parallel-executor/src/mvmemory.rs
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

183 lines
6.9 KiB
Rust

//! Multi-version shared memory (MVMemory).
//!
//! For every `Key`, MVMemory keeps an ordered map `txn_index -> cell`, where a
//! cell is either a concrete `Value` written by that transaction's latest
//! incarnation, or an `Estimate` placeholder (a marker that a lower 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`. 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.
//!
//! Storage is sharded across many independent mutexes keyed by a hash of the
//! `Key`, so non-conflicting transactions touch disjoint locks and run without
//! contention. Hot keys (the counter, the AMM pool) serialize on one shard,
//! which is exactly the honest source of the high-conflict slowdown.
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Mutex;
use crate::types::{Incarnation, Key, TxnIndex, Value};
const SHARDS: usize = 256;
/// One stored cell for a (key, txn_index) pair.
#[derive(Clone, Debug)]
enum Cell {
/// A concrete value written by this txn's incarnation.
Value(Incarnation, Value),
/// Placeholder: this txn wrote here before but is being re-executed.
Estimate,
}
/// What a read observed. This is recorded into the reader's read set so that
/// validation can later re-check that the read would still return the same
/// thing.
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ReadDescriptor {
/// Nothing below the reader wrote this key: it came from base state.
Base,
/// It was written by `(txn_index, incarnation)`.
Version(TxnIndex, Incarnation),
}
/// The result of a live read against MVMemory.
pub enum ReadResult {
/// Read resolved to a concrete value with the given provenance.
Ok(ReadDescriptor, Value),
/// Read hit an ESTIMATE written by `blocking_txn`; the reader must abort and
/// register a dependency on `blocking_txn`.
Blocked(TxnIndex),
}
pub struct MvMemory {
shards: Vec<Mutex<HashMap<Key, BTreeMap<TxnIndex, Cell>>>>,
}
fn shard_of(key: &Key) -> usize {
// Cheap, deterministic spread. FNV-1a over the key's discriminant + fields.
let mut h: u64 = 0xcbf29ce484222325;
let bytes: [u64; 3] = match key {
Key::Balance(a) => [1, *a, 0],
Key::Storage(c, s) => [2, *c, *s],
};
for b in bytes {
h ^= b;
h = h.wrapping_mul(0x100000001b3);
}
(h as usize) % SHARDS
}
impl MvMemory {
pub fn new() -> Self {
let mut shards = Vec::with_capacity(SHARDS);
for _ in 0..SHARDS {
shards.push(Mutex::new(HashMap::new()));
}
MvMemory { shards }
}
/// Read the value visible to `txn_idx`: the highest entry strictly below it.
pub fn read(&self, key: &Key, txn_idx: TxnIndex) -> ReadResult {
let shard = &self.shards[shard_of(key)];
let guard = shard.lock().unwrap();
if let Some(versions) = guard.get(key) {
// highest entry with index < txn_idx
if let Some((&j, cell)) = versions.range(..txn_idx).next_back() {
return match cell {
Cell::Value(inc, v) => ReadResult::Ok(ReadDescriptor::Version(j, *inc), *v),
Cell::Estimate => ReadResult::Blocked(j),
};
}
}
ReadResult::Ok(ReadDescriptor::Base, 0)
}
/// Apply `txn_idx`'s new write set (incarnation `inc`). `prev_keys` is the
/// set of keys the previous incarnation wrote, so we can (a) delete entries
/// this incarnation no longer writes and (b) decide whether the *set* of
/// written keys changed, which downstream validation depends on.
///
/// Returns `true` if the written key-set differs from the previous one
/// (a key was added or removed) -- Block-STM calls this "wrote a new path"
/// and uses it to trigger revalidation of the suffix.
pub fn apply_write_set(
&self,
txn_idx: TxnIndex,
inc: Incarnation,
writes: &HashMap<Key, Value>,
prev_keys: &std::collections::HashSet<Key>,
) -> bool {
// Install / overwrite current writes.
for (k, v) in writes {
let shard = &self.shards[shard_of(k)];
let mut guard = shard.lock().unwrap();
guard
.entry(*k)
.or_insert_with(BTreeMap::new)
.insert(txn_idx, Cell::Value(inc, *v));
}
// Remove stale entries: keys written previously but not now.
let mut removed_any = false;
for k in prev_keys {
if !writes.contains_key(k) {
let shard = &self.shards[shard_of(k)];
let mut guard = shard.lock().unwrap();
if let Some(versions) = guard.get_mut(k) {
versions.remove(&txn_idx);
}
removed_any = true;
}
}
// Added a key not previously written?
let mut added_any = false;
for k in writes.keys() {
if !prev_keys.contains(k) {
added_any = true;
break;
}
}
added_any || removed_any
}
/// Convert all of `txn_idx`'s current writes (over `keys`) into ESTIMATE
/// placeholders. Called the moment a transaction is validation-aborted, so
/// that any concurrent higher transaction that reads one of these keys will
/// block on `txn_idx` instead of consuming a soon-to-be-wrong value. This is
/// what protects the suffix during a re-execution window.
pub fn mark_estimates(&self, txn_idx: TxnIndex, keys: &std::collections::HashSet<Key>) {
for k in keys {
let shard = &self.shards[shard_of(k)];
let mut guard = shard.lock().unwrap();
if let Some(versions) = guard.get_mut(k) {
if let Some(cell) = versions.get_mut(&txn_idx) {
*cell = Cell::Estimate;
}
}
}
}
/// Materialize the final committed state: base overlaid with, for each key,
/// the value written by the HIGHEST transaction index that wrote a concrete
/// value to it. Called once after the block is fully executed & validated.
pub fn snapshot_final(&self, base: &crate::types::StateMap) -> crate::types::StateMap {
let mut out = base.clone();
for shard in &self.shards {
let guard = shard.lock().unwrap();
for (k, versions) in guard.iter() {
// highest concrete value wins
for (_, cell) in versions.iter().rev() {
if let Cell::Value(_, v) = cell {
out.insert(*k, *v);
break;
}
}
}
}
out
}
}