Skip to content

Commit

Permalink
feat: Handle epoch proofs on L1
Browse files Browse the repository at this point in the history
  • Loading branch information
spalladino committed Sep 23, 2024
1 parent e33afef commit d6b1a7c
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 7 deletions.
160 changes: 160 additions & 0 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,166 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
emit L2ProofVerified(header.globalVariables.blockNumber, _proverId);
}

// previous_archive: AppendOnlyTreeSnapshot,
// end_archive: AppendOnlyTreeSnapshot,
// previous_block_hash: Field,
// end_block_hash: Field,
// end_timestamp: u64,
// end_block_number: Field,
// out_hash: Field,
// fees: [FeeRecipient; 32],
// vk_tree_root: Field,
// prover_id: Field,

/**
* @notice Submit a proof for an epoch in the pending chain
*
* @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` and `_blockHash` 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.
*
* @param _epochSize - The size of the epoch (to be promoted to a constant)
* @param _aggregationObject - The aggregation object for the proof
* @param _proof - The proof to verify
*/
function submitEpochRootProof(
uint256 _epochSize,
bytes32[7] calldata _args,
bytes32[64] calldata _fees,
bytes calldata _aggregationObject,
bytes calldata _proof
) external override(IRollup) {
uint256 previousBlockNumber = tips.provenBlockNumber;
uint256 endBlockNumber = previousBlockNumber + _epochSize;

// _args are defined like this because Solidity complains with stack too deep otherwise
// 0 bytes32 _previousArchive,
// 1 bytes32 _endArchive,
// 2 bytes32 _previousBlockHash,
// 3 bytes32 _endBlockHash,
// 4 bytes32 _endTimestamp,
// 5 bytes32 _outHash,
// 6 bytes32 _proverId,

// Public inputs are not fully verified (TODO(#7373))

{
// We do it this way to provide better error messages than passing along the storage values
bytes32 expectedPreviousArchive = blocks[previousBlockNumber].archive;
if (expectedPreviousArchive != _args[0]) {
revert Errors.Rollup__InvalidArchive(expectedPreviousArchive, _args[0]);
}

bytes32 expectedEndArchive = blocks[endBlockNumber].archive;
if (expectedEndArchive != _args[1]) {
revert Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]);
}

bytes32 expectedPreviousBlockHash = blocks[previousBlockNumber].blockHash;
if (expectedPreviousBlockHash != _args[2]) {
revert Errors.Rollup__InvalidBlockHash(expectedPreviousBlockHash, _args[2]);
}

bytes32 expectedEndBlockHash = blocks[endBlockNumber].blockHash;
if (expectedEndBlockHash != _args[3]) {
revert Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]);
}
}

bytes32[] memory publicInputs = new bytes32[](
Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH
);

// From noir-projects/noir-protocol-circuits/crates/rollup-lib/src/root/root_rollup_public_inputs.nr: RootRollupPublicInputs.
// previous_archive: AppendOnlyTreeSnapshot,
// end_archive: AppendOnlyTreeSnapshot,
// previous_block_hash: Field,
// end_block_hash: Field,
// end_timestamp: u64,
// end_block_number: Field,
// out_hash: Field,
// fees: [FeeRecipient; 32],
// vk_tree_root: Field,
// prover_id: Field,

// previous_archive.root: the previous archive tree root
publicInputs[0] = _args[0];
// previous_archive.next_available_leaf_index: the previous archive next available index
// normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed)
// but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N
publicInputs[1] = bytes32(previousBlockNumber + 1);

// end_archive.root: the new archive tree root
publicInputs[2] = _args[1];
// end_archive.next_available_leaf_index: the new archive next available index
publicInputs[3] = bytes32(endBlockNumber + 1);

// previous_block_hash: the block hash just preceding this epoch
publicInputs[4] = _args[2];

// end_block_hash: the last block hash in the epoch
publicInputs[5] = _args[3];

