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: prune if needed #8617

Merged
merged 7 commits into from
Sep 24, 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
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this interface will work, unless you also do a lot of the same accounting in here as you do in the rollup. We should do them in either one or the other.

Specifically, look at the stakeBond and unstakeBond the stake gets a lot of information, some useful some not, but it knows who is bonding etc. But at the unstake it won't be told who is it that is unstaking which makes it weird here.

In the likely case we have, where we are building the escrow specifically for the rollup, you can have the rollup provide enough information for the escrow to know what to do without doubling the accounting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I agree that one escrow per rollup is simpler- that is a deviation from what I was thinking.

Copy link
Contributor

Choose a reason for hiding this comment

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

The unstakeBond is still not identifying who, so it still needs to store a bunch of storage on its own. I suggest making the "mock" without validation but still doing the transfers, such that you can more easily get an idea around the information that needs to be passed along for the minimum.

Also, it is not enough to give a signature, to verify it we need to know the message etc. Either have that logic in the rollup, and then the escrow becomes just bond(address _bonder, uint256 _amount) onlyRollup or have it deal with the full thing.

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 {
just-mitch marked this conversation as resolved.
Show resolved Hide resolved
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
Loading