Skip to content

Commit 4650224

Browse files
committed
Aggregate out hashes and set on L1 per epoch.
1 parent 9dd6e46 commit 4650224

File tree

115 files changed

+4282
-2898
lines changed

Some content is hidden

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

115 files changed

+4282
-2898
lines changed

docs/docs/developers/docs/aztec-nr/framework-description/ethereum-aztec-messaging/outbox.md

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ The `Outbox` is a contract deployed on L1 that handles message passing from the
1010

1111
## `insert()`
1212

13-
Inserts the root of a merkle tree containing all of the L2 to L1 messages in a checkpoint specified by checkpointNumber.
13+
Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch specified by `epochNumber`.
1414

1515
#include_code outbox_insert l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol solidity
1616

17-
| Name | Type | Description |
18-
| ------------------- | --------- | ---------------------------------------------------------------------- |
19-
| `_checkpointNumber` | `uint256` | The checkpoint Number in which the L2 to L1 messages reside |
20-
| `_root` | `bytes32` | The merkle root of the tree where all the L2 to L1 messages are leaves |
21-
| `_minHeight` | `uint256` | The minimum height of the merkle tree that the root corresponds to |
17+
| Name | Type | Description |
18+
| -------------- | --------- | ---------------------------------------------------------------------- |
19+
| `_epochNumber` | `uint256` | The epoch Number in which the L2 to L1 messages reside |
20+
| `_root` | `bytes32` | The merkle root of the tree where all the L2 to L1 messages are leaves |
21+
| `_minHeight` | `uint256` | The minimum height of the merkle tree that the root corresponds to |
2222

2323
#### Edge cases
2424

2525
- Will revert with `Outbox__Unauthorized()` if `msg.sender != ROLLUP_CONTRACT`.
26-
- Will revert with `Errors.Outbox__RootAlreadySetAtCheckpoint(uint256 checkpointNumber)` if the root for the specific checkpoint has already been set.
26+
- Will revert with `Errors.Outbox__RootAlreadySetAtEpoch(uint256 epochNumber)` if the root for the specific epoch has already been set.
2727
- Will revert with `Errors.Outbox__InsertingInvalidRoot()` if the rollup is trying to insert bytes32(0) as the root.
2828

2929
## `consume()`
@@ -32,32 +32,34 @@ Allows a recipient to consume a message from the `Outbox`.
3232

3333
#include_code outbox_consume l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol solidity
3434

35-
| Name | Type | Description |
36-
| ------------------- | ----------- | -------------------------------------------------------------------------------------------- |
37-
| `_message` | `L2ToL1Msg` | The L2 to L1 message we want to consume |
38-
| `_checkpointNumber` | `uint256` | The checkpoint number specifying the checkpoint that contains the message we want to consume |
39-
| `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located |
40-
| `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message, the \_path length directly depends |
35+
| Name | Type | Description |
36+
| -------------- | ----------- | ------------------------------------------------------------------------------------------- |
37+
| `_message` | `L2ToL1Msg` | The L2 to L1 message we want to consume |
38+
| `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the message we want to consume |
39+
| `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located |
40+
| `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message, the \_path length directly depends |
41+
42+
For detailed information about the epoch message tree structure and leaf ID computation, see the [stdlib helpers](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/stdlib/src/messaging/l2_to_l1_membership.ts).
4143

4244
#### Edge cases
4345

4446
- Will revert with `Outbox__InvalidRecipient(address expected, address actual);` if `msg.sender != _message.recipient.actor`.
4547
- Will revert with `Outbox__InvalidChainId()` if `block.chainid != _message.recipient.chainId`.
46-
- Will revert with `Outbox__NothingToConsumeAtCheckpoint(uint256 checkpointNumber)` if the root for the checkpoint has not been set yet.
47-
- Will revert with `Outbox__AlreadyNullified(uint256 checkpointNumber, uint256 leafIndex)` if the message at leafIndex for the checkpoint has already been consumed.
48+
- Will revert with `Outbox__NothingToConsumeAtEpoch(uint256 epochNumber)` if the root for the epoch has not been set yet.
49+
- Will revert with `Outbox__AlreadyNullified(uint256 epochNumber, uint256 leafIndex)` if the message at leafIndex for the epoch has already been consumed.
4850
- Will revert with `Outbox__InvalidPathLength(uint256 expected, uint256 actual)` if the supplied height is less than the existing minimum height of the L2 to L1 message tree, or the supplied height is greater than the maximum (minimum height + log2(maximum messages)).
4951
- Will revert with `MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex)` if unable to verify the message existence in the tree. It returns the message as a leaf, as well as the index of the leaf to expose more info about the error.
5052

51-
## `hasMessageBeenConsumedAtCheckpointAndIndex()`
53+
## `hasMessageBeenConsumedAtEpochAndIndex()`
5254

53-
Checks to see if an index of the L2 to L1 message tree for a specific checkpoint has been consumed.
55+
Checks to see if an index of the L2 to L1 message tree for a specific epoch has been consumed.
5456

55-
#include_code outbox_has_message_been_consumed_at_checkpoint_and_index l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol solidity
57+
#include_code outbox_has_message_been_consumed_at_epoch_and_index l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol solidity
5658

57-
| Name | Type | Description |
58-
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
59-
| `_checkpointNumber` | `uint256` | The checkpoint number specifying the checkpoint that contains the index of the message we want to check |
60-
| `_leafIndex` | `uint256` | The index of the message inside the merkle tree |
59+
| Name | Type | Description |
60+
| -------------- | --------- | --------------------------------------------------------------------------------------------- |
61+
| `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the index of the message we want to check |
62+
| `_leafIndex` | `uint256` | The index of the message inside the merkle tree |
6163

