Skip to content
Open
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
3 changes: 2 additions & 1 deletion l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
Epoch,
Timestamp,
CommitteeAttestations,
EpochProofExtLib,
RollupOperationsExtLib,
ValidatorOperationsExtLib,
EthValue,
Expand Down Expand Up @@ -300,7 +301,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore {
ProposedHeader[] calldata _headers,
bytes calldata _blobPublicInputs
) external view override(IRollup) returns (bytes32[] memory) {
return RollupOperationsExtLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
return EpochProofExtLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion l1-contracts/src/core/RollupCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
import {CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {EpochProofExtLib} from "@aztec/core/libraries/rollup/EpochProofExtLib.sol";
import {RollupOperationsExtLib} from "@aztec/core/libraries/rollup/RollupOperationsExtLib.sol";
import {ValidatorOperationsExtLib} from "@aztec/core/libraries/rollup/ValidatorOperationsExtLib.sol";
import {SlasherDeploymentExtLib} from "@aztec/core/libraries/rollup/SlasherDeploymentExtLib.sol";
Expand Down Expand Up @@ -466,7 +467,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
* @param _args Contains the epoch range, public inputs, fees, attestations, and the ZK proof
*/
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external override(IRollupCore) {
RollupOperationsExtLib.submitEpochRootProof(_args);
EpochProofExtLib.submitEpochRootProof(_args);
}

/**
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 @@ -60,6 +60,8 @@ library Errors {
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__InvalidPreviousInboxRollingHash(bytes32 expected, bytes32 actual); // 0x2fe7cae5
error Rollup__InvalidEndInboxRollingHash(bytes32 expected, bytes32 actual); // 0x4a9cdb72
error Rollup__InboxBucketStillMutable(uint256 bucketSeq); // 0x7254b980
error Rollup__UnconsumedInboxMessages(uint256 nextBucketSeq); // 0x2bd4bf10
error Rollup__InboxConsumptionBehindParent(uint256 expected, uint256 actual); // 0x54e0c025
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ struct TempCheckpointLog {
bytes32 attestationsHash;
bytes32 payloadDigest;
Slot slotNumber;
// Streaming Inbox consumption counts. `inboxMsgTotal` is the cumulative Inbox message count
// consumed as of this checkpoint (the child's parent-total origin); `inboxConsumedBucket` is the bucket sequence
// number the header's rolling hash corresponds to. Declared next to the slot number so the three share one storage
// slot (4 + 8 + 8 of 32 bytes): propose writes and reads that slot for the slot-progression check anyway.
uint64 inboxMsgTotal;
uint64 inboxConsumedBucket;
FeeHeader feeHeader;
// The consensus Inbox rolling hash the checkpoint header committed to, in a slot of its own: epoch proofs anchor
// both ends of their consumed chain segment against it.
bytes32 inboxRollingHash;
}

struct CompressedTempCheckpointLog {
Expand All @@ -44,7 +53,10 @@ struct CompressedTempCheckpointLog {
bytes32 attestationsHash;
bytes32 payloadDigest;
CompressedSlot slotNumber;
uint64 inboxMsgTotal;
uint64 inboxConsumedBucket;
CompressedFeeHeader feeHeader;
bytes32 inboxRollingHash;
}

library CompressedTempCheckpointLogLib {
Expand All @@ -61,7 +73,10 @@ library CompressedTempCheckpointLogLib {
attestationsHash: _checkpoint.attestationsHash,
payloadDigest: _checkpoint.payloadDigest,
slotNumber: _checkpoint.slotNumber.compress(),
feeHeader: _checkpoint.feeHeader.compress()
inboxMsgTotal: _checkpoint.inboxMsgTotal,
inboxConsumedBucket: _checkpoint.inboxConsumedBucket,
feeHeader: _checkpoint.feeHeader.compress(),
inboxRollingHash: _checkpoint.inboxRollingHash
});
}

Expand All @@ -77,7 +92,10 @@ library CompressedTempCheckpointLogLib {
attestationsHash: _compressedCheckpoint.attestationsHash,
payloadDigest: _compressedCheckpoint.payloadDigest,
slotNumber: _compressedCheckpoint.slotNumber.decompress(),
feeHeader: _compressedCheckpoint.feeHeader.decompress()
inboxMsgTotal: _compressedCheckpoint.inboxMsgTotal,
inboxConsumedBucket: _compressedCheckpoint.inboxConsumedBucket,
feeHeader: _compressedCheckpoint.feeHeader.decompress(),
inboxRollingHash: _compressedCheckpoint.inboxRollingHash
});
}
}
36 changes: 36 additions & 0 deletions l1-contracts/src/core/libraries/rollup/EpochProofExtLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol";
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
import {EpochProofLib} from "./EpochProofLib.sol";

/**
* @title EpochProofExtLib - External Rollup Library (Epoch Proof Functions)
* @author Aztec Labs
* @notice External library containing epoch-proof functions for the Rollup contract to avoid exceeding max
* contract size.
*
* @dev This library serves as an external library for the Rollup contract, splitting off the epoch proof
* submission and public-input computation from RollupOperationsExtLib, which the streaming inbox
* validation pushed over the deployable size limit. The library contains external functions primarily
* focused on:
* - Epoch proof submission and verification
* - Epoch proof public input computation
*/
library EpochProofExtLib {
function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external {
EpochProofLib.submitEpochRootProof(_args);
}

function getEpochProofPublicInputs(
uint256 _start,
uint256 _end,
PublicInputArgs calldata _args,
ProposedHeader[] calldata _headers,
bytes calldata _blobPublicInputs
) external view returns (bytes32[] memory) {
return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
}
}
32 changes: 29 additions & 3 deletions l1-contracts/src/core/libraries/rollup/EpochProofLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,32 @@ library EpochProofLib {
expectedEndArchive == _args.endArchive, Errors.Rollup__InvalidArchive(expectedEndArchive, _args.endArchive)
);
}

// Boundary anchoring for the Inbox rolling-hash chain, mirroring previousArchive/endArchive:
// both ends of the claimed chain segment must match the rolling hashes recorded at propose for checkpoints
// _start - 1 and _end. The start needs this to be sound - the previous checkpoint's header is not among this
// proof's public inputs, so nothing else pins where the segment begins. The end is already pinned transitively
// (the checkpoint root binds end_inbox_rolling_hash to the last checkpoint header, whose hash verifyHeaders ties
// to storage), so checking it here only trades a bare proof-verification failure for a specific error.
// The end check is therefore a deliberate gas-for-diagnostics trade: it costs one cold SLOAD (2,100 gas per
// submission) and can be deleted if the submit path ever needs trimming.
{
bytes32 expectedPreviousInboxRollingHash = STFLib.getInboxRollingHash(_start - 1);
require(
expectedPreviousInboxRollingHash == _args.previousInboxRollingHash,
Errors.Rollup__InvalidPreviousInboxRollingHash(
expectedPreviousInboxRollingHash, _args.previousInboxRollingHash
)
);
}

{
bytes32 expectedEndInboxRollingHash = STFLib.getInboxRollingHash(_end);
require(
expectedEndInboxRollingHash == _args.endInboxRollingHash,
Errors.Rollup__InvalidEndInboxRollingHash(expectedEndInboxRollingHash, _args.endInboxRollingHash)
);
}
}

