Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add pending reorg chain resistance in contracts #7926

Merged
merged 4 commits into from
Aug 15, 2024
Merged
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
43 changes: 41 additions & 2 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
bool isProven;
}

// @note The number of slots within which a block must be proven
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue

// This number is currently pulled out of thin air and should be replaced when we are not blind
// @todo #8018
uint256 public constant TIMELINESS_PROVING_IN_SLOTS = 100;

IRegistry public immutable REGISTRY;
IAvailabilityOracle public immutable AVAILABILITY_ORACLE;
IInbox public immutable INBOX;
Expand Down Expand Up @@ -87,11 +92,44 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
provenBlockCount = 1;
}

/**
* @notice Prune the pending chain up to the last proven block
*
* @dev Will revert if there is nothing to prune or if the chain is not ready to be pruned
*/
function prune() external override(IRollup) {
if (pendingBlockCount == provenBlockCount) {
revert Errors.Rollup__NothingToPrune();
}

BlockLog storage firstPendingNotInProven = blocks[provenBlockCount];
uint256 prunableAtSlot =
uint256(firstPendingNotInProven.slotNumber) + TIMELINESS_PROVING_IN_SLOTS;
uint256 currentSlot = getCurrentSlot();

if (currentSlot < prunableAtSlot) {
revert Errors.Rollup__NotReadyToPrune(currentSlot, prunableAtSlot);
}

// @note We are not deleting the blocks, but we are "winding back" the pendingBlockCount
// to the last block that was proven.
// The reason we can do this, is that any new block proposed will overwrite a previous block
// so no values should "survive". It it is however slightly odd for people reading
// the chain separately from the contract without using pendingBlockCount as a boundary.
pendingBlockCount = provenBlockCount;

emit PrunedPending(provenBlockCount, pendingBlockCount);
}

