aere-contracts/contracts/parallel/AereBlockSTMRegistry.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
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:02:37 +03:00

162 lines
7.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title AereBlockSTMRegistry - read-write access annotations for parallel EVM
* @notice The Block-STM scheduler achieves parallel transaction execution
* by speculatively running TXs in parallel and ABORTING + retrying
* any TX whose read-set was invalidated by a higher-priority TX's
* write-set. Reference algorithm: Gelashvili et al., "Block-STM:
* Scaling Blockchain Execution by Turning Ordering Curse to a
* Performance Blessing" (arXiv:2203.06871, 2022).
*
* For that to work efficiently, the scheduler benefits from
* HINTS about which storage slots a TX is likely to read or
* write. Solidity doesn't expose this metadata. This contract
* provides an OPT-IN registry where contracts can publish their
* likely r/w sets at the function-selector grain - the consensus
* layer's Block-STM scheduler (Phase 2 Besu fork) reads it to
* improve parallelism + reduce abort rate.
*
* WHO REGISTERS:
* - High-throughput hot contracts (AereCoreBookV0, AERE402,
* AereSink, AereLendingMarket).
* - Contracts whose abort rate the indexer flags as too high.
*
* WHAT THEY REGISTER:
* - target contract address + 4-byte selector → array of slot
* hints (storage-slot keccak256 prefixes the TX is likely to
* touch + a read/write flag).
* - hints are ADVISORY. The scheduler still detects conflicts
* at runtime; a wrong hint just costs an abort.
*
* WHO PUBLISHES:
* - The contract owner (or any address whitelisted in the
* contract's own ACL). Per-contract publication, no
* Foundation gate - the contract author knows its own
* access pattern best.
*
* PHASE 2 PROMOTION:
* - When G1 Block-STM Besu fork is merged, the scheduler will
* call into a consensus precompile at 0x103 that reads this
* registry. The precompile interface is the
* `hintsFor(target, selector)` view below.
*
* GOTCHA - NO SECURITY GUARANTEE:
* Hints don't change semantics. A wrong hint cannot make a TX
* succeed when it should have failed, only the other way
* around - and the scheduler always re-validates the actual
* r/w set after execution.
*
* IMMUTABILITY:
* - Per-(target,selector) HintSet has a creator (the contract
* that registered itself). Only the creator can update.
* - No admin override.
*
* WHAT IS SHIPPED TODAY (honest scope):
* - The AERE L1 base layer runs Hyperledger Besu and executes
* SEQUENTIALLY. This registry is advisory metadata for the
* EVENTUAL Besu Block-STM fork; it does NOT make the L1 parallel.
* - A real, correct, benchmarked Block-STM parallel executor DOES
* exist and is wired into the AERE ROLLUP layer: see
* `parallel-executor/` (Rust) and `rollup-sequencer/`. The rollup
* sequencer uses it to execute L2 batches across cores and commit a
* keccak256 state root via AereRollupSettlement.proposeStateRoot.
* Correctness (parallel == sequential) is proven over thousands of
* randomized conflicting batches. That is the concrete parallel-
* execution path on AERE today; L1 stays Besu-sequential.
*/
contract AereBlockSTMRegistry {
/* ================================ types ================================ */
enum Mode { READ, WRITE, READWRITE }
struct SlotHint {
bytes32 slotPrefix; // keccak256 of the slot path prefix
Mode mode;
}
struct HintSet {
address publisher; // who published - only this address can update
SlotHint[] hints;
uint64 publishedAt;
}
/* ================================ storage ================================ */
/// @notice (target, selector) → most recent HintSet.
mapping(bytes32 => HintSet) internal _hints;
/* ================================ events ================================ */
event HintsPublished(address indexed target, bytes4 indexed selector, address indexed publisher, uint256 hintCount);
event HintsCleared(address indexed target, bytes4 indexed selector, address indexed publisher);
/* ================================ errors ================================ */
error NotPublisher();
error TooManyHints();
/* ============================== publication ============================= */
/// @notice Publish r/w hints for (target, selector). Only the first
/// publisher can later update or clear. We do not enforce
/// publisher == target - the target's owner can register
/// from a separate ops wallet.
function publish(address target, bytes4 selector, SlotHint[] calldata hints) external {
if (hints.length > 64) revert TooManyHints();
bytes32 key = _key(target, selector);
HintSet storage h = _hints[key];
if (h.publisher == address(0)) {
// AUDIT FIX (HIGH #27): first publisher must be the target itself
// or its Ownable owner (best-effort). Prevents front-run squatting
// by attackers who would poison the Block-STM scheduler.
if (msg.sender != target && msg.sender != _tryOwner(target)) revert NotPublisher();
h.publisher = msg.sender;
} else if (h.publisher != msg.sender) {
revert NotPublisher();
}
delete h.hints;
for (uint256 i = 0; i < hints.length; i++) h.hints.push(hints[i]);
h.publishedAt = uint64(block.timestamp);
emit HintsPublished(target, selector, msg.sender, hints.length);
}
/// @dev best-effort Ownable.owner() probe; returns address(0) on failure.
function _tryOwner(address target) internal view returns (address) {
(bool ok, bytes memory data) = target.staticcall(abi.encodeWithSignature("owner()"));
if (ok && data.length >= 32) return abi.decode(data, (address));
return address(0);
}
/// @notice Clear hints for (target, selector). Only the original publisher.
function clear(address target, bytes4 selector) external {
bytes32 key = _key(target, selector);
HintSet storage h = _hints[key];
if (h.publisher != msg.sender) revert NotPublisher();
delete h.hints;
h.publishedAt = uint64(block.timestamp);
emit HintsCleared(target, selector, msg.sender);
}
/* ================================ views ================================ */
function hintsFor(address target, bytes4 selector) external view returns (
address publisher, SlotHint[] memory hints, uint64 publishedAt
) {
HintSet storage h = _hints[_key(target, selector)];
return (h.publisher, h.hints, h.publishedAt);
}
function publisherOf(address target, bytes4 selector) external view returns (address) {
return _hints[_key(target, selector)].publisher;
}
/* ================================ internal ============================== */
function _key(address target, bytes4 selector) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(target, selector));
}
}