Skip to content

Commit d7610c4

Browse files
committed
Aggregate out hashes and set on L1 per epoch.
1 parent 6e0ff95 commit d7610c4

File tree

116 files changed

+3815
-2664
lines changed

Some content is hidden

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

116 files changed

+3815
-2664
lines changed

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

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,80 +4,78 @@ description: Learn about the outbox mechanism in Aztec portals for sending messa
44
tags: [portals, contracts]
55
---
66

7-
The `Outbox` is a contract deployed on L1 that handles message passing from L2 to L1. Portal contracts call `consume()` to receive and process messages that were sent from L2 contracts. The Rollup contract inserts message roots via `insert()` when checkpoints are proven.
7+
The `Outbox` is a contract deployed on L1 that handles message passing from L2 to L1. Portal contracts call `consume()` to receive and process messages that were sent from L2 contracts. The Rollup contract inserts message roots via `insert()` when epochs are proven.
88

99
**Links**: [Interface](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol), [Implementation](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/l1-contracts/src/core/messagebridge/Outbox.sol).
1010

1111
## `insert()`
1212

13-
Inserts the root of a merkle tree containing all of the L2 to L1 messages in a checkpoint. This function is only callable by the Rollup contract.
13+
Inserts the root of a merkle tree containing all of the L2 to L1 messages in an epoch. This function is only callable by the Rollup contract.
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 |
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 |
2121

2222
### Edge cases
2323

2424
- Will revert with `Outbox__Unauthorized()` if `msg.sender != ROLLUP_CONTRACT`.
25-
- Will revert with `Outbox__CheckpointAlreadyProven(uint256 checkpointNumber)` if the checkpoint has already been proven.
2625

2726
## `consume()`
2827

2928
Allows a recipient to consume a message from the `Outbox`.
3029

3130
#include_code outbox_consume l1-contracts/src/core/interfaces/messagebridge/IOutbox.sol solidity
3231