6264
#### Edge cases
6365

docs/docs/developers/docs/resources/migration_notes.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,35 @@ Additionally, any function or struct that previously referenced an L2 block numb
199199

200200
Note: current node softwares still produce exactly one L2 block per checkpoint, so for now checkpoint numbers and L2 block numbers remain equal. This may change once multi-block checkpoints are enabled.
201201

202+
### [L1 Contracts] L2-to-L1 messages are now grouped by epoch.
203+
204+
L2-to-L1 messages are now aggregated and organized per epoch rather than per block. This change affects how you compute membership witnesses for consuming messages on L1. You now need to know the epoch number in which the message was emitted to retrieve and consume the message.
205+
206+
**Note**: This is only an API change. The protocol behavior remains the same - messages can still only be consumed once an epoch is proven as before.
207+
208+
#### What changed
209+
210+
Previously, you might have computed the membership witness without explicitly needing the epoch:
211+
212+
```typescript
213+
const witness = await computeL2ToL1MembershipWitness(
214+
node,
215+
l2TxReceipt.blockNumber,
216+
l2ToL1Message
217+
);
218+
```
219+
220+
Now, you should provide the epoch number:
221+
222+
```typescript
223+
const epoch = await rollup.getEpochNumberForCheckpoint(l2TxReceipt.blockNumber);
224+
const witness = await computeL2ToL1MembershipWitness(
225+
node,
226+
epoch,
227+
l2ToL1Message
228+
);
229+
```
230+
202231
### [Aztec.js] Wallet interface changes
203232

204233
#### `simulateTx` is now batchable

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol";
2626
struct PublicInputArgs {
2727
bytes32 previousArchive;
2828
bytes32 endArchive;
29+
bytes32 outHash;
2930
address proverId;
3031
}
3132

l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pragma solidity >=0.8.27;
44

55
import {DataStructures} from "../../libraries/DataStructures.sol";
6+
import {Epoch} from "../../libraries/TimeLib.sol";
67

