Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 32 additions & 29 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract Rollup is IRollup {
uint256 public immutable VERSION;
AvailabilityOracle public immutable AVAILABILITY_ORACLE;

bytes32 public rollupStateHash;
bytes32 public archive; // Root of the archive tree
uint256 public lastBlockTs;
// Tracks the last time time was warped on L2 ("warp" is the testing cheatcode).
// See https://github.com/AztecProtocol/aztec-packages/issues/1614
Expand All @@ -44,23 +44,29 @@ contract Rollup is IRollup {
}

/**
* @notice Process an incoming L2Block and progress the state
* @param _proof - The proof of correct execution
* @param _l2Block - The L2Block data, formatted as outlined in `Decoder.sol`
* @notice Process an incoming L2 block and progress the state
* @param _header - The L2 block header.
* @param _archive - A snapshot (root and next available leaf index) of the archive tree after the L2 block is applied
* @param _body - The L2 block body.
* @param _proof - The proof of correct execution.
*/
function process(bytes memory _proof, bytes calldata _l2Block) external override(IRollup) {
_constrainGlobals(_l2Block);
function process(
bytes calldata _header,
bytes calldata _archive,
bytes calldata _body, // Note: this will be replaced with _txsHash once the separation is finished.
bytes memory _proof
) external override(IRollup) {
// TODO: @benejsan Should we represent this values from header as a nice struct?
HeaderDecoder.Header memory header = HeaderDecoder.decode(_header);

// Decode the header
(uint256 l2BlockNumber, bytes32 oldStateHash, bytes32 newStateHash) =
HeaderDecoder.decode(_l2Block[:HeaderDecoder.BLOCK_HEADER_SIZE]);
_validateHeader(header);

// Check if the data is available using availability oracle (change availability oracle if you want a different DA layer)
bytes32 txsHash;
{
// @todo @LHerskind Hack such that the node is unchanged for now.
// should be removed when we have a proper block publication.
txsHash = AVAILABILITY_ORACLE.publish(_l2Block[HeaderDecoder.BLOCK_HEADER_SIZE:]);
txsHash = AVAILABILITY_ORACLE.publish(_body);
}

if (!AVAILABILITY_ORACLE.isAvailable(txsHash)) {
Expand All @@ -70,10 +76,7 @@ contract Rollup is IRollup {

// Decode the cross-chain messages
(bytes32 inHash,, bytes32[] memory l1ToL2Msgs, bytes32[] memory l2ToL1Msgs) =
MessagesDecoder.decode(_l2Block[HeaderDecoder.BLOCK_HEADER_SIZE:]);

bytes32 publicInputHash =
_computePublicInputHash(_l2Block[:HeaderDecoder.BLOCK_HEADER_SIZE], txsHash, inHash);
MessagesDecoder.decode(_body);

// @todo @LHerskind Proper genesis state. If the state is empty, we allow anything for now.
// TODO(#3936): Temporarily disabling this because L2Block encoding has not yet been updated.
Expand All @@ -82,13 +85,14 @@ contract Rollup is IRollup {
// }

bytes32[] memory publicInputs = new bytes32[](1);
publicInputs[0] = publicInputHash;
publicInputs[0] = _computePublicInputHash(_header, txsHash, inHash);

if (!VERIFIER.verify(_proof, publicInputs)) {
revert Errors.Rollup__InvalidProof();
}

rollupStateHash = newStateHash;
// TODO: @benejsan Manually extracting the root here is ugly. TODO: Re-think how to represent archive snap.
archive = bytes32(_header[:0x20]);
lastBlockTs = block.timestamp;

// @todo (issue #605) handle fee collector
Expand All @@ -98,34 +102,33 @@ contract Rollup is IRollup {
IOutbox outbox = REGISTRY.getOutbox();
outbox.sendL1Messages(l2ToL1Msgs);

emit L2BlockProcessed(l2BlockNumber);
emit L2BlockProcessed(header.blockNumber);
}

function _constrainGlobals(bytes calldata _header) internal view {
uint256 chainId = uint256(bytes32(_header[:0x20]));
uint256 version = uint256(bytes32(_header[0x20:0x40]));
uint256 ts = uint256(bytes32(_header[0x60:0x80]));
// block number already constrained by start state hash

if (block.chainid != chainId) {
revert Errors.Rollup__InvalidChainId(chainId, block.chainid);
function _validateHeader(HeaderDecoder.Header memory header) internal view {
if (block.chainid != header.chainId) {
revert Errors.Rollup__InvalidChainId(header.chainId, block.chainid);
}

if (version != VERSION) {
revert Errors.Rollup__InvalidVersion(version, VERSION);
if (header.version != VERSION) {
revert Errors.Rollup__InvalidVersion(header.version, VERSION);
}

if (ts > block.timestamp) {
if (header.timestamp > block.timestamp) {
revert Errors.Rollup__TimestampInFuture();
}

// @todo @LHerskind consider if this is too strict
// This will make multiple l2 blocks in the same l1 block impractical.
// e.g., the first block will update timestamp which will make the second fail.
// Could possibly allow multiple blocks if in same l1 block
if (ts < lastBlockTs) {
if (header.timestamp < lastBlockTs) {
revert Errors.Rollup__TimestampTooOld();
}

if (archive != header.lastArchive) {
revert Errors.Rollup__InvalidArchive(archive, header.lastArchive);
}
}

function _computePublicInputHash(bytes calldata _header, bytes32 _txsHash, bytes32 _inHash)
Expand Down
9 changes: 7 additions & 2 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
pragma solidity >=0.8.18;

interface IRollup {
event L2BlockProcessed(uint256 indexed blockNum);
event L2BlockProcessed(uint256 indexed blockNumber);

function process(bytes memory _proof, bytes calldata _l2Block) external;
function process(
bytes calldata _header,
bytes calldata _archive,
bytes calldata _body,
bytes memory _proof
) external;
}
2 changes: 1 addition & 1 deletion l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ library Errors {
); // 0x5e789f34

// Rollup
error Rollup__InvalidStateHash(bytes32 expected, bytes32 actual); // 0xa3cfaab3
error Rollup__InvalidArchive(bytes32 expected, bytes32 actual); // 0xb682a40e
error Rollup__InvalidProof(); // 0xa5b2ba17
error Rollup__InvalidChainId(uint256 expected, uint256 actual); // 0x37b5bc12
error Rollup__InvalidVersion(uint256 expected, uint256 actual); // 0x9ef30794
Expand Down
90 changes: 48 additions & 42 deletions l1-contracts/src/core/libraries/decoders/HeaderDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,43 @@ import {Hash} from "../Hash.sol";
*
* | byte start | num bytes | name
* | --- | --- | ---
* | 0x0000 | 0x20 | chain-id
* | 0x0020 | 0x20 | version
* | 0x0040 | 0x20 | L2 block number
* | 0x0060 | 0x20 | L2 timestamp
* | 0x0080 | 0x20 | startNoteHashTreeSnapshot.root
* | 0x00a0 | 0x04 | startNoteHashTreeSnapshot.nextAvailableLeafIndex
* | 0x00a4 | 0x20 | startNullifierTreeSnapshot.root
* | 0x00c4 | 0x04 | startNullifierTreeSnapshot.nextAvailableLeafIndex
* | 0x00c8 | 0x20 | startContractTreeSnapshot.root
* | 0x00e8 | 0x04 | startContractTreeSnapshot.nextAvailableLeafIndex
* | 0x00ec | 0x20 | startPublicDataTreeSnapshot.root
* | 0x010c | 0x04 | startPublicDataTreeSnapshot.nextAvailableLeafIndex
* | 0x0110 | 0x20 | startL1ToL2MessageTreeSnapshot.root
* | 0x0130 | 0x04 | startL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex
* | 0x0134 | 0x20 | startArchiveSnapshot.root
* | 0x0154 | 0x04 | startArchiveSnapshot.nextAvailableLeafIndex
* | 0x0158 | 0x20 | endNoteHashTreeSnapshot.root
* | 0x0178 | 0x04 | endNoteHashTreeSnapshot.nextAvailableLeafIndex
* | 0x017c | 0x20 | endNullifierTreeSnapshot.root
* | 0x019c | 0x04 | endNullifierTreeSnapshot.nextAvailableLeafIndex
* | 0x01a0 | 0x20 | endContractTreeSnapshot.root
* | 0x01c0 | 0x04 | endContractTreeSnapshot.nextAvailableLeafIndex
* | 0x01c4 | 0x20 | endPublicDataTreeSnapshot.root
* | 0x01e4 | 0x04 | endPublicDataTreeSnapshot.nextAvailableLeafIndex
* | 0x01e8 | 0x20 | endL1ToL2MessageTreeSnapshot.root
* | 0x0208 | 0x04 | endL1ToL2MessageTreeSnapshot.nextAvailableLeafIndex
* | 0x020c | 0x20 | endArchiveSnapshot.root
* | 0x022c | 0x04 | endArchiveSnapshot.nextAvailableLeafIndex
* | | | Header {
* | | | GlobalVariables {
* | 0x0000 | 0x20 | chainId
* | 0x0020 | 0x20 | version
* | 0x0040 | 0x20 | blockNumber
* | 0x0060 | 0x20 | timestamp
* | | | }
* | | | StateReference {
* | 0x0080 | 0x20 | l1ToL2MessageTree.root
* | 0x00a0 | 0x04 | l1ToL2MessageTree.nextAvailableLeafIndex
* | | | PartialStateReference {
* | 0x00a4 | 0x20 | noteHashTree.root
* | 0x00c4 | 0x04 | noteHashTree.nextAvailableLeafIndex
* | 0x00c8 | 0x20 | nullifierTree.root
* | 0x00e8 | 0x04 | nullifierTree.nextAvailableLeafIndex
* | 0x00ec | 0x20 | contractTree.root
* | 0x010c | 0x04 | contractTree.nextAvailableLeafIndex
* | 0x0110 | 0x20 | publicDataTree.root
* | 0x0130 | 0x04 | publicDataTree.nextAvailableLeafIndex
* | | | }
* | | | }
* | 0x0134 | 0x20 | lastArchive.root
* | 0x0154 | 0x04 | lastArchive.nextAvailableLeafIndex
* | 0x0158 | 0x20 | bodyHash
* | | | }
* | --- | --- | ---
*/
library HeaderDecoder {
// TODO: This is only partial
struct Header {
uint256 chainId;
uint256 version;
uint256 blockNumber;
uint256 timestamp;
bytes32 lastArchive;
}

// DECODING OFFSET CONSTANTS
// Where the start of trees metadata begins in the block
uint256 private constant START_TREES_BLOCK_HEADER_OFFSET = 0x80;
Expand All @@ -68,21 +74,21 @@ library HeaderDecoder {

/**
* @notice Decodes the header
* @param _header - The L2 block calldata.
* @return l2BlockNumber - The L2 block number
* @return startStateHash - The start state hash
* @return endStateHash - The end state hash
* @param _header - The header calldata.
*/
function decode(bytes calldata _header)
internal
pure
returns (uint256 l2BlockNumber, bytes32 startStateHash, bytes32 endStateHash)
{
l2BlockNumber = uint256(bytes32(_header[0x40:0x60]));
// Note, for startStateHash to match the storage, the l2 block number must be new - 1.
// Only jumping 1 block at a time.
startStateHash = computeStateHash(l2BlockNumber - 1, START_TREES_BLOCK_HEADER_OFFSET, _header);
endStateHash = computeStateHash(l2BlockNumber, END_TREES_BLOCK_HEADER_OFFSET, _header);
function decode(bytes calldata _header) internal pure returns (Header memory) {
Header memory header;

header.chainId = uint256(bytes32(_header[:0x20]));
header.version = uint256(bytes32(_header[0x20:0x40]));
header.blockNumber = uint256(bytes32(_header[0x40:0x60]));
header.timestamp = uint256(bytes32(_header[0x60:0x80]));

// The rest is needed only by verifier and hence not decoded here.

header.lastArchive = bytes32(_header[0x134:0x154]);

return header;
}

/**
Expand Down
13 changes: 7 additions & 6 deletions l1-contracts/test/decoders/Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ contract DecoderBase is Test {
}

struct Data {
// TODO(benejsan): Use HeaderDecoder.Header here?
uint256 chainId;
uint256 version;
uint256 blockNumber;
bytes body;
bytes32 calldataHash;
bytes32 endStateHash;
bytes32 l1ToL2MessagesHash;
bytes32 publicInputsHash;
bytes32 startStateHash;
uint256 timestamp;
bytes32 lastArchive;
bytes32 archive;
bytes header;
bytes body;
}

function load(string memory name) public view returns (Full memory) {
Expand Down
38 changes: 19 additions & 19 deletions l1-contracts/test/decoders/Decoder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract HeaderDecoderHelper {
function decode(bytes calldata _header)
public
pure
returns (uint256 l2BlockNumber, bytes32 startStateHash, bytes32 endStateHash)
returns (HeaderDecoder.Header memory)
{
return HeaderDecoder.decode(_header);
}
Expand Down Expand Up @@ -78,29 +78,29 @@ contract DecoderTest is DecoderBase {
function _testDecodeBlock(string memory name) public virtual {
DecoderBase.Full memory data = load(name);

// Using the FULL decoder.
(
uint256 l2BlockNumber,
bytes32 startStateHash,
bytes32 endStateHash,
bytes32 publicInputsHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
) = helper.decode(data.block.body);
(bytes32 diffRoot, bytes32 l1ToL2MessagesHash) =
helper.computeDiffRootAndMessagesHash(data.block.body);
// // Using the FULL decoder.
// (
// uint256 l2BlockNumber,
// bytes32 startStateHash,
// bytes32 endStateHash,
// bytes32 publicInputsHash,
// bytes32[] memory l2ToL1Msgs,
// bytes32[] memory l1ToL2Msgs
// ) = helper.decode(data.block.body);
// (bytes32 diffRoot, bytes32 l1ToL2MessagesHash) =
// helper.computeDiffRootAndMessagesHash(data.block.body);

// Header
{
(uint256 headerL2BlockNumber, bytes32 headerStartStateHash, bytes32 headerEndStateHash) =
HeaderDecoder.Header memory header =
headerHelper.decode(data.block.body);

assertEq(l2BlockNumber, data.block.blockNumber, "Invalid block number");
assertEq(headerL2BlockNumber, data.block.blockNumber, "Invalid block number");
assertEq(startStateHash, data.block.startStateHash, "Invalid start state hash");
assertEq(headerStartStateHash, data.block.startStateHash, "Invalid start state hash");
assertEq(endStateHash, data.block.endStateHash, "Invalid end state hash");
assertEq(headerEndStateHash, data.block.endStateHash, "Invalid end state hash");
assertEq(header.chainId, data.block.chainId, "Invalid chain Id");
assertEq(header.version, data.block.version, "Invalid version");
assertEq(header.blockNumber, data.block.blockNumber, "Invalid block number");
assertEq(header.timestamp, data.block.timestamp, "Invalid timestamp");
assertEq(header.lastArchive, data.block.lastArchive, "Invalid last archive");
// assertEq(header.archive, data.block.archive, "Invalid archive");
}

// Messages
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ describe('Archiver', () => {
function makeL2BlockProcessedEvent(l1BlockNum: bigint, l2BlockNum: bigint) {
return {
blockNumber: l1BlockNum,
args: { blockNum: l2BlockNum },
args: { blockNumber: l2BlockNum },
transactionHash: `0x${l2BlockNum}`,
} as Log<bigint, number, undefined, true, typeof RollupAbi, 'L2BlockProcessed'>;
}
Expand Down Expand Up @@ -349,8 +349,10 @@ function makeL1ToL2MessageCancelledEvents(l1BlockNum: bigint, entryKeys: string[
* @returns A fake tx with calldata that corresponds to calling process in the Rollup contract.
*/
function makeRollupTx(l2Block: L2Block) {
const header = toHex(l2Block.header.toBuffer());
const archive = toHex(l2Block.archive.toBuffer());
const body = toHex(l2Block.bodyToBuffer());
const proof = `0x`;
const block = toHex(l2Block.toBufferWithLogs());
const input = encodeFunctionData({ abi: RollupAbi, functionName: 'process', args: [proof, block] });
const input = encodeFunctionData({ abi: RollupAbi, functionName: 'process', args: [header, archive, body, proof] });
return { input } as Transaction<bigint, number>;
}
Loading