33-
| Name | Type | Description |
34-
| ------------------- | ----------- | -------------------------------------------------------------------------------------------- |
35-
| `_message` | `L2ToL1Msg` | The L2 to L1 message to consume |
36-
| `_checkpointNumber` | `uint256` | The checkpoint number specifying the checkpoint that contains the message to consume |
37-
| `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located |
38-
| `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message |
32+
| Name | Type | Description |
33+
| -------------- | ----------- | -------------------------------------------------------------------------- |
34+
| `_message` | `L2ToL1Msg` | The L2 to L1 message to consume |
35+
| `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the message to consume |
36+
| `_leafIndex` | `uint256` | The index inside the merkle tree where the message is located |
37+
| `_path` | `bytes32[]` | The sibling path used to prove inclusion of the message |
3938

4039
### Edge cases
4140

4241
- Will revert with `Outbox__PathTooLong()` if the path length is >= 256.
4342
- Will revert with `Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength)` if the leaf index exceeds the tree capacity for the given path length.
44-
- Will revert with `Outbox__CheckpointNotProven(uint256 checkpointNumber)` if the checkpoint has not been proven yet.
4543
- Will revert with `Outbox__VersionMismatch(uint256 expected, uint256 actual)` if the message version does not match the Outbox version.
4644
- Will revert with `Outbox__InvalidRecipient(address expected, address actual)` if `msg.sender != _message.recipient.actor`.
4745
- Will revert with `Outbox__InvalidChainId()` if `block.chainid != _message.recipient.chainId`.
48-
- Will revert with `Outbox__NothingToConsumeAtCheckpoint(uint256 checkpointNumber)` if the root for the checkpoint has not been set.
49-
- Will revert with `Outbox__AlreadyNullified(uint256 checkpointNumber, uint256 leafIndex)` if the message has already been consumed.
46+
- Will revert with `Outbox__NothingToConsumeAtEpoch(uint256 epochNumber)` if the root for the epoch has not been set.
47+
- Will revert with `Outbox__AlreadyNullified(uint256 epochNumber, uint256 leafIndex)` if the message has already been consumed.
5048
- Will revert with `MerkleLib__InvalidIndexForPathLength()` if the leaf index has bits set beyond the tree height.
5149
- Will revert with `MerkleLib__InvalidRoot(bytes32 expected, bytes32 actual, bytes32 leaf, uint256 leafIndex)` if the merkle proof verification fails.
5250

53-
## `hasMessageBeenConsumedAtCheckpoint()`
51+
## `hasMessageBeenConsumedAtEpoch()`
5452

55-
Checks if an L2 to L1 message in a specific checkpoint has been consumed.
53+
Checks if an L2 to L1 message in a specific epoch has been consumed.
5654

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

59-
| Name | Type | Description |
60-
| ------------------- | --------- | ------------------------------------------------------------------------------------------------------- |
61-
| `_checkpointNumber` | `uint256` | The checkpoint number specifying the checkpoint that contains the message to check |
62-
| `_leafId` | `uint256` | The unique id of the message leaf |
57+
| Name | Type | Description |
58+
| -------------- | --------- | ------------------------------------------------------------------------ |
59+
| `_epochNumber` | `uint256` | The epoch number specifying the epoch that contains the message to check |
60+
| `_leafId` | `uint256` | The unique id of the message leaf |
6361

6462
### Edge cases
6563

6664
- This function does not throw. Out-of-bounds access is considered valid, but will always return false.
6765

6866
## `getRootData()`
6967

70-
Returns the merkle root for a given checkpoint number. Returns `bytes32(0)` if the checkpoint has not been proven.
68+
Returns the merkle root for a given epoch number. Returns `bytes32(0)` if the epoch has not been proven.
7169

7270
```solidity
73-
function getRootData(uint256 _checkpointNumber) external view returns (bytes32);
71+
function getRootData(uint256 _epochNumber) external view returns (bytes32);
7472
```
7573

76-
| Name | Type | Description |
77-
| ------------------- | --------- | ------------------------------------------------ |
78-
| `_checkpointNumber` | `uint256` | The checkpoint number to fetch the root data for |
74+
| Name | Type | Description |
75+
| -------------- | --------- | ------------------------------------------- |
76+
| `_epochNumber` | `uint256` | The epoch number to fetch the root data for |
7977

80-
**Returns**: The merkle root of the L2 to L1 message tree for the checkpoint, or `bytes32(0)` if not proven.
78+
**Returns**: The merkle root of the L2 to L1 message tree for the epoch, or `bytes32(0)` if not proven.
8179

8280
## Related pages
8381

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,37 @@ Additionally, any function or struct that previously referenced an L2 block numb
550550

551551
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.
552552

553+
### [L1 Contracts] L2-to-L1 messages are now grouped by epoch.
554+
555+
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.
556+
557+
**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.
558+
559+
#### What changed
560+
561+
Previously, you might have computed the membership witness without explicitly needing the epoch:
562+
563+
```typescript
564+
const witness = await computeL2ToL1MembershipWitness(
565+
node,
566+
l2TxReceipt.blockNumber,
567+
l2ToL1Message
568+
);
569+
```
570+
571+
Now, you should provide the epoch number:
572+
573+
```typescript
574+
const epoch = await rollup.getEpochNumberForCheckpoint(
575+
CheckpointNumber.fromBlockNumber(l2TxReceipt.blockNumber)
576+
);
577+
const witness = await computeL2ToL1MembershipWitness(
578+
node,
579+
epoch,
580+
l2ToL1Message
581+
);
582+
```
583+
553584
### [Aztec.js] Wallet interface changes
554585

555586
#### `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
@@ -27,6 +27,7 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol";
2727
struct PublicInputArgs {
2828
bytes32 previousArchive;
2929
bytes32 endArchive;
30+
bytes32 outHash;
3031
address proverId;
3132
}
3233

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
@@ -44,12 +44,9 @@ library Errors {
4444
uint32 storedDeadline,
4545
uint32 deadlinePassed
4646
); // 0x5e789f34
47-
error Outbox__RootAlreadySetAtCheckpoint(uint256 checkpointNumber); // 0x6eb83cef
4847
error Outbox__InvalidRecipient(address expected, address actual); // 0x57aad581
49-
error Outbox__AlreadyNullified(uint256 checkpointNumber, uint256 leafIndex); // 0xfd71c2d4
50-
error Outbox__NothingToConsumeAtCheckpoint(uint256 checkpointNumber); // 0x0279277d
51-
error Outbox__CheckpointNotProven(uint256 checkpointNumber); // 0x104bcfc1
52-
error Outbox__CheckpointAlreadyProven(uint256 checkpointNumber);
48+
error Outbox__AlreadyNullified(Epoch epoch, uint256 leafIndex); // 0xfd71c2d4
49+
error Outbox__NothingToConsumeAtEpoch(Epoch epoch); // 0x5e3d32ce
5350
error Outbox__PathTooLong();
5451
error Outbox__LeafIndexOutOfBounds(uint256 leafIndex, uint256 pathLength);
5552

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

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

2221
/**
@@ -117,7 +116,20 @@ library EpochProofLib {
117116
require(verifyEpochRootProof(_args), Errors.Rollup__InvalidProof());
118117

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

122134
RewardLib.handleRewardsAndFees(_args, endEpoch);
123135

@@ -171,30 +183,33 @@ library EpochProofLib {
171183
// struct RootRollupPublicInputs {
172184
// previous_archive_root: Field,
173185
// end_archive_root: Field,
186+
// out_hash: Field,
174187
// checkpointHeaderHashes: [Field; Constants.AZTEC_MAX_EPOCH_DURATION],
175188
// fees: [FeeRecipient; Constants.AZTEC_MAX_EPOCH_DURATION],
176189
// chain_id: Field,
177190
// version: Field,
178191
// vk_tree_root: Field,
179192
// protocol_contracts_hash: Field,
180193
// prover_id: Field,
181-
// blob_public_inputs: FinalBlobAccumulatorPublicInputs,
194+
// blob_public_inputs: FinalBlobAccumulator,
182195
// }
183196
{
184197
// previous_archive.root: the previous archive tree root
185198
publicInputs[0] = _args.previousArchive;
186199

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

191206
uint256 numCheckpoints = _end - _start + 1;
192207

193208
for (uint256 i = 0; i < numCheckpoints; i++) {
194-
publicInputs[2 + i] = STFLib.getHeaderHash(_start + i);
209+
publicInputs[3 + i] = STFLib.getHeaderHash(_start + i);
195210
}
196211

197-
uint256 offset = 2 + Constants.AZTEC_MAX_EPOCH_DURATION;
212+
uint256 offset = 3 + Constants.AZTEC_MAX_EPOCH_DURATION;
198213

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

254268
return publicInputs;
255269
}

0 commit comments

Comments
 (0)