// end_timestamp: the timestamp of the last block in the epoch
publicInputs[6] = _args[4];

// end_block_number: last block number in the epoch
publicInputs[7] = bytes32(endBlockNumber);

// out_hash: root of this epoch's l2 to l1 message tree
publicInputs[8] = _args[5];

// fees[9-40]: array of recipient-value pairs
for (uint256 i = 0; i < 64; i++) {
publicInputs[9 + i] = _fees[i];
}

// vk_tree_root
publicInputs[41] = vkTreeRoot;

// prover_id: id of current epoch's prover
publicInputs[42] = _args[6];

// the block proof is recursive, which means it comes with an aggregation object
// this snippet copies it into the public inputs needed for verification
// it also guards against empty _aggregationObject used with mocked proofs
uint256 aggregationLength = _aggregationObject.length / 32;
for (uint256 i = 0; i < Constants.AGGREGATION_OBJECT_LENGTH && i < aggregationLength; i++) {
bytes32 part;
assembly {
part := calldataload(add(_aggregationObject.offset, mul(i, 32)))
}
publicInputs[i + 43] = part;
}

if (!verifier.verify(_proof, publicInputs)) {
revert Errors.Rollup__InvalidProof();
}

tips.provenBlockNumber = endBlockNumber;

for (uint256 i = 0; i < 32; i++) {
address coinbase = address(uint160(uint256(publicInputs[9 + i * 2])));
uint256 fees = uint256(publicInputs[10 + i * 2]);

if (coinbase != address(0) && fees > 0) {
// @note This will currently fail if there are insufficient funds in the bridge
// which WILL happen for the old version after an upgrade where the bridge follow.
// Consider allowing a failure. See #7938.
FEE_JUICE_PORTAL.distributeFees(coinbase, fees);
}
}

emit L2ProofVerified(endBlockNumber, _args[6]);
}

