Skip to content

Commit a126e22

Browse files
authored
feat: Prepare protocol circuits for batch rollup (#7727)
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
1 parent 2ffcda3 commit a126e22

File tree

73 files changed

+2301
-450
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2301
-450
lines changed

l1-contracts/src/core/Rollup.sol

+76-23
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {Leonidas} from "./sequencer_selection/Leonidas.sol";
3333
contract Rollup is Leonidas, IRollup, ITestRollup {
3434
struct BlockLog {
3535
bytes32 archive;
36+
bytes32 blockHash;
3637
uint128 slotNumber;
3738
bool isProven;
3839
}
@@ -88,7 +89,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
8889
VERSION = 1;
8990

9091
// Genesis block
91-
blocks[0] = BlockLog({archive: bytes32(0), slotNumber: 0, isProven: true});
92+
blocks[0] =
93+
BlockLog({archive: bytes32(0), blockHash: bytes32(0), slotNumber: 0, isProven: true});
9294
pendingBlockCount = 1;
9395
provenBlockCount = 1;
9496
}
@@ -181,17 +183,19 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
181183
*
182184
* @param _header - The L2 block header
183185
* @param _archive - A root of the archive tree after the L2 block is applied
186+
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
184187
* @param _signatures - Signatures from the validators
185188
* @param _body - The body of the L2 block
186189
*/
187190
function publishAndProcess(
188191
bytes calldata _header,
189192
bytes32 _archive,
193+
bytes32 _blockHash,
190194
SignatureLib.Signature[] memory _signatures,
191195
bytes calldata _body
192196
) external override(IRollup) {
193197
AVAILABILITY_ORACLE.publish(_body);
194-
process(_header, _archive, _signatures);
198+
process(_header, _archive, _blockHash, _signatures);
195199
}
196200

197201
/**
@@ -200,19 +204,24 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
200204
* @dev `eth_log_handlers` rely on this function
201205
* @param _header - The L2 block header
202206
* @param _archive - A root of the archive tree after the L2 block is applied
207+
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
203208
* @param _body - The body of the L2 block
204209
*/
205-
function publishAndProcess(bytes calldata _header, bytes32 _archive, bytes calldata _body)
206-
external
207-
override(IRollup)
208-
{
210+
function publishAndProcess(
211+
bytes calldata _header,
212+
bytes32 _archive,
213+
bytes32 _blockHash,
214+
bytes calldata _body
215+
) external override(IRollup) {
209216
AVAILABILITY_ORACLE.publish(_body);
210-
process(_header, _archive);
217+
process(_header, _archive, _blockHash);
211218
}
212219

213220
/**
214221
* @notice Submit a proof for a block in the pending chain
215222
*
223+
* @dev TODO(#7346): Verify root proofs rather than block root when batch rollups are integrated.
224+
*
216225
* @dev Will call `_progressState` to update the proven chain. Notice this have potentially
217226
* unbounded gas consumption.
218227
*
@@ -231,10 +240,11 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
231240
*
232241
* @param _header - The header of the block (should match the block in the pending chain)
233242
* @param _archive - The archive root of the block (should match the block in the pending chain)
243+
* @param _proverId - The id of this block's prover
234244
* @param _aggregationObject - The aggregation object for the proof
235245
* @param _proof - The proof to verify
236246
*/
237-
function submitProof(
247+
function submitBlockRootProof(
238248
bytes calldata _header,
239249
bytes32 _archive,
240250
bytes32 _proverId,
@@ -259,23 +269,59 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
259269
revert Errors.Rollup__InvalidProposedArchive(expectedArchive, _archive);
260270
}
261271

262-
bytes32[] memory publicInputs =
263-
new bytes32[](4 + Constants.HEADER_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH);
264-
// the archive tree root
265-
publicInputs[0] = _archive;
272+
// TODO(#7346): Currently verifying block root proofs until batch rollups fully integrated.
273+
// Hence the below pub inputs are BlockRootOrBlockMergePublicInputs, which are larger than
274+
// the planned set (RootRollupPublicInputs), for the interim.
275+
// Public inputs are not fully verified (TODO(#7373))
276+
277+
bytes32[] memory publicInputs = new bytes32[](
278+
Constants.BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH + Constants.AGGREGATION_OBJECT_LENGTH
279+
);
280+
281+
// From block_root_or_block_merge_public_inputs.nr: BlockRootOrBlockMergePublicInputs.
282+
// previous_archive.root: the previous archive tree root
283+
publicInputs[0] = expectedLastArchive;
284+
// previous_archive.next_available_leaf_index: the previous archive next available index
285+
publicInputs[1] = bytes32(header.globalVariables.blockNumber);
286+
287+
// new_archive.root: the new archive tree root
288+
publicInputs[2] = expectedArchive;
266289
// this is the _next_ available leaf in the archive tree
267290
// normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed)
268291
// but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N
269-
publicInputs[1] = bytes32(header.globalVariables.blockNumber + 1);
270-
271-
publicInputs[2] = vkTreeRoot;
272-
273-
bytes32[] memory headerFields = HeaderLib.toFields(header);
274-
for (uint256 i = 0; i < headerFields.length; i++) {
275-
publicInputs[i + 3] = headerFields[i];
292+
// new_archive.next_available_leaf_index: the new archive next available index
293+
publicInputs[3] = bytes32(header.globalVariables.blockNumber + 1);
294+
295+
// TODO(#7346): Currently previous block hash is unchecked, but will be checked in batch rollup (block merge -> root).
296+
// block-building-helpers.ts is injecting as 0 for now, replicating here.
297+
// previous_block_hash: the block hash just preceding this block (will eventually become the end_block_hash of the prev batch)
298+
publicInputs[4] = bytes32(0);
299+
300+
// end_block_hash: the current block hash (will eventually become the hash of the final block proven in a batch)
301+
publicInputs[5] = blocks[header.globalVariables.blockNumber].blockHash;
302+
303+
// For block root proof outputs, we have a block 'range' of just 1 block => start and end globals are the same
304+
bytes32[] memory globalVariablesFields = HeaderLib.toFields(header.globalVariables);
305+
for (uint256 i = 0; i < globalVariablesFields.length; i++) {
306+
// start_global_variables
307+
publicInputs[i + 6] = globalVariablesFields[i];
308+
// end_global_variables
309+
publicInputs[globalVariablesFields.length + i + 6] = globalVariablesFields[i];
276310
}
311+
// out_hash: root of this block's l2 to l1 message tree (will eventually be root of roots)
312+
publicInputs[24] = header.contentCommitment.outHash;
313+
314+
// For block root proof outputs, we have a single recipient-value fee payment pair,
315+
// but the struct contains space for the max (32) => we keep 31*2=62 fields blank to represent it.
316+
// fees: array of recipient-value pairs, for a single block just one entry (will eventually be filled and paid out here)
317+
publicInputs[25] = bytes32(uint256(uint160(header.globalVariables.coinbase)));
318+
publicInputs[26] = bytes32(header.totalFees);
319+
// publicInputs[27] -> publicInputs[88] left blank for empty fee array entries
277320

278-
publicInputs[headerFields.length + 3] = _proverId;
321+
// vk_tree_root
322+
publicInputs[89] = vkTreeRoot;
323+
// prover_id: id of current block range's prover
324+
publicInputs[90] = _proverId;
279325

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

292338
if (!verifier.verify(_proof, publicInputs)) {
@@ -327,11 +373,13 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
327373
*
328374
* @param _header - The L2 block header
329375
* @param _archive - A root of the archive tree after the L2 block is applied
376+
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
330377
* @param _signatures - Signatures from the validators
331378
*/
332379
function process(
333380
bytes calldata _header,
334381
bytes32 _archive,
382+
bytes32 _blockHash,
335383
SignatureLib.Signature[] memory _signatures
336384
) public override(IRollup) {
337385
// Decode and validate header
@@ -343,6 +391,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
343391
// the slot number to uint128
344392
blocks[pendingBlockCount++] = BlockLog({
345393
archive: _archive,
394+
blockHash: _blockHash,
346395
slotNumber: uint128(header.globalVariables.slotNumber),
347396
isProven: false
348397
});
@@ -385,10 +434,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
385434
*
386435
* @param _header - The L2 block header
387436
* @param _archive - A root of the archive tree after the L2 block is applied
437+
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
388438
*/
389-
function process(bytes calldata _header, bytes32 _archive) public override(IRollup) {
439+
function process(bytes calldata _header, bytes32 _archive, bytes32 _blockHash)
440+
public
441+
override(IRollup)
442+
{
390443
SignatureLib.Signature[] memory emptySignatures = new SignatureLib.Signature[](0);
391-
process(_header, _archive, emptySignatures);
444+
process(_header, _archive, _blockHash, emptySignatures);
392445
}
393446

394447
/**

l1-contracts/src/core/interfaces/IRollup.sol

+22-4
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,44 @@ interface IRollup {
2929
function publishAndProcess(
3030
bytes calldata _header,
3131
bytes32 _archive,
32+
bytes32 _blockHash,
3233
SignatureLib.Signature[] memory _signatures,
3334
bytes calldata _body
3435
) external;
35-
function publishAndProcess(bytes calldata _header, bytes32 _archive, bytes calldata _body)
36-
external;
37-
function process(bytes calldata _header, bytes32 _archive) external;
36+
function publishAndProcess(
37+
bytes calldata _header,
38+
bytes32 _archive,
39+
bytes32 _blockHash,
40+
bytes calldata _body
41+
) external;
42+
function process(bytes calldata _header, bytes32 _archive, bytes32 _blockHash) external;
3843
function process(
3944
bytes calldata _header,
4045
bytes32 _archive,
46+
bytes32 _blockHash,
4147
SignatureLib.Signature[] memory _signatures
4248
) external;
4349

44-
function submitProof(
50+
function submitBlockRootProof(
4551
bytes calldata _header,
4652
bytes32 _archive,
4753
bytes32 _proverId,
4854
bytes calldata _aggregationObject,
4955
bytes calldata _proof
5056
) external;
5157

58+
// TODO(#7346): Integrate batch rollups
59+
// function submitRootProof(
60+
// bytes32 _previousArchive,
61+
// bytes32 _archive,
62+
// bytes32 outHash,
63+
// address[32] calldata coinbases,
64+
// uint256[32] calldata fees,
65+
// bytes32 _proverId,
66+
// bytes calldata _aggregationObject,
67+
// bytes calldata _proof
68+
// ) external;
69+
5270
function archive() external view returns (bytes32);
5371
function isBlockProven(uint256 _blockNumber) external view returns (bool);
5472
function archiveAt(uint256 _blockNumber) external view returns (bytes32);

l1-contracts/src/core/libraries/ConstantsGen.sol

+4-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ library Constants {
9191
uint256 internal constant ROOT_PARITY_INDEX = 19;
9292
uint256 internal constant BASE_ROLLUP_INDEX = 20;
9393
uint256 internal constant MERGE_ROLLUP_INDEX = 21;
94-
uint256 internal constant ROOT_ROLLUP_INDEX = 22;
94+
uint256 internal constant BLOCK_ROOT_ROLLUP_INDEX = 22;
95+
uint256 internal constant BLOCK_MERGE_ROLLUP_INDEX = 23;
96+
uint256 internal constant ROOT_ROLLUP_INDEX = 24;
9597
uint256 internal constant FUNCTION_SELECTOR_NUM_BYTES = 4;
9698
uint256 internal constant ARGS_HASH_CHUNK_LENGTH = 16;
9799
uint256 internal constant ARGS_HASH_CHUNK_COUNT = 16;
@@ -197,6 +199,7 @@ library Constants {
197199
uint256 internal constant KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH = 663;
198200
uint256 internal constant CONSTANT_ROLLUP_DATA_LENGTH = 12;
199201
uint256 internal constant BASE_OR_MERGE_PUBLIC_INPUTS_LENGTH = 29;
202+
uint256 internal constant BLOCK_ROOT_OR_BLOCK_MERGE_PUBLIC_INPUTS_LENGTH = 91;
200203
uint256 internal constant GET_NOTES_ORACLE_RETURN_LENGTH = 674;
201204
uint256 internal constant NOTE_HASHES_NUM_BYTES_PER_BASE_ROLLUP = 2048;
202205
uint256 internal constant NULLIFIERS_NUM_BYTES_PER_BASE_ROLLUP = 2048;

l1-contracts/src/core/libraries/HeaderLib.sol

+29
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,33 @@ library HeaderLib {
203203

204204
return fields;
205205
}
206+
207+
// TODO(#7346): Currently using the below to verify block root proofs until batch rollups fully integrated.
208+
// Once integrated, remove the below fn (not used anywhere else).
209+
function toFields(GlobalVariables memory _globalVariables)
210+
internal
211+
pure
212+
returns (bytes32[] memory)
213+
{
214+
bytes32[] memory fields = new bytes32[](Constants.GLOBAL_VARIABLES_LENGTH);
215+
216+
fields[0] = bytes32(_globalVariables.chainId);
217+
fields[1] = bytes32(_globalVariables.version);
218+
fields[2] = bytes32(_globalVariables.blockNumber);
219+
fields[3] = bytes32(_globalVariables.slotNumber);
220+
fields[4] = bytes32(_globalVariables.timestamp);
221+
fields[5] = bytes32(uint256(uint160(_globalVariables.coinbase)));
222+
fields[6] = bytes32(_globalVariables.feeRecipient);
223+
fields[7] = bytes32(_globalVariables.gasFees.feePerDaGas);
224+
fields[8] = bytes32(_globalVariables.gasFees.feePerL2Gas);
225+
226+
// fail if the header structure has changed without updating this function
227+
if (fields.length != Constants.GLOBAL_VARIABLES_LENGTH) {
228+
// TODO(Miranda): Temporarily using this method and below error while block-root proofs are verified
229+
// When we verify root proofs, this method can be removed => no need for separate named error
230+
revert Errors.HeaderLib__InvalidHeaderSize(Constants.HEADER_LENGTH, fields.length);
231+
}
232+
233+
return fields;
234+
}
206235
}

0 commit comments

Comments
 (0)