Skip to content

Commit

Permalink
feat: l1-publisher cleanup (#8148)
Browse files Browse the repository at this point in the history
Doing some cleanup after #7850, looking to address the issue of #8153
and #8110.

- #8153
- Addressed by including a "watcher" as part of the setup, which will
push us to the next slot if there is already a block proposed for the
current one.
- #8110
- Updates the logic in the contract such that we can deal with
"simulating" in the future, and use this from the sequencer.

Gets rid of the `time_traveler` from the l1-publisher, now lives in the
watcher which is used in tests.

Issues related to slot duration is still to be addressed, so the name of
this branch got slightly funky.

Taking over the extra check added in #8204 since i) they are related and
ii) the pain of going through CI made me do it.
  • Loading branch information
LHerskind authored Aug 29, 2024
1 parent 0dd954e commit 6ae2535
Show file tree
Hide file tree
Showing 24 changed files with 517 additions and 328 deletions.
168 changes: 142 additions & 26 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {Errors} from "./libraries/Errors.sol";
import {Constants} from "./libraries/ConstantsGen.sol";
import {MerkleLib} from "./libraries/MerkleLib.sol";
import {SignatureLib} from "./sequencer_selection/SignatureLib.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {DataStructures} from "./libraries/DataStructures.sol";

// Contracts
import {MockVerifier} from "../mock/MockVerifier.sol";
Expand All @@ -31,6 +33,8 @@ import {Leonidas} from "./sequencer_selection/Leonidas.sol";
* not giving a damn about gas costs.
*/
contract Rollup is Leonidas, IRollup, ITestRollup {
using SafeCast for uint256;

struct BlockLog {
bytes32 archive;
bytes32 blockHash;
Expand Down Expand Up @@ -381,6 +385,69 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
return blocks[_blockNumber].archive;
}

/**
* @notice Check if a proposer can propose at a given time
*
* @param _ts - The timestamp to check
* @param _proposer - The proposer to check
* @param _archive - The archive to check (should be the latest archive)
*
* @return uint256 - The slot at the given timestamp
* @return uint256 - The block number at the given timestamp
*/
function canProposeAtTime(uint256 _ts, address _proposer, bytes32 _archive)
external
view
override(IRollup)
returns (uint256, uint256)
{
uint256 slot = getSlotAt(_ts);

uint256 lastSlot = uint256(blocks[pendingBlockCount - 1].slotNumber);
if (slot <= lastSlot) {
revert Errors.Rollup__SlotAlreadyInChain(lastSlot, slot);
}

bytes32 tipArchive = archive();
if (tipArchive != _archive) {
revert Errors.Rollup__InvalidArchive(tipArchive, _archive);
}

if (isDevNet) {
_devnetSequencerSubmissionChecks(_proposer);
} else {
address proposer = getProposerAt(_ts);
if (proposer != address(0) && proposer != _proposer) {
revert Errors.Leonidas__InvalidProposer(proposer, _proposer);
}
}

return (slot, pendingBlockCount);
}

/**
* @notice Validate a header for submission
*
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header
* without having to deal with viem or anvil for simulating timestamps in the future.
*
* @param _header - The header to validate
* @param _signatures - The signatures to validate
* @param _digest - The digest to validate
* @param _currentTime - The current time
* @param _flags - The flags to validate
*/
function validateHeader(
bytes calldata _header,
SignatureLib.Signature[] memory _signatures,
bytes32 _digest,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) external view override(IRollup) {
HeaderLib.Header memory header = HeaderLib.decode(_header);
_validateHeader(header, _signatures, _digest, _currentTime, _flags);
}

/**
* @notice Processes an incoming L2 block with signatures
*
Expand All @@ -397,15 +464,19 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
) public override(IRollup) {
// Decode and validate header
HeaderLib.Header memory header = HeaderLib.decode(_header);
_validateHeaderForSubmissionBase(header);
_validateHeaderForSubmissionSequencerSelection(header, _signatures, _archive);
setupEpoch();
_validateHeader({
_header: header,
_signatures: _signatures,
_digest: _archive,
_currentTime: block.timestamp,
_flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false})
});

// As long as the header is passing validity check in `_validateHeaderForSubmissionBase` we can safely cast
// the slot number to uint128
blocks[pendingBlockCount++] = BlockLog({
archive: _archive,
blockHash: _blockHash,
slotNumber: uint128(header.globalVariables.slotNumber),
slotNumber: header.globalVariables.slotNumber.toUint128(),
isProven: false
});

Expand Down Expand Up @@ -497,6 +568,29 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
}
}

/**
* @notice Validates the header for submission
*
* @param _header - The proposed block header
* @param _signatures - The signatures for the attestations
* @param _digest - The digest that signatures signed
* @param _currentTime - The time of execution
* @dev - This value is provided to allow for simple simulation of future
* @param _flags - Flags specific to the execution, whether certain checks should be skipped
*/
function _validateHeader(
HeaderLib.Header memory _header,
SignatureLib.Signature[] memory _signatures,
bytes32 _digest,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) internal view {
_validateHeaderForSubmissionBase(_header, _currentTime, _flags);
_validateHeaderForSubmissionSequencerSelection(
_header.globalVariables.slotNumber, _signatures, _digest, _currentTime, _flags
);
}

/**
* @notice Validate a header for submission to the pending chain (sequencer selection checks)
*
Expand All @@ -511,15 +605,17 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @dev While in isDevNet, we allow skipping all of the checks as we simply assume only TRUSTED sequencers
*
* @param _header - The header to validate
* @param _slot - The slot of the header to validate
* @param _signatures - The signatures to validate
* @param _archive - The archive root of the block
* @param _digest - The digest that signatures sign over
*/
function _validateHeaderForSubmissionSequencerSelection(
HeaderLib.Header memory _header,
uint256 _slot,
SignatureLib.Signature[] memory _signatures,
bytes32 _archive
) internal {
bytes32 _digest,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) internal view {
if (isDevNet) {
// @note If we are running in a devnet, we don't want to perform all the consensus
// checks, we instead simply require that either there are NO validators or
Expand All @@ -528,36 +624,29 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
// This means that we relaxes the condition that the block must land in the
// correct slot and epoch to make it more fluid for the devnet launch
// or for testing.
if (getValidatorCount() == 0) {
return;
}

if (!isValidator(msg.sender)) {
revert Errors.Leonidas__InvalidProposer(getValidatorAt(0), msg.sender);
}
_devnetSequencerSubmissionChecks(msg.sender);
return;
}

uint256 slot = _header.globalVariables.slotNumber;

// Ensure that the slot proposed is NOT in the future
uint256 currentSlot = getCurrentSlot();
if (slot != currentSlot) {
revert Errors.HeaderLib__InvalidSlotNumber(currentSlot, slot);
uint256 currentSlot = getSlotAt(_currentTime);
if (_slot != currentSlot) {
revert Errors.HeaderLib__InvalidSlotNumber(currentSlot, _slot);
}

// @note We are currently enforcing that the slot is in the current epoch
// If this is not the case, there could potentially be a weird reorg
// of an entire epoch if no-one from the new epoch committee have seen
// those blocks or behaves as if they did not.

uint256 epochNumber = getEpochAt(getTimestampForSlot(slot));
uint256 currentEpoch = getCurrentEpoch();
uint256 epochNumber = getEpochAt(getTimestampForSlot(_slot));
uint256 currentEpoch = getEpochAt(_currentTime);
if (epochNumber != currentEpoch) {
revert Errors.Rollup__InvalidEpoch(currentEpoch, epochNumber);
}

_processPendingBlock(epochNumber, slot, _signatures, _archive);
_processPendingBlock(_slot, _signatures, _digest, _flags);
}

/**
Expand All @@ -577,7 +666,11 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @param _header - The header to validate
*/
function _validateHeaderForSubmissionBase(HeaderLib.Header memory _header) internal view {
function _validateHeaderForSubmissionBase(
HeaderLib.Header memory _header,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) internal view {
if (block.chainid != _header.globalVariables.chainId) {
revert Errors.Rollup__InvalidChainId(block.chainid, _header.globalVariables.chainId);
}
Expand Down Expand Up @@ -612,9 +705,32 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
revert Errors.Rollup__InvalidTimestamp(timestamp, _header.globalVariables.timestamp);
}

if (timestamp > _currentTime) {
// @note If you are hitting this error, it is likely because the chain you use have a blocktime that differs
// from the value that we have in the constants.
// When you are encountering this, it will likely be as the sequencer expects to be able to include
// an Aztec block in the "next" ethereum block based on a timestamp that is 12 seconds in the future
// from the last block. However, if the actual will only be 1 second in the future, you will end up
// expecting this value to be in the future.
revert Errors.Rollup__TimestampInFuture(_currentTime, timestamp);
}

// Check if the data is available using availability oracle (change availability oracle if you want a different DA layer)
if (!AVAILABILITY_ORACLE.isAvailable(_header.contentCommitment.txsEffectsHash)) {
if (
!_flags.ignoreDA && !AVAILABILITY_ORACLE.isAvailable(_header.contentCommitment.txsEffectsHash)
) {
revert Errors.Rollup__UnavailableTxs(_header.contentCommitment.txsEffectsHash);
}
}

function _devnetSequencerSubmissionChecks(address _proposer) internal view {
if (getValidatorCount() == 0) {
return;
}

if (!isValidator(_proposer)) {
revert Errors.DevNet__InvalidProposer(getValidatorAt(0), _proposer);
}
return;
}
}
13 changes: 13 additions & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IInbox} from "../interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "../interfaces/messagebridge/IOutbox.sol";

import {SignatureLib} from "../sequencer_selection/SignatureLib.sol";
import {DataStructures} from "../libraries/DataStructures.sol";

interface ITestRollup {
function setDevNet(bool _devNet) external;
Expand All @@ -20,6 +21,18 @@ interface IRollup {
event ProgressedState(uint256 provenBlockCount, uint256 pendingBlockCount);
event PrunedPending(uint256 provenBlockCount, uint256 pendingBlockCount);

function canProposeAtTime(uint256 _ts, address _proposer, bytes32 _archive)
external
view
returns (uint256, uint256);
function validateHeader(
bytes calldata _header,
SignatureLib.Signature[] memory _signatures,
bytes32 _digest,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) external view;

function prune() external;

function INBOX() external view returns (IInbox);
Expand Down
5 changes: 5 additions & 0 deletions l1-contracts/src/core/libraries/DataStructures.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,9 @@ library DataStructures {
uint256 blockNumber;
}
// docs:end:registry_snapshot

struct ExecutionFlags {
bool ignoreDA;
bool ignoreSignatures;
}
}
3 changes: 2 additions & 1 deletion l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pragma solidity >=0.8.18;
library Errors {
// DEVNET related
error DevNet__NoPruningAllowed(); // 0x6984c590
error DevNet__InvalidProposer(address expected, address actual); // 0x11e6e6f7

// Inbox
error Inbox__Unauthorized(); // 0xe5336a6b
Expand Down Expand Up @@ -55,7 +56,7 @@ library Errors {
error Rollup__InvalidChainId(uint256 expected, uint256 actual); // 0x37b5bc12
error Rollup__InvalidVersion(uint256 expected, uint256 actual); // 0x9ef30794
error Rollup__InvalidTimestamp(uint256 expected, uint256 actual); // 0x3132e895
error Rollup__TimestampInFuture(); // 0xbc1ce916
error Rollup__TimestampInFuture(uint256 max, uint256 actual); // 0x89f30690
error Rollup__TimestampTooOld(); // 0x72ed9c81
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3
error Rollup__NothingToPrune(); // 0x850defd3
Expand Down
36 changes: 18 additions & 18 deletions l1-contracts/src/core/sequencer_selection/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.18;

import {DataStructures} from "../libraries/DataStructures.sol";
import {Errors} from "../libraries/Errors.sol";
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";
import {Ownable} from "@oz/access/Ownable.sol";
Expand Down Expand Up @@ -349,29 +350,18 @@ contract Leonidas is Ownable, ILeonidas {
* - If the proposer is not the real proposer AND the proposer is not open
* - If the number of valid attestations is insufficient
*
* @param _epochNumber - The epoch number of the block
* @param _slot - The slot of the block
* @param _signatures - The signatures of the committee members
* @param _digest - The digest of the block
*/
function _processPendingBlock(
uint256 _epochNumber,
uint256 _slot,
SignatureLib.Signature[] memory _signatures,
bytes32 _digest
) internal {
// @note Setup the CURRENT epoch if not already done.
// not necessarily the one we are processing!
setupEpoch();

Epoch storage epoch = epochs[_epochNumber];

// We should never enter this case because of `setupEpoch`
if (epoch.sampleSeed == 0) {
revert Errors.Leonidas__EpochNotSetup();
}

address proposer = getProposerAt(getTimestampForSlot(_slot));
bytes32 _digest,
DataStructures.ExecutionFlags memory _flags
) internal view {
uint256 ts = getTimestampForSlot(_slot);
address proposer = getProposerAt(ts);

// If the proposer is open, we allow anyone to propose without needing any signatures
if (proposer == address(0)) {
Expand All @@ -383,7 +373,17 @@ contract Leonidas is Ownable, ILeonidas {
revert Errors.Leonidas__InvalidProposer(proposer, msg.sender);
}

uint256 needed = epoch.committee.length * 2 / 3 + 1;
// @note This is NOT the efficient way to do it, but it is a very convenient way for us to do it
// that allows us to reduce the number of code paths. Also when changed with optimistic for
// pleistarchus, this will be changed, so we can live with it.

if (_flags.ignoreSignatures) {
return;
}

address[] memory committee = getCommitteeAt(ts);

uint256 needed = committee.length * 2 / 3 + 1;
if (_signatures.length < needed) {
revert Errors.Leonidas__InsufficientAttestationsProvided(needed, _signatures.length);
}
Expand All @@ -400,7 +400,7 @@ contract Leonidas is Ownable, ILeonidas {
}

// The verification will throw if invalid
signature.verify(epoch.committee[i], ethSignedDigest);
signature.verify(committee[i], ethSignedDigest);
validAttestations++;
}

Expand Down
Loading

0 comments on commit 6ae2535

Please sign in to comment.