Skip to content

Commit

Permalink
feat: cleanup publisher
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Aug 28, 2024
1 parent eaf2271 commit f24d9da
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 216 deletions.
161 changes: 132 additions & 29 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,90 @@ 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) {
// @note This is a convenience function to allow for easier testing of the header validation
// without having to go through the entire process of submitting a block.
// This is not used in production code.

HeaderLib.Header memory header = HeaderLib.decode(_header);
_validateHeader(header, _signatures, _digest, _currentTime, _flags);
}

function _validateHeader(
HeaderLib.Header memory _header,
SignatureLib.Signature[] memory _signatures,
bytes32 _digest,
uint256 _currentTime,
DataStructures.ExecutionFlags memory _flags
) internal view {
// @note This is a convenience function to allow for easier testing of the header validation
// without having to go through the entire process of submitting a block.
// This is not used in production code.

_validateHeaderForSubmissionBase(_header, _currentTime, _flags);
_validateHeaderForSubmissionSequencerSelection(
_header.globalVariables.slotNumber, _signatures, _digest, _currentTime, _flags
);
}

/**
* @notice Processes an incoming L2 block with signatures
*
Expand All @@ -397,15 +485,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 @@ -511,15 +603,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 +622,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 +664,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,19 +703,31 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
revert Errors.Rollup__InvalidTimestamp(timestamp, _header.globalVariables.timestamp);
}

if (timestamp > block.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(block.timestamp, timestamp);
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;
}
}
1 change: 1 addition & 0 deletions 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
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 f24d9da

Please sign in to comment.