bytes32[] memory publicInputs = new bytes32[](Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH);
Expand Down Expand Up @@ -319,9 +345,9 @@ library EpochProofLib {

publicInputs[2] = _args.outHash;

// Inbox rolling-hash chain segment consumed across the epoch (AZIP-22 Fast Inbox). Deliberately UNVALIDATED
// until the Fast Inbox flip, when they get checked against per-checkpoint records written at propose; for now
// they are only passed through to the proof's public inputs.
// Inbox rolling-hash chain segment consumed across the epoch. The start is validated above
// against the record written at propose for checkpoint _start - 1; the end is pinned transitively through the
// stored checkpoint header hashes (see the anchoring block in assertAcceptable).
publicInputs[3] = _args.previousInboxRollingHash;
publicInputs[4] = _args.endInboxRollingHash;
}
Expand Down
40 changes: 30 additions & 10 deletions l1-contracts/src/core/libraries/rollup/ProposeLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {ValidatorSelectionLib} from "@aztec/core/libraries/rollup/ValidatorSelec
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {ProposedHeader, ProposedHeaderLib} from "./ProposedHeaderLib.sol";
import {STFLib} from "./STFLib.sol";

Expand All @@ -33,6 +34,10 @@ struct ProposeArgs {
bytes32 archive;
OracleInput oracleInput;
ProposedHeader header;
// Sequence number of the Inbox bucket the header's `inboxRollingHash` corresponds to.
// Unsigned lookup aid kept out of the attested payload digest: a wrong hint can only revert, never change what is
// accepted, since integrity comes from the rolling-hash equality check against the committee-signed header.
uint256 bucketHint;
}

