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 log of blocks proposed and split pending/proven #7635

Merged
merged 3 commits into from
Jul 31, 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
4 changes: 2 additions & 2 deletions l1-contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ remappings = [
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

fs_permissions = [
{access = "read", path = "./test/fixtures/mixed_block_0.json"},
{access = "read", path = "./test/fixtures/mixed_block_1.json"},
{access = "read", path = "./test/fixtures/empty_block_0.json"},
{access = "read", path = "./test/fixtures/mixed_block_2.json"},
{access = "read", path = "./test/fixtures/empty_block_1.json"},
{access = "read", path = "./test/fixtures/empty_block_2.json"},
]

[fmt]
Expand Down
125 changes: 116 additions & 9 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import {Leonidas} from "./sequencer_selection/Leonidas.sol";
* not giving a damn about gas costs.
*/
contract Rollup is Leonidas, IRollup {
struct BlockLog {
bytes32 archive;
bool isProven;
}

IRegistry public immutable REGISTRY;
IAvailabilityOracle public immutable AVAILABILITY_ORACLE;
IInbox public immutable INBOX;
Expand All @@ -40,12 +45,22 @@ contract Rollup is Leonidas, IRollup {
IERC20 public immutable GAS_TOKEN;

IVerifier public verifier;
bytes32 public archive; // Root of the archive tree

uint256 public lastBlockTs;
// Tracks the last time time was warped on L2 ("warp" is the testing cheatcode).
// See https://github.com/AztecProtocol/aztec-packages/issues/1614
uint256 public lastWarpedBlockTs;

uint256 public pendingBlockCount;
uint256 public provenBlockCount;

// @todo Validate assumption:
// Currently we assume that the archive root following a block is specific to the block
// e.g., changing any values in the block or header should in the end make its way to the archive
//
// More direct approach would be storing keccak256(header) as well
mapping(uint256 blockNumber => BlockLog log) public blocks;

bytes32 public vkTreeRoot;

constructor(
Expand All @@ -62,6 +77,11 @@ contract Rollup is Leonidas, IRollup {
OUTBOX = new Outbox(address(this));
vkTreeRoot = _vkTreeRoot;
VERSION = 1;

// Genesis block
blocks[0] = BlockLog(bytes32(0), true);
pendingBlockCount = 1;
provenBlockCount = 1;
}

function setVerifier(address _verifier) external override(IRollup) {
Expand All @@ -73,6 +93,18 @@ contract Rollup is Leonidas, IRollup {
vkTreeRoot = _vkTreeRoot;
}

function archive() public view returns (bytes32) {
return blocks[pendingBlockCount - 1].archive;
}

function isBlockProven(uint256 _blockNumber) public view returns (bool) {
return blocks[_blockNumber].isProven;
}

function archiveAt(uint256 _blockNumber) public view returns (bytes32) {
return blocks[_blockNumber].archive;
}

/**
* @notice Process an incoming L2 block and progress the state
* @param _header - The L2 block header
Expand All @@ -88,14 +120,21 @@ contract Rollup is Leonidas, IRollup {

// Decode and validate header
HeaderLib.Header memory header = HeaderLib.decode(_header);
HeaderLib.validate(header, VERSION, lastBlockTs, archive);
HeaderLib.validate(header, VERSION, lastBlockTs, archive());

if (header.globalVariables.blockNumber != pendingBlockCount) {
revert Errors.Rollup__InvalidBlockNumber(
pendingBlockCount, header.globalVariables.blockNumber
);
}

// 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)) {
revert Errors.Rollup__UnavailableTxs(header.contentCommitment.txsEffectsHash);
}

archive = _archive;
blocks[pendingBlockCount++] = BlockLog(_archive, false);

lastBlockTs = block.timestamp;

bytes32 inHash = INBOX.consume();
Expand Down Expand Up @@ -124,6 +163,30 @@ contract Rollup is Leonidas, IRollup {
process(_header, _archive, emptySignatures);
}

/**
* @notice Submit a proof for a block in the pending chain
*
* @dev Will call `_progressState` to update the proven chain. Notice this have potentially
* unbounded gas consumption.
*
* @dev Will emit `L2ProofVerified` if the proof is valid
*
* @dev Will throw if:
* - The block number is past the pending chain
* - The last archive root of the header does not match the archive root of parent block
* - The archive root of the header does not match the archive root of the proposed block
* - The proof is invalid
*
* @dev We provide the `_archive` even if it could be read from storage itself because it allow for
* better error messages. Without passing it, we would just have a proof verification failure.
*
* @dev Following the `BlockLog` struct assumption
*
* @param _header - The header of the block (should match the block in the pending chain)
* @param _archive - The archive root of the block (should match the block in the pending chain)
* @param _aggregationObject - The aggregation object for the proof
* @param _proof - The proof to verify
*/
function submitProof(
bytes calldata _header,
bytes32 _archive,
Expand All @@ -133,6 +196,23 @@ contract Rollup is Leonidas, IRollup {
) external override(IRollup) {
HeaderLib.Header memory header = HeaderLib.decode(_header);

if (header.globalVariables.blockNumber >= pendingBlockCount) {
revert Errors.Rollup__TryingToProveNonExistingBlock();
}

bytes32 expectedLastArchive = blocks[header.globalVariables.blockNumber - 1].archive;
bytes32 expectedArchive = blocks[header.globalVariables.blockNumber].archive;

// We do it this way to provide better error messages than passing along the storage values
// TODO(#4148) Proper genesis state. If the state is empty, we allow anything for now.
if (expectedLastArchive != bytes32(0) && header.lastArchive.root != expectedLastArchive) {
revert Errors.Rollup__InvalidArchive(expectedLastArchive, header.lastArchive.root);
}

if (_archive != expectedArchive) {
revert Errors.Rollup__InvalidProposedArchive(expectedArchive, _archive);
}

bytes32[] memory publicInputs =
new bytes32[](4 + Constants.HEADER_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH);
// the archive tree root
Expand Down Expand Up @@ -167,14 +247,41 @@ contract Rollup is Leonidas, IRollup {
revert Errors.Rollup__InvalidProof();
}

blocks[header.globalVariables.blockNumber].isProven = true;

_progressState();

emit L2ProofVerified(header.globalVariables.blockNumber, _proverId);
}

function _computePublicInputHash(bytes calldata _header, bytes32 _archive)
internal
pure
returns (bytes32)
{
return Hash.sha256ToField(bytes.concat(_header, _archive));
/**
* @notice Progresses the state of the proven chain as far as possible
*
* @dev Emits `ProgressedState` if the state is progressed
*
* @dev Will continue along the pending chain as long as the blocks are proven
* stops at the first unproven block.
*
* @dev Have a potentially unbounded gas usage. @todo Will need a bounded version, such that it cannot be
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're saying in the event the pending chain grows too far beyond the proven chain? Wouldn't a timeliness requirement effectively bound this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even with a timeliness, it "could" grow very large if blocks are coming in at max speed, and you could even have that it is past the timeliness but you just keep on extending and then at before someone is actually pruning it you would execute this. Sure, it is very unlikely, but you don't want to end up with a frozen system anyway.

* used as a DOS vector.
*/
function _progressState() internal {
if (pendingBlockCount == provenBlockCount) {
// We are already up to date
return;
}

uint256 cachedProvenBlockCount = provenBlockCount;

for (; cachedProvenBlockCount < pendingBlockCount; cachedProvenBlockCount++) {
if (!blocks[cachedProvenBlockCount].isProven) {
break;
}
}

if (cachedProvenBlockCount > provenBlockCount) {
provenBlockCount = cachedProvenBlockCount;
emit ProgressedState(provenBlockCount, pendingBlockCount);
}
}
}
1 change: 1 addition & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity >=0.8.18;
interface IRollup {
event L2BlockProcessed(uint256 indexed blockNumber);
event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId);
event ProgressedState(uint256 provenBlockCount, uint256 pendingBlockCount);

function process(bytes calldata _header, bytes32 _archive) external;

Expand Down
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 @@ -39,6 +39,9 @@ library Errors {

// Rollup
error Rollup__InvalidArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
error Rollup__InvalidProposedArchive(bytes32 expected, bytes32 actual); // 0x32532e73
error Rollup__InvalidBlockNumber(uint256 expected, uint256 actual); // 0xe5edf847
error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954
error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233
error Rollup__InvalidProof(); // 0xa5b2ba17
error Rollup__InvalidChainId(uint256 expected, uint256 actual); // 0x37b5bc12
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/src/core/libraries/HeaderLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ library HeaderLib {
view
{
if (block.chainid != _header.globalVariables.chainId) {
revert Errors.Rollup__InvalidChainId(_header.globalVariables.chainId, block.chainid);
revert Errors.Rollup__InvalidChainId(block.chainid, _header.globalVariables.chainId);
}

if (_header.globalVariables.version != _version) {
revert Errors.Rollup__InvalidVersion(_header.globalVariables.version, _version);
revert Errors.Rollup__InvalidVersion(_version, _header.globalVariables.version);
}

// block number already constrained by archive root check
Expand Down
Loading
Loading