Skip to content

Commit

Permalink
feat: Add log of blocks proposed and split pending/proven (#7635)
Browse files Browse the repository at this point in the history
Fixes #7615.

Adds a list of "BlockLog"s what we have within the 🐢 team talked about
just as log for more generic consensus notation, and then have two
values to address whereto the pending and the proven is accounted for.

Add extra requirements to the `submitProof` function to ensure that
proofs are actually linked to the blocks that are in the pending log. If
all constraints are passed, will mark the block as proven (but not
instantly move the tip forward).

Introduce a `progressState` function which we execute at every proof
submission that will try to progress the proven tip as far as possible
(as long as the next block is proven we continue).

This allow us to deal with non-sequential proving. 

- Also renames the fixtures to be block 1 and 2 instead of 0, 1 as it
makes it much easier to reason about when the genesis block is actually
already in the chain.
- Changed a few wrongly ordered error messages.
  • Loading branch information
LHerskind authored Jul 31, 2024
1 parent f35786a commit 5478747
Show file tree
Hide file tree
Showing 15 changed files with 453 additions and 246 deletions.
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
* 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

0 comments on commit 5478747

Please sign in to comment.