-
Notifications
You must be signed in to change notification settings - Fork 296
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: Prepare protocol circuits for batch rollup #7727
Changes from 12 commits
c49bd33
358c7b3
9b288e3
c740c10
8859550
c031a20
6b97c3a
56c01ac
df113ce
b064be0
951981f
d84984e
381da05
930ebea
82aa39a
abfca2b
b23e646
605e192
644173b
eb3e283
bb4815d
1f114aa
6d37433
117d9d3
ef2a883
3127243
3e2f55f
a657e05
bc17e45
99064a1
388bce3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,6 +166,8 @@ contract Rollup is Leonidas, IRollup { | |
/** | ||
* @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. | ||
* | ||
|
@@ -184,13 +186,19 @@ contract Rollup is Leonidas, IRollup { | |
* | ||
* @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 | ||
* _previousBlockHash - The poseidon hash of the previous block (should match the value in the previous archive tree) | ||
* @param _currentBlockHash - The poseidon hash of this block (should match the value in the new archive tree) | ||
* @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, | ||
// TODO(#7246): Prev block hash unchecked for single blocks, should be checked for batch rollups. See block-building-helpers.ts for where to inject. | ||
// bytes32 _previousBlockHash, | ||
bytes32 _currentBlockHash, | ||
bytes calldata _aggregationObject, | ||
bytes calldata _proof | ||
) external override(IRollup) { | ||
|
@@ -213,23 +221,66 @@ contract Rollup is Leonidas, IRollup { | |
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); | ||
|
||
// TODO(#7346): Move archive membership proof to contract? | ||
// verifyMembership(archivePath, _previousBlockHash, header.globalVariables.blockNumber - 1, expectedLastArchive) | ||
|
||
// end_block_hash: the current block hash (will eventually become the hash of the final block proven in a batch) | ||
publicInputs[5] = _currentBlockHash; | ||
|
||
// TODO(#7346): Move archive membership proof to contract? | ||
// Currently archive root is updated by adding the new block hash inside block-root circuit. | ||
// verifyMembership(archivePath, _currentBlockHash, header.globalVariables.blockNumber, expectedArchive) | ||
|
||
// 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]; | ||
Comment on lines
+306
to
+309
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd try to get this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, shouldn't it be 9? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It starts at 6 because:
Basically, we have one block rather than a range so the pair of global variables are the same. I'm just using one loop to append them both. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah got it, my bad! |
||
} | ||
// 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 | ||
|
@@ -240,7 +291,7 @@ contract Rollup is Leonidas, IRollup { | |
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)) { | ||
|
@@ -253,6 +304,162 @@ contract Rollup is Leonidas, IRollup { | |
|
||
emit L2ProofVerified(header.globalVariables.blockNumber, _proverId); | ||
} | ||
// TODO(#7346): Commented out for now as stack too deep (unused until batch rollups integrated anyway). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a first impl of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How long do you expect it to be like this? I don't really like having a big bunch of uncommented code as it often end up just being distracting when reading through the code or something that is broken when one finally tries to un-comment it because things have changes and it have been standing still 🤷 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree - not sure on when. We will use it when batch rollups are fully integrated (the main part of the work will be editing the sequencer/orchestrator code, which is under Phil's team) after provernet is up. The logic follows from the other verify function pretty clearly so can remove for now for cleanliness. |
||
// /** | ||
// * @notice Submit a proof for a range of blocks in the pending chain | ||
// * | ||
// * @dev TODO(#7346): Currently unused - integrate when batch rollups are integrated. | ||
// * | ||
// * @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 previous archive root does not match the archive root of the previous range's last block | ||
// * - The new archive root does not match the archive root of the proposed range's last block | ||
// * - The proof is invalid | ||
// * | ||
// * @dev We provide the `_archive` and `_previousArchive` 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 _previousArchive - The archive root of the last block in the previous proven range | ||
// * @param _archive - The archive root of the last block in the range | ||
// * @param _previousBlockHash - The poseidon hash of the last block in the previous proven range (should match the value in the previous archive tree) | ||
// * @param _currentBlockHash - The poseidon hash of the last block in this range (should match the value in the new archive tree) | ||
// * @param outHash - The root of roots of the blocks' l2 to l1 message tree | ||
// * @param coinbases - The recipients of the fees for each block in the range (max 32) | ||
// * @param fees - The fees to be paid for each block in the range (max 32) | ||
// * @param _proverId - The id of this block's prover | ||
// * @param _aggregationObject - The aggregation object for the proof | ||
// * @param _proof - The proof to verify | ||
// */ | ||
// function submitRootProof( | ||
// bytes32 _previousArchive, | ||
// bytes32 _archive, | ||
// bytes32 _previousBlockHash, | ||
// bytes32 _currentBlockHash, | ||
// bytes32 outHash, | ||
// address[32] calldata coinbases, | ||
// uint256[32] calldata fees, | ||
// bytes32 _proverId, | ||
// bytes calldata _aggregationObject, | ||
// bytes calldata _proof | ||
// ) external override(IRollup) { | ||
// // TODO(#7346): The below assumes that the range of blocks being proven is always the 'next' range, | ||
// // does not allow for any 'gaps'. Maybe we should allow gaps to avoid someone holding up the chain. | ||
// uint256 startBlockNumber = provenBlockCount + 1; | ||
// uint256 endBlockNumber = pendingBlockCount; | ||
|
||
// // TODO: For now, while this fn is unused, checking input prev and current archives against expected. | ||
// // It may be better to input block numbers and gather archives from there. | ||
// bytes32 expectedLastArchive = blocks[startBlockNumber - 1].archive; | ||
// bytes32 expectedArchive = blocks[endBlockNumber].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) && _previousArchive != expectedLastArchive) { | ||
// revert Errors.Rollup__InvalidArchive(expectedLastArchive, _previousArchive); | ||
// } | ||
|
||
// // TODO: Below assumes the end state after proving this range of blocks cannot be 0, correct? | ||
// if (expectedArchive == bytes32(0)) { | ||
// revert Errors.Rollup__TryingToProveNonExistingBlock(); | ||
// } | ||
|
||
// if (_archive != expectedArchive) { | ||
// revert Errors.Rollup__InvalidProposedArchive(expectedArchive, _archive); | ||
// } | ||
|
||
// // TODO(#7346): Add a constant with calculated len of RootRollupPublicInputs: | ||
// // Currently 64 for fees (32 * 2) + 4 for archives (2 * 2) + 6 for indiv. fields | ||
// // Public inputs are not fully verified (TODO(#7373)) | ||
|
||
// bytes32[] memory publicInputs = | ||
// new bytes32[](74 + Constants.AGGREGATION_OBJECT_LENGTH); | ||
|
||
// // From root_rollup_public_inputs.nr RootRollupPublicInputs. | ||
// // 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(startBlockNumber); | ||
|
||
// // end_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 | ||
// // end_archive.next_available_leaf_index: the new archive next available index | ||
// publicInputs[3] = bytes32(endBlockNumber + 1); | ||
|
||
// // previous_block_hash: the block hash of block number startBlockNumber - 1 | ||
// publicInputs[4] = _previousBlockHash; | ||
|
||
// // verifyMembership(archivePath, _previousBlockHash, startBlockNumber - 1, expectedLastArchive) | ||
|
||
// // end_timestamp: TODO: is this the correct timestamp for public inputs? | ||
// publicInputs[5] = bytes32(lastBlockTs); | ||
|
||
// // end_block_hash: the block hash of block number endBlockNumber | ||
// publicInputs[6] = _currentBlockHash; | ||
|
||
// // verifyMembership(archivePath, _currentBlockHash, endBlockNumber, expectedArchive) | ||
|
||
// // out_hash: the root of roots of each block's l2 to l1 message tree | ||
// publicInputs[7] = outHash; | ||
|
||
// // TODO(Miranda): | ||
// // Current outbox takes a single block's set of l2 to l1 messages where the outHash represents the root | ||
// // of a wonky tree, where each leaf is itself a small tree of each tx's l2 to l1 messages. | ||
// // For #7346 we need this outHash to represent multiple blocks' outHashes. | ||
// // OUTBOX.insert( | ||
// // endBlockNumber, outHash, l2ToL1TreeMinHeight | ||
// // ); | ||
|
||
// // fees: array of recipient-value pairs | ||
// for (uint256 i = 0; i < 32; i++) { | ||
// publicInputs[2*i + 8] = bytes32(uint256(uint160(coinbases[i]))); | ||
// publicInputs[2*i + 9] = bytes32(fees[i]); | ||
// // TODO(#7346): Move payout of fees here from process() | ||
// // if (coinbases[i] != address(0) && fees[i] > 0) { | ||
// // GAS_TOKEN.transfer(coinbases[i], fees[i]); | ||
// // } | ||
// } | ||
|
||
// // prover_id: id of current block range's prover | ||
// publicInputs[73] = _proverId; | ||
|
||
// for (uint256 i = 0; i < 74; i++) { | ||
// console.logBytes32(publicInputs[i]); | ||
// } | ||
|
||
// // 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 + 74] = part; | ||
// } | ||
|
||
// if (!verifier.verify(_proof, publicInputs)) { | ||
// revert Errors.Rollup__InvalidProof(); | ||
// } | ||
|
||
// for (uint256 i = startBlockNumber; i < endBlockNumber; i++) { | ||
// blocks[i].isProven = true; | ||
// } | ||
|
||
// _progressState(); | ||
|
||
// emit L2ProofVerified(endBlockNumber, _proverId); | ||
// } | ||
|
||
/** | ||
* @notice Progresses the state of the proven chain as far as possible | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pretty messy (sorry) for a few reasons:
previousBlockHash
for a block-root circuit (it's not used now, but required for batch rollups, but the prover doesn't 'know' yet about blocks that came before it AFAIK). For now, I've set it to 0 and it originates inblock-building-helpers
. When merge-root is used, it will check thatleft.end_block_hash == right.previous_block_hash
. The very firstprevious_block_hash
will be bubbled up to the final root where it will be checked on L1, but maybe the other checks (archive, block numbers, etc.) are sufficient?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are currently storing the
archive
in theBlockLog
to support non-sequential proving, and as we needed it as input to the proof validation anyway.If we are going the "TxObjects" direction (meeting Thursday) we won't have the archive at that point, and it will instead make sense for us to store the
BlockHash
as we need something to sign over for the committee anyway.With that change, you don't need the membership check here as it would simply be reading the values and checking directly against those instead.
Shortterm, I think a fine approach to get the logic down would be to extend
BlockLog
with the hash, and then you can simple read those or perform the check similarly to the archive checks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion, have added
blockHash
toBlockLog
!