78
/**
89
* @title IOutbox
@@ -11,21 +12,18 @@ import {DataStructures} from "../../libraries/DataStructures.sol";
1112
* and will be consumed by the portal contracts.
1213
*/
1314
interface IOutbox {
14-
event RootAdded(uint256 indexed checkpointNumber, bytes32 indexed root);
15-
event MessageConsumed(
16-
uint256 indexed checkpointNumber, bytes32 indexed root, bytes32 indexed messageHash, uint256 leafId
17-
);
15+
event RootAdded(Epoch indexed epoch, bytes32 indexed root);
16+
event MessageConsumed(Epoch indexed epoch, bytes32 indexed root, bytes32 indexed messageHash, uint256 leafId);
1817

1918
// docs:start:outbox_insert
2019
/**
21-
* @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in
22-
* a checkpoint specified by _checkpointNumber.
20+
* @notice Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch specified by _epoch.
2321
* @dev Only callable by the rollup contract
2422
* @dev Emits `RootAdded` upon inserting the root successfully
25-
* @param _checkpointNumber - The checkpoint number in which the L2 to L1 messages reside
23+
* @param _epoch - The epoch in which the L2 to L1 messages reside
2624
* @param _root - The merkle root of the tree where all the L2 to L1 messages are leaves
2725
*/
28-
function insert(uint256 _checkpointNumber, bytes32 _root) external;
26+
function insert(Epoch _epoch, bytes32 _root) external;
2927
// docs:end:outbox_insert
3028

3129
// docs:start:outbox_consume
@@ -34,39 +32,36 @@ interface IOutbox {
3432
* @dev Only useable by portals / recipients of messages
3533
* @dev Emits `MessageConsumed` when consuming messages
3634
* @param _message - The L2 to L1 message
37-
* @param _checkpointNumber - The checkpoint number specifying the checkpoint that contains the message we want to
38-
* consume
39-
* @param _leafIndex - The index inside the merkle tree where the message is located
40-
* @param _path - The sibling path used to prove inclusion of the message, the _path length directly depends
41-
* on the total amount of L2 to L1 messages in the checkpoint. i.e. the length of _path is equal to the depth of the
42-
* L1 to L2 message tree.
35+
* @param _epoch - The epoch that contains the message we want to consume
36+
* @param _leafIndex - The index at the level in the epoch message tree where the message is located
37+
* @param _path - The sibling path used to prove inclusion of the message, the _path length depends
38+
* on the location of the L2 to L1 message in the epoch message tree.
4339
*/
4440
function consume(
4541
DataStructures.L2ToL1Msg calldata _message,
46-
uint256 _checkpointNumber,
42+
Epoch _epoch,
4743
uint256 _leafIndex,
4844
bytes32[] calldata _path
4945
) external;
5046
// docs:end:outbox_consume
5147

52-
// docs:start:outbox_has_message_been_consumed_at_checkpoint_and_index
48+
// docs:start:outbox_has_message_been_consumed_at_epoch_and_index
5349
/**
54-
* @notice Checks to see if an L2 to L1 message in a specific checkpoint has been consumed
50+
* @notice Checks to see if an L2 to L1 message in a specific epoch has been consumed
5551
* @dev - This function does not throw. Out-of-bounds access is considered valid, but will always return false
56-
* @param _checkpointNumber - The checkpoint number specifying the checkpoint that contains the message we want to
57-
* check
52+
* @param _epoch - The epoch that contains the message we want to check
5853
* @param _leafId - The unique id of the message leaf
5954
*/
60-
function hasMessageBeenConsumedAtCheckpoint(uint256 _checkpointNumber, uint256 _leafId) external view returns (bool);
61-
// docs:end:outbox_has_message_been_consumed_at_checkpoint_and_index
55+
function hasMessageBeenConsumedAtEpoch(Epoch _epoch, uint256 _leafId) external view returns (bool);
56+
// docs:end:outbox_has_message_been_consumed_at_epoch_and_index
6257

6358
/**
64-
* @notice Fetch the root data for a given checkpoint number
65-
* Returns (0, 0) if the checkpoint is not proven
59+
* @notice Fetch the root data for a given epoch
60+
* Returns (0, 0) if the epoch is not proven
6661
*
67-
* @param _checkpointNumber - The checkpoint number to fetch the root data for
62+
* @param _epoch - The epoch to fetch the root data for
6863
*
6964
* @return bytes32 - The root of the merkle tree containing the L2 to L1 messages
7065
*/
71-
function getRootData(uint256 _checkpointNumber) external view returns (bytes32);
66+
function getRootData(Epoch _epoch) external view returns (bytes32);
7267
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ library Constants {
2323
14_269_942_583_723_164_841_365_114_274_712_143_548_835_546_030_057_296_325_580_016_468_921_911_294_613;
2424
uint256 internal constant FEE_JUICE_ADDRESS = 5;
2525
uint256 internal constant BLS12_POINT_COMPRESSED_BYTES = 48;
26-
uint256 internal constant ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 158;
26+
uint256 internal constant ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH = 159;
2727
uint256 internal constant NUM_MSGS_PER_BASE_PARITY = 256;
2828
uint256 internal constant NUM_BASE_PARITY_PER_ROOT_PARITY = 4;
2929
}

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,9 @@ library Errors {
4343
uint32 storedDeadline,
4444
uint32 deadlinePassed
4545
); // 0x5e789f34
46-
error Outbox__RootAlreadySetAtCheckpoint(uint256 checkpointNumber); // 0x6eb83cef
4746
error Outbox__InvalidRecipient(address expected, address actual); // 0x57aad581
48-
error Outbox__AlreadyNullified(uint256 checkpointNumber, uint256 leafIndex); // 0xfd71c2d4
49-
error Outbox__NothingToConsumeAtCheckpoint(uint256 checkpointNumber); // 0x0279277d
50-
error Outbox__CheckpointNotProven(uint256 checkpointNumber); // 0x104bcfc1
51-
error Outbox__CheckpointAlreadyProven(uint256 checkpointNumber);
47+
error Outbox__AlreadyNullified(Epoch epoch, uint256 leafIndex); // 0xfd71c2d4
48+
error Outbox__NothingToConsumeAtEpoch(Epoch epoch); // 0x5e3d32ce
5249
error Outbox__PathTooLong();
5350
error Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength);
5451

l1-contracts/src/core/libraries/rollup/EpochProofLib.sol

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {STFLib} from "@aztec/core/libraries/rollup/STFLib.sol";
1515
import {ValidatorSelectionLib} from "@aztec/core/libraries/rollup/ValidatorSelectionLib.sol";
1616
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
1717
import {CompressedSlot, CompressedTimeMath} from "@aztec/shared/libraries/CompressedTimeMath.sol";
18-
import {Math} from "@oz/utils/math/Math.sol";
1918
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
2019

2120
/**
@@ -116,7 +115,20 @@ library EpochProofLib {
116115
require(verifyEpochRootProof(_args), Errors.Rollup__InvalidProof());
117116

118117
RollupStore storage rollupStore = STFLib.getStorage();
119-
rollupStore.tips = rollupStore.tips.updateProven(Math.max(rollupStore.tips.getProven(), _args.end));
118+
119+
// Advance the proven block number and insert the out hash if the chain is extended.
120+
if (_args.end > rollupStore.tips.getProven()) {
121+
rollupStore.tips = rollupStore.tips.updateProven(_args.end);
122+
123+
// Handle L2->L1 message processing.
124+
// The circuit outputs a zero out hash if the epoch contains no messages. It is also impossible for a partial
125+
// epoch to produce a non-zero out hash, then later produce a zero out hash once more checkpoints are included.
126+
// Therefore, we can safely skip the insertion for a zero out hash here.
127+
if (_args.args.outHash != bytes32(0)) {
128+
// Insert L2->L1 messages root into outbox for consumption.
129+
rollupStore.config.outbox.insert(endEpoch, _args.args.outHash);
130+
}
131+
}
120132

121133
RewardLib.handleRewardsAndFees(_args, endEpoch);
122134

@@ -170,30 +182,33 @@ library EpochProofLib {
170182
// struct RootRollupPublicInputs {
171183
// previous_archive_root: Field,
172184
// end_archive_root: Field,
185+
// out_hash: Field,
173186
// checkpointHeaderHashes: [Field; Constants.AZTEC_MAX_EPOCH_DURATION],
174187
// fees: [FeeRecipient; Constants.AZTEC_MAX_EPOCH_DURATION],
175188
// chain_id: Field,
176189
// version: Field,
177190
// vk_tree_root: Field,
178191
// protocol_contracts_hash: Field,
179192
// prover_id: Field,
180-
// blob_public_inputs: FinalBlobAccumulatorPublicInputs,
193+
// blob_public_inputs: FinalBlobAccumulator,
181194
// }
182195
{
183196
// previous_archive.root: the previous archive tree root
184197
publicInputs[0] = _args.previousArchive;
185198

186199
// end_archive.root: the new archive tree root
187200
publicInputs[1] = _args.endArchive;
201+
202+
publicInputs[2] = _args.outHash;
188203
}
189204

190205
uint256 numCheckpoints = _end - _start + 1;
191206

192207
for (uint256 i = 0; i < numCheckpoints; i++) {
193-
publicInputs[2 + i] = STFLib.getHeaderHash(_start + i);
208+
publicInputs[3 + i] = STFLib.getHeaderHash(_start + i);
194209
}
195210

196-
uint256 offset = 2 + Constants.AZTEC_MAX_EPOCH_DURATION;
211+
uint256 offset = 3 + Constants.AZTEC_MAX_EPOCH_DURATION;
197212

198213
uint256 feesLength = Constants.AZTEC_MAX_EPOCH_DURATION * 2;
199214
// fees[2n to 2n + 1]: a fee element, which contains of a recipient and a value
@@ -248,7 +263,6 @@ library EpochProofLib {
248263
publicInputs[offset] = bytes32(uint256(uint248(bytes31((_blobPublicInputs[96:127])))));
249264
// c[1]
250265
publicInputs[offset + 1] = bytes32(uint256(uint136(bytes17((_blobPublicInputs[127:144])))));
251-
offset += 2;
252266

253267
return publicInputs;
254268
}

0 commit comments

Comments
 (0)