Skip to content

Commit

Permalink
feat: Prepare protocol circuits for batch rollup (#7727)
Browse files Browse the repository at this point in the history
First run at creating new rollup circuits for batch block proving (see
[this PR](AztecProtocol/engineering-designs#7)
for details).

~Please note the e2e tests will fail miserably as the circuits are not
yet linked up to the sequencer/prover/L1! Pushing for visibility.~

EDIT: Added support for verifying block-root proofs on L1. Though we
don't currently have an L1 verifier (so tests would pass whatever public
inputs we had), the method now accepts the new inputs until we have
batch rollups integrated.

---

Changes complete:

- Rename `root` to `block_root` and change outputs
- Add `block_merge` circuit and associated types/structs
- Add new `root` circuit and associated types/structs (NB Github doesn't
realise that old root -> block_root because of this new circuit, so the
comparison is hard to read!)
- Added new tyes ^ to `circuits.js` and useful methods to `bb-prover`,
`circuit-types`, and `noir-protocol-circuits-types`
- Made minor changes to `prover-client` (`orchestrator.ts` and
`block-building-helpers.ts`) to use the new `block_root` public outputs
- `Rollup.sol` now verifies a `block_root` proof and stores `blockHash`

--
TODOs:

- When adding fees in a `block_merge` or `root`, merge fees with the
same recipient - Miranda
- ~Edit publisher and L1 to accept a `block_root` proof with new public
inputs (for testing, so e2es will pass)~ Complete!
- Teach the prover/sequencer to prove many blocks and submit a `root`
proof - Miranda + Phil's team?
- ~Make final L1 changes to verify batch proofs~ - Complete! Currently
not tested with real solidity verifier, but bb verifier passes
  • Loading branch information
MirandaWood authored Aug 23, 2024
1 parent 2ffcda3 commit a126e22
Show file tree
Hide file tree
Showing 73 changed files with 2,301 additions and 450 deletions.
99 changes: 76 additions & 23 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {Leonidas} from "./sequencer_selection/Leonidas.sol";
contract Rollup is Leonidas, IRollup, ITestRollup {
struct BlockLog {
bytes32 archive;
bytes32 blockHash;
uint128 slotNumber;
bool isProven;
}
Expand Down Expand Up @@ -88,7 +89,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
VERSION = 1;

// Genesis block
blocks[0] = BlockLog({archive: bytes32(0), slotNumber: 0, isProven: true});
blocks[0] =
BlockLog({archive: bytes32(0), blockHash: bytes32(0), slotNumber: 0, isProven: true});
pendingBlockCount = 1;
provenBlockCount = 1;
}
Expand Down Expand Up @@ -181,17 +183,19 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @param _header - The L2 block header
* @param _archive - A root of the archive tree after the L2 block is applied
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
* @param _signatures - Signatures from the validators
* @param _body - The body of the L2 block
*/
function publishAndProcess(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) external override(IRollup) {
AVAILABILITY_ORACLE.publish(_body);
process(_header, _archive, _signatures);
process(_header, _archive, _blockHash, _signatures);
}

/**
Expand All @@ -200,19 +204,24 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
* @dev `eth_log_handlers` rely on this function
* @param _header - The L2 block header
* @param _archive - A root of the archive tree after the L2 block is applied
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
* @param _body - The body of the L2 block
*/
function publishAndProcess(bytes calldata _header, bytes32 _archive, bytes calldata _body)
external
override(IRollup)
{
function publishAndProcess(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes calldata _body
) external override(IRollup) {
AVAILABILITY_ORACLE.publish(_body);
process(_header, _archive);
process(_header, _archive, _blockHash);
}

/**
* @notice Submit a proof for a block in the pending chain
*
* @dev TODO(#7346): Verify root proofs rather than block root when batch rollups are integrated.
*
* @dev Will call `_progressState` to update the proven chain. Notice this have potentially
* unbounded gas consumption.
*
Expand All @@ -231,10 +240,11 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @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 _proverId - The id of this block's prover
* @param _aggregationObject - The aggregation object for the proof
* @param _proof - The proof to verify
*/
function submitProof(
function submitBlockRootProof(
bytes calldata _header,
bytes32 _archive,
bytes32 _proverId,
Expand All @@ -259,23 +269,59 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
revert Errors.Rollup__InvalidProposedArchive(expectedArchive, _archive);
}

bytes32[] memory publicInputs =
new bytes32[](4 + Constants.HEADER_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH);
// the archive tree root
publicInputs[0] = _archive;
// TODO(#7346): Currently verifying block root proofs until batch rollups fully integrated.
// Hence the below pub inputs are BlockRootOrBlockMergePublicInputs, which are larger than
// the planned set (RootRollupPublicInputs), for the interim.
// Public inputs are not fully verified (TODO(#7373))

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

// From block_root_or_block_merge_public_inputs.nr: BlockRootOrBlockMergePublicInputs.
// previous_archive.root: the previous archive tree root
publicInputs[0] = expectedLastArchive;
// previous_archive.next_available_leaf_index: the previous archive next available index
publicInputs[1] = bytes32(header.globalVariables.blockNumber);

// new_archive.root: the new archive tree root
publicInputs[2] = expectedArchive;
// this is the _next_ available leaf in the archive tree
// 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(header.globalVariables.blockNumber + 1);

publicInputs[2] = vkTreeRoot;

bytes32[] memory headerFields = HeaderLib.toFields(header);
for (uint256 i = 0; i < headerFields.length; i++) {
publicInputs[i + 3] = headerFields[i];
// new_archive.next_available_leaf_index: the new archive next available index
publicInputs[3] = bytes32(header.globalVariables.blockNumber + 1);

// TODO(#7346): Currently previous block hash is unchecked, but will be checked in batch rollup (block merge -> root).
// block-building-helpers.ts is injecting as 0 for now, replicating here.
// previous_block_hash: the block hash just preceding this block (will eventually become the end_block_hash of the prev batch)
publicInputs[4] = bytes32(0);

// end_block_hash: the current block hash (will eventually become the hash of the final block proven in a batch)
publicInputs[5] = blocks[header.globalVariables.blockNumber].blockHash;

// For block root proof outputs, we have a block 'range' of just 1 block => start and end globals are the same
bytes32[] memory globalVariablesFields = HeaderLib.toFields(header.globalVariables);
for (uint256 i = 0; i < globalVariablesFields.length; i++) {
// start_global_variables
publicInputs[i + 6] = globalVariablesFields[i];
// end_global_variables
publicInputs[globalVariablesFields.length + i + 6] = globalVariablesFields[i];
}
// out_hash: root of this block's l2 to l1 message tree (will eventually be root of roots)
publicInputs[24] = header.contentCommitment.outHash;

// For block root proof outputs, we have a single recipient-value fee payment pair,
// but the struct contains space for the max (32) => we keep 31*2=62 fields blank to represent it.
// fees: array of recipient-value pairs, for a single block just one entry (will eventually be filled and paid out here)
publicInputs[25] = bytes32(uint256(uint160(header.globalVariables.coinbase)));
publicInputs[26] = bytes32(header.totalFees);
// publicInputs[27] -> publicInputs[88] left blank for empty fee array entries

publicInputs[headerFields.length + 3] = _proverId;
// vk_tree_root
publicInputs[89] = vkTreeRoot;
// prover_id: id of current block range's prover
publicInputs[90] = _proverId;

// the block proof is recursive, which means it comes with an aggregation object
// this snippet copies it into the public inputs needed for verification
Expand All @@ -286,7 +332,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
assembly {
part := calldataload(add(_aggregationObject.offset, mul(i, 32)))
}
publicInputs[i + 4 + Constants.HEADER_LENGTH] = part;
publicInputs[i + 91] = part;
}

if (!verifier.verify(_proof, publicInputs)) {
Expand Down Expand Up @@ -327,11 +373,13 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @param _header - The L2 block header
* @param _archive - A root of the archive tree after the L2 block is applied
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
* @param _signatures - Signatures from the validators
*/
function process(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
SignatureLib.Signature[] memory _signatures
) public override(IRollup) {
// Decode and validate header
Expand All @@ -343,6 +391,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
// the slot number to uint128
blocks[pendingBlockCount++] = BlockLog({
archive: _archive,
blockHash: _blockHash,
slotNumber: uint128(header.globalVariables.slotNumber),
isProven: false
});
Expand Down Expand Up @@ -385,10 +434,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
*
* @param _header - The L2 block header
* @param _archive - A root of the archive tree after the L2 block is applied
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
*/
function process(bytes calldata _header, bytes32 _archive) public override(IRollup) {
function process(bytes calldata _header, bytes32 _archive, bytes32 _blockHash)
public
override(IRollup)
{
SignatureLib.Signature[] memory emptySignatures = new SignatureLib.Signature[](0);
process(_header, _archive, emptySignatures);
process(_header, _archive, _blockHash, emptySignatures);
}

/**
Expand Down
26 changes: 22 additions & 4 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,44 @@ interface IRollup {
function publishAndProcess(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) external;
function publishAndProcess(bytes calldata _header, bytes32 _archive, bytes calldata _body)
external;
function process(bytes calldata _header, bytes32 _archive) external;
function publishAndProcess(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes calldata _body
) external;
function process(bytes calldata _header, bytes32 _archive, bytes32 _blockHash) external;
function process(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
SignatureLib.Signature[] memory _signatures
) external;

function submitProof(
function submitBlockRootProof(
bytes calldata _header,
bytes32 _archive,
bytes32 _proverId,
bytes calldata _aggregationObject,
bytes calldata _proof
) external;

// TODO(#7346): Integrate batch rollups
// function submitRootProof(
// bytes32 _previousArchive,
// bytes32 _archive,
// bytes32 outHash,
// address[32] calldata coinbases,
// uint256[32] calldata fees,
// bytes32 _proverId,
// bytes calldata _aggregationObject,
// bytes calldata _proof
// ) external;

function archive() external view returns (bytes32);
function isBlockProven(uint256 _blockNumber) external view returns (bool);
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
Expand Down
5 changes: 4 additions & 1 deletion l1-contracts/src/core/libraries/ConstantsGen.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ library Constants {
uint256 internal constant ROOT_PARITY_INDEX = 19;
uint256 internal constant BASE_ROLLUP_INDEX = 20;
uint256 internal constant MERGE_ROLLUP_INDEX = 21;
uint256 internal constant ROOT_ROLLUP_INDEX = 22;
uint256 internal constant BLOCK_ROOT_ROLLUP_INDEX = 22;
uint256 internal constant BLOCK_MERGE_ROLLUP_INDEX = 23;
uint256 internal constant ROOT_ROLLUP_INDEX = 24;
uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4;
uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 16;
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16;
Expand Down Expand Up @@ -197,6 +199,7 @@ library Constants {
uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 663;
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 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
29 changes: 29 additions & 0 deletions l1-contracts/src/core/libraries/HeaderLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,33 @@ library HeaderLib {

return fields;
}

// TODO(#7346): Currently using the below to verify block root proofs until batch rollups fully integrated.
// Once integrated, remove the below fn (not used anywhere else).
function toFields(GlobalVariables memory _globalVariables)
internal
pure
returns (bytes32[] memory)
{
bytes32[] memory fields = new bytes32[](Constants.GLOBAL_VARIABLES_LENGTH);

fields[0] = bytes32(_globalVariables.chainId);
fields[1] = bytes32(_globalVariables.version);
fields[2] = bytes32(_globalVariables.blockNumber);
fields[3] = bytes32(_globalVariables.slotNumber);
fields[4] = bytes32(_globalVariables.timestamp);
fields[5] = bytes32(uint256(uint160(_globalVariables.coinbase)));
fields[6] = bytes32(_globalVariables.feeRecipient);
fields[7] = bytes32(_globalVariables.gasFees.feePerDaGas);
fields[8] = bytes32(_globalVariables.gasFees.feePerL2Gas);

// fail if the header structure has changed without updating this function
if (fields.length != Constants.GLOBAL_VARIABLES_LENGTH) {
// TODO(Miranda): Temporarily using this method and below error while block-root proofs are verified
// When we verify root proofs, this method can be removed => no need for separate named error
revert Errors.HeaderLib__InvalidHeaderSize(Constants.HEADER_LENGTH, fields.length);
}

return fields;
}
}
Loading

0 comments on commit a126e22

Please sign in to comment.