/**
* Sets the assumeProvenUntilBlockNumber. Only the contract deployer can set it.
* @param blockNumber - New value.
*/
function setAssumeProvenUntilBlockNumber(uint256 blockNumber) external onlyOwner {
function setAssumeProvenUntilBlockNumber(uint256 blockNumber)
external
override(ITestRollup)
onlyOwner
{
if (blockNumber > provenBlockCount && blockNumber <= pendingBlockCount) {
for (uint256 i = provenBlockCount; i < blockNumber; i++) {
blocks[i].isProven = true;
Expand Down Expand Up @@ -308,7 +346,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
isProven: false
});

bytes32 inHash = INBOX.consume();
// @note The block number here will always be >=1 as the genesis block is at 0
bytes32 inHash = INBOX.consume(header.globalVariables.blockNumber);
if (header.contentCommitment.inHash != inHash) {
revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash);
}
Expand Down
4 changes: 4 additions & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ interface ITestRollup {
function setDevNet(bool _devNet) external;
function setVerifier(address _verifier) external;
function setVkTreeRoot(bytes32 _vkTreeRoot) external;
function setAssumeProvenUntilBlockNumber(uint256 blockNumber) external;
}

interface IRollup {
event L2BlockProcessed(uint256 indexed blockNumber);
event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId);
event ProgressedState(uint256 provenBlockCount, uint256 pendingBlockCount);
event PrunedPending(uint256 provenBlockCount, uint256 pendingBlockCount);

function prune() external;

function INBOX() external view returns (IInbox);

Expand Down
5 changes: 4 additions & 1 deletion l1-contracts/src/core/interfaces/messagebridge/IInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ interface IInbox {
* @dev Only callable by the rollup contract
* @dev In the first iteration we return empty tree root because first block's messages tree is always
* empty because there has to be a 1 block lag to prevent sequencer DOS attacks
*
* @param _toConsume - The block number to consume
*
* @return The root of the consumed tree
*/
function consume() external returns (bytes32);
function consume(uint256 _toConsume) external returns (bytes32);
// docs:end:consume
}
14 changes: 14 additions & 0 deletions l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,18 @@ interface IOutbox {
view
returns (bool);
// docs:end:outbox_has_message_been_consumed_at_block_and_index

/**
* @notice Fetch the root data for a given block number
* Returns (0, 0) if the block is not proven
*
* @param _l2BlockNumber - The block number to fetch the root data for
*
* @return root - The root of the merkle tree containing the L2 to L1 messages
* @return minHeight - The min height for the the merkle tree that the root corresponds to
*/
function getRootData(uint256 _l2BlockNumber)
external
view
returns (bytes32 root, uint256 minHeight);
}
3 changes: 3 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ library Errors {
error Outbox__InvalidRecipient(address expected, address actual); // 0x57aad581
error Outbox__AlreadyNullified(uint256 l2BlockNumber, uint256 leafIndex); // 0xfd71c2d4
error Outbox__NothingToConsumeAtBlock(uint256 l2BlockNumber); // 0xa4508f22
error Outbox__BlockNotProven(uint256 l2BlockNumber); // 0x0e194a6d

// Rollup
error Rollup__InvalidArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
Expand All @@ -53,6 +54,8 @@ library Errors {
error Rollup__TimestampInFuture(); // 0xbc1ce916
error Rollup__TimestampTooOld(); // 0x72ed9c81
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3
error Rollup__NothingToPrune(); // 0x850defd3
error Rollup__NotReadyToPrune(uint256 currentSlot, uint256 prunableAt); // 0x9fdf1614

// Registry
error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf
Expand Down
23 changes: 14 additions & 9 deletions l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {FrontierMerkle} from "./frontier_tree/Frontier.sol";
* @title Inbox
* @author Aztec Labs
* @notice Lives on L1 and is used to pass messages into the rollup, e.g., L1 -> L2 messages.
*
* @dev The current code is horribly gas inefficient, and should only be used as a reference implementation.
*/
contract Inbox is IInbox {
using Hash for DataStructures.L1ToL2Msg;
Expand All @@ -30,12 +32,10 @@ contract Inbox is IInbox {
uint256 internal immutable SIZE;
bytes32 internal immutable EMPTY_ROOT; // The root of an empty frontier tree

// Number of a tree which is ready to be consumed
uint256 public toConsume = Constants.INITIAL_L2_BLOCK_NUM;
// Number of a tree which is currently being filled
uint256 public inProgress = Constants.INITIAL_L2_BLOCK_NUM + 1;

mapping(uint256 blockNumber => IFrontier tree) internal trees;
mapping(uint256 blockNumber => IFrontier tree) public trees;

constructor(address _rollup, uint256 _height) {
ROLLUP = _rollup;
Expand All @@ -52,10 +52,13 @@ contract Inbox is IInbox {

/**
* @notice Inserts a new message into the Inbox
*
* @dev Emits `MessageSent` with data for easy access by the sequencer
*
* @param _recipient - The recipient of the message
* @param _content - The content of the message (application specific)
* @param _secretHash - The secret hash of the message (make it possible to hide when a specific message is consumed on L2)
*
* @return Hash of the sent message.
*/
function sendL2Message(
Expand Down Expand Up @@ -96,29 +99,31 @@ contract Inbox is IInbox {

/**
* @notice Consumes the current tree, and starts a new one if needed
*
* @dev Only callable by the rollup contract
* @dev In the first iteration we return empty tree root because first block's messages tree is always
* empty because there has to be a 1 block lag to prevent sequencer DOS attacks
*
* @param _toConsume - The block number to consume
*
* @return The root of the consumed tree
*/
function consume() external override(IInbox) returns (bytes32) {
function consume(uint256 _toConsume) external override(IInbox) returns (bytes32) {
if (msg.sender != ROLLUP) {
revert Errors.Inbox__Unauthorized();
}

bytes32 root = EMPTY_ROOT;
if (toConsume > Constants.INITIAL_L2_BLOCK_NUM) {
root = trees[toConsume].root();
if (_toConsume > Constants.INITIAL_L2_BLOCK_NUM) {
root = trees[_toConsume].root();
}

// If we are "catching up" we skip the tree creation as it is already there
if (toConsume + 1 == inProgress) {
if (_toConsume + 1 == inProgress) {
inProgress += 1;
trees[inProgress] = IFrontier(new FrontierMerkle(HEIGHT));
}

toConsume += 1;

return root;
}
}
52 changes: 41 additions & 11 deletions l1-contracts/src/core/messagebridge/Outbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ pragma solidity >=0.8.18;
// Libraries
import {DataStructures} from "../libraries/DataStructures.sol";
import {Errors} from "../libraries/Errors.sol";
import {Constants} from "../libraries/ConstantsGen.sol";
import {MerkleLib} from "../libraries/MerkleLib.sol";
import {Hash} from "../libraries/Hash.sol";
import {IOutbox} from "../interfaces/messagebridge/IOutbox.sol";

import {Rollup} from "../Rollup.sol";

/**
* @title Outbox
* @author Aztec Labs
Expand All @@ -26,18 +27,19 @@ contract Outbox is IOutbox {
mapping(uint256 => bool) nullified;
}

address public immutable ROLLUP_CONTRACT;
mapping(uint256 l2BlockNumber => RootData) public roots;
Rollup public immutable ROLLUP;
mapping(uint256 l2BlockNumber => RootData) internal roots;

constructor(address _rollup) {
ROLLUP_CONTRACT = _rollup;
ROLLUP = Rollup(_rollup);
}

/**
* @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in
* a block specified by _l2BlockNumber.
* @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in a block
*
* @dev Only callable by the rollup contract
* @dev Emits `RootAdded` upon inserting the root successfully
*
* @param _l2BlockNumber - The L2 Block Number in which the L2 to L1 messages reside
* @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves
* @param _minHeight - The min height of the merkle tree that the root corresponds to
Expand All @@ -46,14 +48,10 @@ contract Outbox is IOutbox {
external
override(IOutbox)
{
if (msg.sender != ROLLUP_CONTRACT) {
if (msg.sender != address(ROLLUP)) {
revert Errors.Outbox__Unauthorized();
}

if (roots[_l2BlockNumber].root != bytes32(0)) {
revert Errors.Outbox__RootAlreadySetAtBlock(_l2BlockNumber);
}

if (_root == bytes32(0)) {
revert Errors.Outbox__InsertingInvalidRoot();
}
Expand All @@ -66,8 +64,10 @@ contract Outbox is IOutbox {

/**
* @notice Consumes an entry from the Outbox
*
* @dev Only useable by portals / recipients of messages
* @dev Emits `MessageConsumed` when consuming messages
*
* @param _message - The L2 to L1 message
* @param _l2BlockNumber - The block number specifying the block that contains the message we want to consume
* @param _leafIndex - The index inside the merkle tree where the message is located
Expand All @@ -81,6 +81,10 @@ contract Outbox is IOutbox {
uint256 _leafIndex,
bytes32[] calldata _path
) external override(IOutbox) {
if (_l2BlockNumber >= ROLLUP.provenBlockCount()) {
revert Errors.Outbox__BlockNotProven(_l2BlockNumber);
}

if (msg.sender != _message.recipient.actor) {
revert Errors.Outbox__InvalidRecipient(_message.recipient.actor, msg.sender);
}
Expand Down Expand Up @@ -121,9 +125,13 @@ contract Outbox is IOutbox {

/**
* @notice Checks to see if an index of the L2 to L1 message tree for a specific block has been consumed
*
* @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false
*
* @param _l2BlockNumber - The block number specifying the block that contains the index of the message we want to check
* @param _leafIndex - The index of the message inside the merkle tree
*
* @return bool - True if the message has been consumed, false otherwise
*/
function hasMessageBeenConsumedAtBlockAndIndex(uint256 _l2BlockNumber, uint256 _leafIndex)
external
Expand All @@ -133,4 +141,26 @@ contract Outbox is IOutbox {
{
return roots[_l2BlockNumber].nullified[_leafIndex];
}

/**
* @notice Fetch the root data for a given block number
* Returns (0, 0) if the block is not proven
*
* @param _l2BlockNumber - The block number to fetch the root data for
*
* @return root - The root of the merkle tree containing the L2 to L1 messages
* @return minHeight - The min height for the the merkle tree that the root corresponds to
*/
function getRootData(uint256 _l2BlockNumber)
external
view
override(IOutbox)
returns (bytes32 root, uint256 minHeight)
{
if (_l2BlockNumber >= ROLLUP.provenBlockCount()) {
return (bytes32(0), 0);
}
RootData storage rootData = roots[_l2BlockNumber];
return (rootData.root, rootData.minHeight);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ pragma solidity >=0.8.18;

import {Hash} from "../../libraries/Hash.sol";
import {IFrontier} from "../../interfaces/messagebridge/IFrontier.sol";
import {Ownable} from "@oz/access/Ownable.sol";

// This truncates each hash and hash preimage to 31 bytes to follow Noir.
// It follows the logic in /noir-protocol-circuits/crates/parity-lib/src/utils/sha256_merkle_tree.nr
// TODO(Miranda): Possibly nuke this contract, and use a generic version which can either use
// regular sha256 or sha256ToField when emulating circuits
contract FrontierMerkle is IFrontier {
contract FrontierMerkle is IFrontier, Ownable {
uint256 public immutable HEIGHT;
uint256 public immutable SIZE;

Expand All @@ -21,7 +22,7 @@ contract FrontierMerkle is IFrontier {
// for the zeros at each level. This would save gas on computations
mapping(uint256 level => bytes32 zero) public zeros;

constructor(uint256 _height) {
constructor(uint256 _height) Ownable(msg.sender) {
HEIGHT = _height;
SIZE = 2 ** _height;

Expand All @@ -31,7 +32,7 @@ contract FrontierMerkle is IFrontier {
}
}

function insertLeaf(bytes32 _leaf) external override(IFrontier) returns (uint256) {
function insertLeaf(bytes32 _leaf) external override(IFrontier) onlyOwner returns (uint256) {
uint256 index = nextIndex;
uint256 level = _computeLevel(index);
bytes32 right = _leaf;
Expand Down
8 changes: 5 additions & 3 deletions l1-contracts/src/core/sequencer_selection/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,24 @@ contract Leonidas is Ownable, ILeonidas {
uint256 nextSeed;
}

// @note @LHerskind - The multiple cause pain and suffering in the E2E tests as we introduce
// @note @LHerskind The multiple cause pain and suffering in the E2E tests as we introduce
// a timeliness requirement into the publication that did not exists before,
// and at the same time have a setup that will impact the time at every tx
// because of auto-mine. By using just 1, we can make our test work
// but anything using an actual working chain would eat dung as simulating
// transactions is slower than an actual ethereum slot.
//
// The value should be a higher multiple for any actual chain
// @todo #8019
uint256 public constant SLOT_DURATION = Constants.ETHEREUM_SLOT_DURATION * 1;

// The duration of an epoch in slots
// @todo @LHerskind - This value should be updated when we are not blind.
// @todo @LHerskind - This value should be updated when we are not blind.
// @todo #8020
uint256 public constant EPOCH_DURATION = 32;

// The target number of validators in a committee
// @todo @LHerskind - This value should be updated when we are not blind.
// @todo #8021
uint256 public constant TARGET_COMMITTEE_SIZE = EPOCH_DURATION;

// The time that the contract was deployed
Expand Down
Loading
Loading