Skip to content

Commit

Permalink
feat: prune if needed (#8617)
Browse files Browse the repository at this point in the history
Fix #8572 
Fix #8608
  • Loading branch information
just-mitch authored Sep 24, 2024
1 parent 80acfd9 commit 49b17d0
Show file tree
Hide file tree
Showing 12 changed files with 629 additions and 163 deletions.
200 changes: 152 additions & 48 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity >=0.8.18;

// Interfaces
import {IRollup, ITestRollup} from "./interfaces/IRollup.sol";
import {IProofCommitmentEscrow} from "./interfaces/IProofCommitmentEscrow.sol";
import {IInbox} from "./interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "./interfaces/messagebridge/IOutbox.sol";
import {IRegistry} from "./interfaces/messagebridge/IRegistry.sol";
Expand All @@ -15,13 +16,14 @@ import {HeaderLib} from "./libraries/HeaderLib.sol";
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 {SignatureLib} from "./libraries/SignatureLib.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {DataStructures} from "./libraries/DataStructures.sol";
import {TxsDecoder} from "./libraries/decoders/TxsDecoder.sol";

// Contracts
import {MockVerifier} from "../mock/MockVerifier.sol";
import {MockProofCommitmentEscrow} from "../mock/MockProofCommitmentEscrow.sol";
import {Inbox} from "./messagebridge/Inbox.sol";
import {Outbox} from "./messagebridge/Outbox.sol";
import {Leonidas} from "./sequencer_selection/Leonidas.sol";
Expand All @@ -46,20 +48,22 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
uint128 slotNumber;
}

// @note The number of slots within which a block must be proven
// 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;
// See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb
// for justification of CLAIM_DURATION_IN_L2_SLOTS.
uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13;
uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000;

uint256 public immutable L1_BLOCK_AT_GENESIS;
IRegistry public immutable REGISTRY;
IInbox public immutable INBOX;
IOutbox public immutable OUTBOX;
IProofCommitmentEscrow public immutable PROOF_COMMITMENT_ESCROW;
uint256 public immutable VERSION;
IFeeJuicePortal public immutable FEE_JUICE_PORTAL;
IVerifier public verifier;

ChainTips public tips;
DataStructures.EpochProofClaim public proofClaim;

// @todo Validate assumption:
// Currently we assume that the archive root following a block is specific to the block
Expand All @@ -84,6 +88,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
verifier = new MockVerifier();
REGISTRY = _registry;
FEE_JUICE_PORTAL = _fpcJuicePortal;
PROOF_COMMITMENT_ESCROW = new MockProofCommitmentEscrow();
INBOX = new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT);
OUTBOX = new Outbox(address(this));
vkTreeRoot = _vkTreeRoot;
Expand All @@ -102,57 +107,17 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
setupEpoch();
}

function status(uint256 myHeaderBlockNumber)
external
view
override(IRollup)
returns (
uint256 provenBlockNumber,
bytes32 provenArchive,
uint256 pendingBlockNumber,
bytes32 pendingArchive,
bytes32 archiveOfMyBlock
)
{
return (
tips.provenBlockNumber,
blocks[tips.provenBlockNumber].archive,
tips.pendingBlockNumber,
blocks[tips.pendingBlockNumber].archive,
archiveAt(myHeaderBlockNumber)
);
}

