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.
162 lines
7.3 KiB
Solidity
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));
|
|
}
|
|
}
|