/**
* @notice Check if msg.sender can propose at a given time
*
Expand Down
9 changes: 9 additions & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,16 @@ interface IRollup {
bytes calldata _proof
) external;

function submitEpochRootProof(
uint256 _epochSize,
bytes32[7] calldata _args,
bytes32[64] calldata _fees,
bytes calldata _aggregationObject,
bytes calldata _proof
) external;

function canProposeAtTime(uint256 _ts, bytes32 _archive) external view returns (uint256, uint256);

function validateHeader(
bytes calldata _header,
SignatureLib.Signature[] memory _signatures,
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ library Constants {
uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 12;
uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 29;
uint256 internal constant BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 91;
uint256 internal constant FEE_RECIPIENT_LENGTH = 2;
uint256 internal constant ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 75;
uint256 internal constant GET_NOTES_ORACLE_RETURN_LENGTH = 674;
uint256 internal constant NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP = 2048;
uint256 internal constant NULLIFIERS_NUM_BYTES_PER_BASE_ROLLUP = 2048;
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ library Errors {
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__InvalidBlockHash(bytes32 expected, bytes32 actual);
error Rollup__SlotValueTooLarge(uint256 slot); // 0x7234f4fe
error Rollup__SlotAlreadyInChain(uint256 lastSlot, uint256 proposedSlot); // 0x83510bd0
error Rollup__InvalidEpoch(uint256 expected, uint256 actual); // 0x3c6d65e6
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use dep::types::{
abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, global_variables::GlobalVariables},
constants::BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH, traits::{Empty, Serialize, Deserialize},
utils::reader::Reader, address::EthAddress
constants::{BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH, FEE_RECIPIENT_LENGTH},
traits::{Empty, Serialize, Deserialize}, utils::reader::Reader, address::EthAddress
};

struct FeeRecipient {
Expand All @@ -16,14 +16,14 @@ impl Empty for FeeRecipient {
}
}

impl Serialize<2> for FeeRecipient {
fn serialize(self) -> [Field; 2] {
impl Serialize<FEE_RECIPIENT_LENGTH> for FeeRecipient {
fn serialize(self) -> [Field; FEE_RECIPIENT_LENGTH] {
[self.recipient.to_field(), self.value]
}
}

impl Deserialize<2> for FeeRecipient {
fn deserialize(values: [Field; 2]) -> Self {
impl Deserialize<FEE_RECIPIENT_LENGTH> for FeeRecipient {
fn deserialize(values: [Field; FEE_RECIPIENT_LENGTH]) -> Self {
Self { recipient: EthAddress::from_field(values[0]), value: values[1] }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use types::{
merkle_tree::{append_only_tree, calculate_empty_tree_root}, state_reference::StateReference,
traits::Empty
};
use types::debug_log::debug_log_format;

global ALLOWED_PREVIOUS_CIRCUITS = [
BASE_ROLLUP_INDEX,
Expand All @@ -35,6 +36,7 @@ struct BlockRootRollupInputs {
start_l1_to_l2_message_tree_snapshot: AppendOnlyTreeSnapshot,

// inputs required to add the block hash
// TODO: Remove in favor of left.constants.last_archive
start_archive_snapshot: AppendOnlyTreeSnapshot,
new_archive_sibling_path: [Field; ARCHIVE_HEIGHT],
// Added previous_block_hash to be passed through to the final root, where it will be either:
Expand Down Expand Up @@ -93,6 +95,24 @@ impl BlockRootRollupInputs {

let total_fees = components::accumulate_fees(left, right);

// unsafe {
// debug_log_format("Assembling header in block root rollup", []);
// debug_log_format(
// "header.last_archive={}",
// left.constants.last_archive.serialize()
// );
// debug_log_format(
// "header.content_commitment={}",
// content_commitment.serialize()
// );
// debug_log_format("header.state={}", state.serialize());
// debug_log_format(
// "header.global_variables={}",
// left.constants.global_variables.serialize()
// );
// debug_log_format("header.total_fees={0}", [total_fees]);
// }

let header = Header {
last_archive: left.constants.last_archive,
content_commitment,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use dep::types::abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot;
use crate::abis::block_root_or_block_merge_public_inputs::FeeRecipient;
// TODO(#7346): Currently unused! Will be used when batch rollup circuits are integrated.

struct RootRollupPublicInputs {
// Snapshot of archive tree before/after this rollup has been processed
previous_archive: AppendOnlyTreeSnapshot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ global BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH: u32 = CONSTANT_ROLLUP_DATA_LENGTH + P
// + 64 for 32 * FeeRecipient { recipient, value }, + 4 for previous_block_hash, end_block_hash, out_hash, vk_tree_root + 1 temporarily for prover_id
global BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH: u32 = 2 * APPEND_ONLY_TREE_SNAPSHOT_LENGTH + 2 * GLOBAL_VARIABLES_LENGTH + 69;

global FEE_RECIPIENT_LENGTH: u32 = 2;
global ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH: u32 = 2 * APPEND_ONLY_TREE_SNAPSHOT_LENGTH + 7 + 32 * FEE_RECIPIENT_LENGTH;

global GET_NOTES_ORACLE_RETURN_LENGTH: u32 = 674;
global NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP: u32 = 32 * MAX_NOTE_HASHES_PER_TX;
global NULLIFIERS_NUM_BYTES_PER_BASE_ROLLUP: u32 = 32 * MAX_NULLIFIERS_PER_TX;
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/circuits.js/src/constants.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ export const KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 663;
export const CONSTANT_ROLLUP_DATA_LENGTH = 12;
export const BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 29;
export const BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 91;
export const FEE_RECIPIENT_LENGTH = 2;
export const ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 75;
export const GET_NOTES_ORACLE_RETURN_LENGTH = 674;
export const NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP = 2048;
export const NULLIFIERS_NUM_BYTES_PER_BASE_ROLLUP = 2048;
Expand Down

0 comments on commit d6b1a7c

Please sign in to comment.