struct ProposePayload {
Expand All @@ -46,7 +51,9 @@ struct InterimProposeValues {
bytes32[] blobHashes;
bytes32 blobsHashesCommitment;
bytes[] blobCommitments;
bytes32 inHash;
bytes32 blobCommitmentsHash;
FeeHeader feeHeader;
uint256 consumedInboxMsgTotal;
bytes32 headerHash;
bytes32 attestationsHash;
bytes32 payloadDigest;
Expand Down Expand Up @@ -121,6 +128,7 @@ library ProposeLib {
using TimeLib for Epoch;
using CompressedTimeMath for CompressedSlot;
using ChainTipsLib for CompressedChainTips;
using SafeCast for uint256;

/**
* @notice Publishes a new checkpoint to the pending chain.
Expand Down Expand Up @@ -261,17 +269,31 @@ library ProposeLib {
uint256 checkpointNumber = tips.getPending() + 1;
tips = tips.updatePending(checkpointNumber);

// Validate the streaming Inbox consumption against the parent checkpoint's consumed position.
// The parent is checkpointNumber - 1, always available: checkpoint 0 carries the {0,0,0} genesis base
// case written at initialization. rollupStore.tips is not committed until below, so the parent read still sees
// the parent as the pending tip. The returned cumulative total is stored in this checkpoint's record so its
// child validates against it and, since temp-log records rewind with the pending chain on a prune, the record
// stays prune-consistent.
v.consumedInboxMsgTotal = validateInboxConsumption(
rollupStore.config.inbox,
v.header.inboxRollingHash,
_args.bucketHint,
v.header.slotNumber,
STFLib.getInboxMsgTotal(checkpointNumber - 1)
);

// Calculate accumulated blob commitments hash for this checkpoint
// Blob commitments are collected and proven per root rollup proof (per epoch),
// so we need to know whether we are at the epoch start:
v.isFirstCheckpointOfEpoch =
v.currentEpoch > STFLib.getEpochForCheckpoint(checkpointNumber - 1) || checkpointNumber == 1;
bytes32 blobCommitmentsHash = BlobLib.calculateBlobCommitmentsHash(
v.blobCommitmentsHash = BlobLib.calculateBlobCommitmentsHash(
STFLib.getBlobCommitmentsHash(checkpointNumber - 1), v.blobCommitments, v.isFirstCheckpointOfEpoch
);

// Compute fee header for checkpoint metadata
FeeHeader memory feeHeader = FeeLib.computeFeeHeader(
v.feeHeader = FeeLib.computeFeeHeader(
checkpointNumber,
_args.oracleInput.feeAssetPriceModifier,
v.header.totalManaUsed,
Expand All @@ -289,20 +311,18 @@ library ProposeLib {
STFLib.addTempCheckpointLog(
TempCheckpointLog({
headerHash: v.headerHash,
blobCommitmentsHash: blobCommitmentsHash,
blobCommitmentsHash: v.blobCommitmentsHash,
outHash: v.header.outHash,
attestationsHash: v.attestationsHash,
payloadDigest: v.payloadDigest,
slotNumber: v.header.slotNumber,
feeHeader: feeHeader
feeHeader: v.feeHeader,
inboxRollingHash: v.header.inboxRollingHash,
inboxMsgTotal: v.consumedInboxMsgTotal.toUint64(),
inboxConsumedBucket: _args.bucketHint.toUint64()
})
);

// Consume pending L1->L2 messages and validate against header commitment
// @note The checkpoint number here will always be >=1 as the genesis checkpoint is at 0
v.inHash = rollupStore.config.inbox.consume(checkpointNumber);
require(v.header.inHash == v.inHash, Errors.Rollup__InvalidInHash(v.inHash, v.header.inHash));

{
bytes32 archive = _args.archive;
if (v.isEscapeHatch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
pragma solidity >=0.8.27;

import {Errors} from "@aztec/core/libraries/Errors.sol";
import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol";
import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol";
import {Timestamp, TimeLib, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol";
import {BlobLib} from "@aztec-blob-lib/BlobLib.sol";
import {EpochProofLib} from "./EpochProofLib.sol";
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
import {AttestationLib} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {
ProposeLib,
Expand All @@ -21,16 +18,16 @@ import {
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";

/**
* @title RollupOperationsExtLib - External Rollup Library (Proposal and Proof Verification Functions)
* @title RollupOperationsExtLib - External Rollup Library (Proposal Functions)
* @author Aztec Labs
* @notice External library containing proposal-related functions for the Rollup contract to avoid exceeding max
* contract size.
*
* @dev This library serves as an external library for the Rollup contract, splitting off proposal-related
* functionality to keep the main contract within the maximum contract size limit. The library contains
* external functions primarily focused on:
* functionality to keep the main contract within the maximum contract size limit. Epoch-proof functions
* live in EpochProofExtLib to keep this library itself deployable. The library contains external
* functions primarily focused on:
* - Checkpoint proposal submission and validation
* - Epoch proof submission and verification
* - Blob validation and commitment management
* - Chain pruning operations
*/
Expand All @@ -39,10 +36,6 @@ library RollupOperationsExtLib {
using TimeLib for Slot;
using AttestationLib for CommitteeAttestations;

function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external {
EpochProofLib.submitEpochRootProof(_args);
}

function validateHeaderWithAttestations(
ValidateHeaderArgs calldata _args,
CommitteeAttestations calldata _attestations,
Expand Down Expand Up @@ -78,16 +71,6 @@ library RollupOperationsExtLib {
STFLib.prune();
}

function getEpochProofPublicInputs(
uint256 _start,
uint256 _end,
PublicInputArgs calldata _args,
ProposedHeader[] calldata _headers,
bytes calldata _blobPublicInputs
) external view returns (bytes32[] memory) {
return EpochProofLib.getEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
}

function validateBlobs(bytes calldata _blobsInput, bool _checkBlob)
external
view
Expand Down
27 changes: 26 additions & 1 deletion l1-contracts/src/core/libraries/rollup/STFLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,12 @@ library STFLib {
slotNumber: Slot.wrap(0),
feeHeader: FeeHeader({
excessMana: 0, manaUsed: 0, ethPerFeeAsset: _initialEthPerFeeAsset, congestionCost: 0, proverCost: 0
})
}),
// Genesis Inbox consumption base case, matching the Inbox's genesis bucket-0 sentinel {0, 0, 0}, so
// checkpoint 1 validates its consumption against it.
inboxRollingHash: bytes32(0),
inboxMsgTotal: 0,
inboxConsumedBucket: 0
}).compress();
}

Expand Down Expand Up @@ -305,6 +310,26 @@ library STFLib {
return getStorageTempCheckpointLog(_checkpointNumber).slotNumber.decompress();
}

/**
* @notice Retrieves the cumulative Inbox message count consumed as of a checkpoint
* @dev Gas-efficient accessor reading only the streaming-inbox consumed total. Reverts if the checkpoint is stale.
* @param _checkpointNumber The checkpoint number to get the consumed total for
* @return The cumulative Inbox message count consumed as of the checkpoint
*/
function getInboxMsgTotal(uint256 _checkpointNumber) internal view returns (uint64) {
return getStorageTempCheckpointLog(_checkpointNumber).inboxMsgTotal;
}

/**
* @notice Retrieves the Inbox rolling hash a checkpoint committed to
* @dev Gas-efficient accessor reading only the streaming-inbox rolling hash. Reverts if the checkpoint is stale.
* @param _checkpointNumber The checkpoint number to get the rolling hash for
* @return The consensus Inbox rolling hash recorded for the checkpoint
*/
function getInboxRollingHash(uint256 _checkpointNumber) internal view returns (bytes32) {
return getStorageTempCheckpointLog(_checkpointNumber).inboxRollingHash;
}

/**
* @notice Gets the effective pending checkpoint number based on pruning eligibility
* @dev Returns either the pending checkpoint number or proven checkpoint number depending on
Expand Down
8 changes: 4 additions & 4 deletions l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,10 @@ contract Inbox is IInbox {
currentTree = trees[inProgress];
}

// this is the global leaf index and not index in the checkpoint subtree
// such that users can simply use it and don't need access to a node if they are to consume it in public.
// trees are constant size so global index = tree number * size + subtree index
uint256 index = (inProgress - Constants.INITIAL_CHECKPOINT_NUMBER) * SIZE + currentTree.nextIndex;
// Compact cumulative message index: the zero-based position of this message in the Inbox's
// insertion order, equal to the number of messages inserted before it. It is embedded in the leaf preimage and
// matches the streaming L1-to-L2 tree's leaf count, so consumers do not need per-checkpoint tree geometry.
uint256 index = totalMessagesInserted;

// If the sender is the fee asset portal, we use a magic address to simpler have it initialized at genesis.
// We assume that no-one will know the private key for this address and that the precompile won't change to
Expand Down
Loading
Loading