Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions l1-contracts/src/core/interfaces/messagebridge/IInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,27 @@ interface IInbox {

function getInProgress() external view returns (uint64);

/**
* @notice Records that checkpoint consumption has reached the given bucket
* @dev Only callable by the rollup contract. The pointer only ever advances: it is a cache of the
* per-checkpoint consumption records kept by the rollup (used for overwrite protection on the ring),
* not the source of truth, so it does not rewind when checkpoints are pruned.
* @param _seq - The bucket sequence number consumption has reached
*/
function markBucketConsumed(uint256 _seq) external;

/**
* @notice Returns the sequence number of the bucket currently accumulating messages
* @return The current bucket sequence number
*/
function getCurrentBucketSeq() external view returns (uint64);

/**
* @notice Returns the highest bucket sequence number that checkpoint consumption has reached
* @return The consumed bucket sequence number
*/
function getConsumedBucketSeq() external view returns (uint64);

/**
* @notice Returns the bucket with the given sequence number
* @dev Reverts if the bucket is ahead of the current one or has already been overwritten in the ring
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ library Errors {
error Rollup__InvalidCheckpointHeaderCount(uint256 expected, uint256 actual);
error Rollup__InvalidCheckpointNumber(uint256 expected, uint256 actual); // 0xd1ba9bfa
error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233
error Rollup__InvalidInboxRollingHash(bytes32 expected, bytes32 actual); // 0xed1f7bb5
error Rollup__UnconsumedInboxMessages(uint256 nextBucketSeq); // 0x2bd4bf10
error Rollup__InvalidOutHash(bytes32 expected, bytes32 actual); // 0x8eb39062
error Rollup__InvalidPreviousArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
error Rollup__InvalidProof(); // 0xa5b2ba17
Expand Down
62 changes: 62 additions & 0 deletions l1-contracts/src/core/libraries/rollup/ProposeLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.8.27;

import {BlobLib} from "@aztec-blob-lib/BlobLib.sol";
import {IEscapeHatch} from "@aztec/core/interfaces/IEscapeHatch.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
import {RollupStore, IRollupCore, CheckpointHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol";
import {TempCheckpointLog} from "@aztec/core/libraries/compressed-data/CheckpointLog.sol";
import {FeeHeader} from "@aztec/core/libraries/compressed-data/fees/FeeStructs.sol";
Expand All @@ -20,6 +21,14 @@ import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {ProposedHeader, ProposedHeaderLib} from "./ProposedHeaderLib.sol";
import {STFLib} from "./STFLib.sol";

// Streaming-inbox protocol constants (AZIP-22 Fast Inbox). These mirror the protocol circuit constants and
// should move into the generated Constants library once the Solidity emitter includes them.
// Minimum bucket age, in seconds, at the start of a checkpoint's build frame for its consumption to be
// mandatory. One L1 slot: validators cannot be required to act on buckets they may not have seen.
uint256 constant INBOX_LAG_SECONDS = 12;
// Maximum number of L1 to L2 messages a single checkpoint can insert.
uint256 constant MAX_L1_TO_L2_MSGS_PER_CHECKPOINT = 1024;

struct ProposeArgs {
bytes32 archive;
OracleInput oracleInput;
Expand Down Expand Up @@ -370,6 +379,59 @@ library ProposeLib {
);
}

/**
* @notice Validates a checkpoint's Inbox consumption against the streaming inbox buckets and records how
* far consumption has reached. Not yet called from propose(): the legacy `consume()`/`inHash` flow
* remains the enforced path until the streaming inbox (AZIP-22 Fast Inbox) flips on.
*
* @dev Two checks:
* 1. The checkpoint header's `inboxRollingHash` must equal the rolling hash snapshotted in the Inbox
* bucket referenced by `_bucketHint`. The hint is a plain calldata lookup aid, not signed and not
* part of the header: a wrong hint cannot change what gets accepted, it only reverts. A checkpoint
* that consumes no messages references the same bucket as its parent.
* 2. Mandatory consumption (the censorship assert): the first unconsumed bucket (`_bucketHint + 1`)
* must either not exist, sit past the consumption cutoff, or be cap-escaped — consuming through it
* would exceed MAX_L1_TO_L2_MSGS_PER_CHECKPOINT messages since the parent checkpoint's cumulative
* total. The cutoff is the start of the checkpoint's build frame minus INBOX_LAG_SECONDS: a
* checkpoint proposed in slot S is built during slot S-1, and validators are not required to have
* seen buckets younger than one L1 slot at build start.
*
* On success, advances the Inbox's consumed-bucket pointer, which the Inbox needs to protect
* unconsumed buckets from ring overwrites.
*
* @param _inbox - The Inbox holding the rolling-hash buckets
* @param _inboxRollingHash - The checkpoint header's inbox rolling hash
* @param _bucketHint - Sequence number of the bucket the header's rolling hash corresponds to
* @param _slotNumber - The slot the checkpoint is proposed in
* @param _parentTotalMsgCount - Cumulative Inbox message count consumed as of the parent checkpoint
*/
function validateInboxConsumption(
IInbox _inbox,
bytes32 _inboxRollingHash,
uint256 _bucketHint,
Slot _slotNumber,
uint256 _parentTotalMsgCount
) internal {
IInbox.InboxBucket memory bucket = _inbox.getBucket(_bucketHint);
require(
bucket.rollingHash == _inboxRollingHash,
Errors.Rollup__InvalidInboxRollingHash(bucket.rollingHash, _inboxRollingHash)
);

if (_bucketHint < _inbox.getCurrentBucketSeq()) {
IInbox.InboxBucket memory next = _inbox.getBucket(_bucketHint + 1);
Timestamp buildFrameStart = TimeLib.toTimestamp(_slotNumber - Slot.wrap(1));
Timestamp cutoff = buildFrameStart - Timestamp.wrap(INBOX_LAG_SECONDS);
require(
next.timestamp > Timestamp.unwrap(cutoff)
|| next.totalMsgCount - _parentTotalMsgCount > MAX_L1_TO_L2_MSGS_PER_CHECKPOINT,
Errors.Rollup__UnconsumedInboxMessages(_bucketHint + 1)
);
}

_inbox.markBucketConsumed(_bucketHint);
}

/**
* @notice Gets the mana min fee components
* For more context, consult:
Expand Down
16 changes: 16 additions & 0 deletions l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ contract Inbox is IInbox {
mapping(uint256 ringIndex => InboxBucket bucket) internal buckets;

uint64 internal currentBucketSeq;
uint64 internal consumedBucketSeq;

constructor(
address _rollup,
Expand Down Expand Up @@ -249,10 +250,25 @@ contract Inbox is IInbox {
return state.inProgress;
}

function markBucketConsumed(uint256 _seq) external override(IInbox) {
require(msg.sender == ROLLUP, Errors.Inbox__Unauthorized());

uint64 current = currentBucketSeq;
require(_seq <= current, Errors.Inbox__BucketOutOfWindow(_seq, current));

if (_seq > consumedBucketSeq) {
consumedBucketSeq = SafeCast.toUint64(_seq);
}
}

function getCurrentBucketSeq() external view override(IInbox) returns (uint64) {
return currentBucketSeq;
}

function getConsumedBucketSeq() external view override(IInbox) returns (uint64) {
return consumedBucketSeq;
}

function getBucket(uint256 _seq) external view override(IInbox) returns (InboxBucket memory) {
uint256 current = currentBucketSeq;
require(_seq <= current && current - _seq < BUCKET_RING_SIZE, Errors.Inbox__BucketOutOfWindow(_seq, current));
Expand Down
Loading
Loading