/**
* @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
*
* @dev While in devnet, this will be guarded behind an `onlyOwner`
*/
function prune() external override(IRollup) onlyOwner {
if (tips.pendingBlockNumber == tips.provenBlockNumber) {
function prune() external override(IRollup) {
if (!_canPrune()) {
revert Errors.Rollup__NothingToPrune();
}

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

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

uint256 pending = tips.pendingBlockNumber;

// @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven.
// We can do because any new block proposed will overwrite a previous block in the block log,
// so no values should "survive".
// People must therefore read the chain using the pendingTip as a boundary.
tips.pendingBlockNumber = tips.provenBlockNumber;

emit PrunedPending(tips.provenBlockNumber, pending);
_prune();
}

/**
Expand Down Expand Up @@ -192,6 +157,60 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
vkTreeRoot = _vkTreeRoot;
}

function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote)
external
override(IRollup)
{
uint256 currentSlot = getCurrentSlot();
address currentProposer = getCurrentProposer();
uint256 epochToProve = getEpochToProve();

if (currentProposer != address(0) && currentProposer != msg.sender) {
revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender);
}

if (_quote.epochToProve != epochToProve) {
revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve);
}

if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) {
revert Errors.Rollup__NotInClaimPhase(
currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS
);
}

// if the epoch to prove is not the one that has been claimed,
// then whatever is in the proofClaim is stale
if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) {
revert Errors.Rollup__ProofRightAlreadyClaimed();
}

if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) {
revert Errors.Rollup__InsufficientBondAmount(
PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount
);
}

if (_quote.validUntilSlot < currentSlot) {
revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot);
}

// We don't currently unstake,
// but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652.
// Blocked on submitting epoch proofs to this contract.
PROOF_COMMITMENT_ESCROW.stakeBond(_quote.bondAmount, _quote.prover);

proofClaim = DataStructures.EpochProofClaim({
epochToProve: epochToProve,
basisPointFee: _quote.basisPointFee,
bondAmount: _quote.bondAmount,
bondProvider: _quote.prover,
proposerClaimant: msg.sender
});

emit ProofRightClaimed(epochToProve, _quote.prover, msg.sender, _quote.bondAmount, currentSlot);
}

/**
* @notice Publishes the body and propose the block
* @dev `eth_log_handlers` rely on this function
Expand All @@ -210,6 +229,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) external override(IRollup) {
if (_canPrune()) {
_prune();
}
bytes32 txsEffectsHash = TxsDecoder.decode(_body);

// Decode and validate header
Expand Down Expand Up @@ -294,6 +316,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
bytes calldata _aggregationObject,
bytes calldata _proof
) external override(IRollup) {
if (_canPrune()) {
_prune();
}
HeaderLib.Header memory header = HeaderLib.decode(_header);

if (header.globalVariables.blockNumber > tips.pendingBlockNumber) {
Expand Down Expand Up @@ -403,6 +428,27 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
emit L2ProofVerified(header.globalVariables.blockNumber, _proverId);
}

function status(uint256 myHeaderBlockNumber)
external
view
override(IRollup)
returns (
uint256 provenBlockNumber,
bytes32 provenArchive,
uint256 pendingBlockNumber,
bytes32 pendingArchive,
bytes32 archiveOfMyBlock
)
{
return (
tips.provenBlockNumber,
blocks[tips.provenBlockNumber].archive,
tips.pendingBlockNumber,
blocks[tips.pendingBlockNumber].archive,
archiveAt(myHeaderBlockNumber)
);
}

/**
* @notice Check if msg.sender can propose at a given time
*
Expand Down Expand Up @@ -489,6 +535,23 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
return tips.pendingBlockNumber;
}

/**
* @notice Get the epoch that should be proven
*
* @dev This is the epoch that should be proven. It does so by getting the epoch of the block
* following the last proven block. If there is no such block (i.e. the pending chain is
* the same as the proven chain), then revert.
*
* @return uint256 - The epoch to prove
*/
function getEpochToProve() public view override(IRollup) returns (uint256) {
if (tips.provenBlockNumber == tips.pendingBlockNumber) {
revert Errors.Rollup__NoEpochToProve();
} else {
return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber);
}
}

/**
* @notice Get the archive root of a specific block
*
Expand All @@ -503,6 +566,47 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
return bytes32(0);
}

function _prune() internal {
// TODO #8656
delete proofClaim;

uint256 pending = tips.pendingBlockNumber;

// @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven.
// We can do because any new block proposed will overwrite a previous block in the block log,
// so no values should "survive".
// People must therefore read the chain using the pendingTip as a boundary.
tips.pendingBlockNumber = tips.provenBlockNumber;

emit PrunedPending(tips.provenBlockNumber, pending);
}

function _canPrune() internal view returns (bool) {
if (tips.pendingBlockNumber == tips.provenBlockNumber) {
return false;
}

uint256 currentSlot = getCurrentSlot();
uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber);
uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION;

// suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch.
// we prune the pending chain back to the end of epoch 1 if:
// - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2)
// - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted)
bool inClaimPhase = currentSlot
< startSlotOfPendingEpoch + Constants.AZTEC_EPOCH_DURATION + CLAIM_DURATION_IN_L2_SLOTS;

bool claimExists = currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION
&& proofClaim.epochToProve == oldestPendingEpoch && proofClaim.proposerClaimant != address(0);

if (inClaimPhase || claimExists) {
// If we are in the claim phase, do not prune
return false;
}
return true;
}

/**
* @notice Validates the header for submission
*
Expand Down
12 changes: 12 additions & 0 deletions l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.18;

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

interface IProofCommitmentEscrow {
function deposit(uint256 _amount) external;
function withdraw(uint256 _amount) external;
function stakeBond(uint256 _bondAmount, address _prover) external;
function unstakeBond(uint256 _bondAmount, address _prover) external;
}
15 changes: 14 additions & 1 deletion l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity >=0.8.18;
import {IInbox} from "../interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "../interfaces/messagebridge/IOutbox.sol";

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

interface ITestRollup {
Expand All @@ -18,9 +18,18 @@ interface IRollup {
event L2BlockProposed(uint256 indexed blockNumber, bytes32 indexed archive);
event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId);
event PrunedPending(uint256 provenBlockNumber, uint256 pendingBlockNumber);
event ProofRightClaimed(
uint256 indexed epoch,
address indexed bondProvider,
address indexed proposer,
uint256 bondAmount,
uint256 currentSlot
);

function prune() external;

function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external;

function propose(
bytes calldata _header,
bytes32 _archive,
Expand Down Expand Up @@ -48,10 +57,13 @@ interface IRollup {
DataStructures.ExecutionFlags memory _flags
) external view;

// solhint-disable-next-line func-name-mixedcase
function INBOX() external view returns (IInbox);

// solhint-disable-next-line func-name-mixedcase
function OUTBOX() external view returns (IOutbox);

// solhint-disable-next-line func-name-mixedcase
function L1_BLOCK_AT_GENESIS() external view returns (uint256);

function status(uint256 myHeaderBlockNumber)
Expand Down Expand Up @@ -81,5 +93,6 @@ interface IRollup {
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
function getProvenBlockNumber() external view returns (uint256);
function getPendingBlockNumber() external view returns (uint256);
function getEpochToProve() external view returns (uint256);
function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32);
}
19 changes: 19 additions & 0 deletions l1-contracts/src/core/libraries/DataStructures.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

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

/**
* @title Data Structures Library
* @author Aztec Labs
Expand Down Expand Up @@ -79,4 +81,21 @@ library DataStructures {
bool ignoreDA;
bool ignoreSignatures;
}

struct EpochProofQuote {
SignatureLib.Signature signature;
uint256 epochToProve;
uint256 validUntilSlot;
uint256 bondAmount;
address prover;
uint32 basisPointFee;
}

struct EpochProofClaim {
uint256 epochToProve; // the epoch that the bond provider is claiming to prove
uint256 basisPointFee; // the fee that the bond provider will receive as a percentage of the block rewards
uint256 bondAmount; // the amount of escrowed funds that the bond provider will stake. Must be at least PROOF_COMMITMENT_BOND_AMOUNT
address bondProvider; // the address that has deposited funds in the escrow contract
address proposerClaimant; // the address of the proposer that submitted the claim
}
}
Loading

0 comments on commit 49b17d0

Please sign in to comment.