Skip to content
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: Add data availability oracle #3897

Merged
merged 5 commits into from
Jan 9, 2024
Merged
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
55 changes: 42 additions & 13 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {IOutbox} from "./interfaces/messagebridge/IOutbox.sol";
import {IRegistry} from "./interfaces/messagebridge/IRegistry.sol";

// Libraries
import {Decoder} from "./libraries/Decoder.sol";
import {HeaderDecoder} from "./libraries/decoders/HeaderDecoder.sol";
import {MessagesDecoder} from "./libraries/decoders/MessagesDecoder.sol";
import {Hash} from "./libraries/Hash.sol";
import {Errors} from "./libraries/Errors.sol";

// Contracts
import {MockVerifier} from "../mock/MockVerifier.sol";
import {AvailabilityOracle} from "./availability_oracle/AvailabilityOracle.sol";

/**
* @title Rollup
Expand All @@ -25,6 +28,7 @@ contract Rollup is IRollup {
MockVerifier public immutable VERIFIER;
IRegistry public immutable REGISTRY;
uint256 public immutable VERSION;
AvailabilityOracle public immutable AVAILABILITY_ORACLE;

bytes32 public rollupStateHash;
uint256 public lastBlockTs;
Expand All @@ -34,6 +38,7 @@ contract Rollup is IRollup {

constructor(IRegistry _registry) {
VERIFIER = new MockVerifier();
AVAILABILITY_ORACLE = new AvailabilityOracle();
REGISTRY = _registry;
VERSION = 1;
}
Expand All @@ -45,14 +50,30 @@ contract Rollup is IRollup {
*/
function process(bytes memory _proof, bytes calldata _l2Block) external override(IRollup) {
_constrainGlobals(_l2Block);
(
uint256 l2BlockNumber,
bytes32 oldStateHash,
bytes32 newStateHash,
bytes32 publicInputHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
) = Decoder.decode(_l2Block);

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

// 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:]);
}

if (!AVAILABILITY_ORACLE.isAvailable(txsHash)) {
// @todo @LHerskind Impossible to hit with above hack.
revert Errors.Rollup__UnavailableTxs(txsHash);
}

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

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

// @todo @LHerskind Proper genesis state. If the state is empty, we allow anything for now.
if (rollupStateHash != bytes32(0) && rollupStateHash != oldStateHash) {
Expand All @@ -79,10 +100,10 @@ contract Rollup is IRollup {
emit L2BlockProcessed(l2BlockNumber);
}

function _constrainGlobals(bytes calldata _l2Block) internal view {
uint256 chainId = uint256(bytes32(_l2Block[:0x20]));
uint256 version = uint256(bytes32(_l2Block[0x20:0x40]));
uint256 ts = uint256(bytes32(_l2Block[0x60:0x80]));
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) {
Expand All @@ -105,4 +126,12 @@ contract Rollup is IRollup {
revert Errors.Rollup__TimestampTooOld();
}
}

function _computePublicInputHash(bytes calldata _header, bytes32 _txsHash, bytes32 _inHash)
internal
pure
returns (bytes32)
{
return Hash.sha256ToField(bytes.concat(_header, _txsHash, _inHash));
}
}
29 changes: 29 additions & 0 deletions l1-contracts/src/core/availability_oracle/AvailabilityOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.18;

// Interfaces
import {IAvailabilityOracle} from "./../interfaces/IAvailabilityOracle.sol";

// Libraries
import {TxsDecoder} from "./../libraries/decoders/TxsDecoder.sol";

/**
* @title AvailabilityOracle
* @author Aztec Labs
* @notice An availability oracle that uses L1 calldata for publication
*/
contract AvailabilityOracle is IAvailabilityOracle {
mapping(bytes32 txsHash => bool available) public override(IAvailabilityOracle) isAvailable;

/**
* @notice Publishes transactions and marks its commitment, the TxsHash, as available
* @param _body - The L1 calldata
* @return txsHash - The TxsHash
*/
function publish(bytes calldata _body) external override(IAvailabilityOracle) returns (bytes32) {
bytes32 _txsHash = TxsDecoder.decode(_body);
isAvailable[_txsHash] = true;
return _txsHash;
}
}
9 changes: 9 additions & 0 deletions l1-contracts/src/core/interfaces/IAvailabilityOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.18;

interface IAvailabilityOracle {
function publish(bytes calldata _body) external returns (bytes32);

function isAvailable(bytes32 _txsHash) external view returns (bool);
}
1 change: 1 addition & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ library Errors {
error Rollup__InvalidVersion(uint256 expected, uint256 actual); // 0x9ef30794
error Rollup__TimestampInFuture(); // 0xbc1ce916
error Rollup__TimestampTooOld(); // 0x72ed9c81
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3

// Registry
error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
pragma solidity >=0.8.18;

// Libraries
import {Constants} from "./ConstantsGen.sol";
import {Hash} from "./Hash.sol";
import {Constants} from "../ConstantsGen.sol";
import {Hash} from "../Hash.sol";

/**
* @title Decoder Library
Expand Down
107 changes: 107 additions & 0 deletions l1-contracts/src/core/libraries/decoders/HeaderDecoder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

// Libraries
import {Constants} from "../ConstantsGen.sol";
import {Hash} from "../Hash.sol";

/**
* @title Header Decoder Library
* @author Aztec Labs
* @notice Decoding a L2 header
* Concerned with readability and velocity of development not giving a damn about gas costs.
*
* -------------------
* You can use https://gist.github.com/LHerskind/724a7e362c97e8ac2902c6b961d36830 to generate the below outline.
* -------------------
* L2 Block Header specification
* -------------------
*
* | 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
* | --- | --- | ---
*/
library HeaderDecoder {
// DECODING OFFSET CONSTANTS
// Where the start of trees metadata begins in the block
uint256 private constant START_TREES_BLOCK_HEADER_OFFSET = 0x80;

// The size of the block header elements
uint256 private constant TREES_BLOCK_HEADER_SIZE = 0xd8;

// Where the end of trees metadata begins in the block
uint256 private constant END_TREES_BLOCK_HEADER_OFFSET =
START_TREES_BLOCK_HEADER_OFFSET + TREES_BLOCK_HEADER_SIZE;

// Where the metadata ends and the block data begins.
uint256 internal constant BLOCK_HEADER_SIZE =
START_TREES_BLOCK_HEADER_OFFSET + 2 * TREES_BLOCK_HEADER_SIZE;

/**
* @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
*/
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);
}

/**
* @notice Computes a state hash
* @param _l2BlockNumber - The L2 block number
* @param _offset - The offset into the data, 0x80 for start, 0x019c for end
* @param _header - The L2 block calldata.
* @return The state hash
* @dev The state hash is sha256 hash of block's header elements. For each block the header elements are
* the block number, snapshots of all the trees and the root of the public data tree. This function
* copies all of these to memory and then hashes them.
*/
function computeStateHash(uint256 _l2BlockNumber, uint256 _offset, bytes calldata _header)
internal
pure
returns (bytes32)
{
return sha256(
bytes.concat(bytes32(_l2BlockNumber), _header[_offset:_offset + TREES_BLOCK_HEADER_SIZE])
);
}
}
109 changes: 109 additions & 0 deletions l1-contracts/src/core/libraries/decoders/MessagesDecoder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.18;

// Libraries
import {Constants} from "../ConstantsGen.sol";
import {Hash} from "../Hash.sol";

/**
* @title Messages Decoder Library
* @author Aztec Labs
* @notice Decoding a L2 block body and returns cross-chain messages + (in/out)Hash.
* Concerned with readability and velocity of development not giving a damn about gas costs.
* @dev Assumes the input trees to be padded.
*
* -------------------
* You can use https://gist.github.com/LHerskind/724a7e362c97e8ac2902c6b961d36830 to generate the below outline.
* -------------------
* L2 Body Data specification
* -------------------
*
* | byte start | num bytes | name
* | --- | --- | ---
* | 0x00 | 0x04 | len(newCommitments) (denoted a)
* | 0x04 | a * 0x20 | newCommitments
* | 0x04 + a * 0x20 | 0x04 | len(newNullifiers) (denoted b)
* | 0x08 + a * 0x20 | b * 0x20 | newNullifiers
* | 0x08 + a * 0x20 + b * 0x20 | 0x04 | len(newPublicDataWrites) (denoted c)
* | 0x0c + a * 0x20 + b * 0x20 | c * 0x40 | newPublicDataWrites
* | 0x0c + a * 0x20 + b * 0x20 + c * 0x40 | 0x04 | len(newL2ToL1Msgs) (denoted d)
* | 0x10 + a * 0x20 + b * 0x20 + c * 0x40 | d * 0x20 | newL2ToL1Msgs
* | 0x10 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | 0x04 | len(contracts) (denoted e)
* | 0x14 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 | e * 0x20 | newContracts
* | 0x14 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x20 | e * 0x34 | newContractsData
* | 0x14 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | 0x04 | len(newL1ToL2Msgs) (denoted f)
* | 0x18 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 | f * 0x20 | newL1ToL2Msgs
* | 0x18 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | 0x04 | byteLen(newEncryptedLogs) (denoted g)
* | 0x1c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 | g | newEncryptedLogs
* | 0x1c + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | 0x04 | byteLen(newUnencryptedLogs) (denoted h)
* | 0x20 + a * 0x20 + b * 0x20 + c * 0x40 + d * 0x20 + e * 0x54 + f * 0x20 + g | h | newUnencryptedLogs
* | --- | --- | ---
*/
library MessagesDecoder {
/**
* @notice Computes consumables for the block
* @param _body - The L2 block calldata.
* @return l1ToL2MsgsHash - The hash of the L1 to L2 messages
* @return l2ToL1MsgsHash - The hash of the L1 to L2 messages
* @return l2ToL1Msgs - The L2 to L1 messages of the block
* @return l1ToL2Msgs - The L1 to L2 messages of the block
*/
function decode(bytes calldata _body)
internal
pure
returns (bytes32, bytes32, bytes32[] memory, bytes32[] memory)
{
bytes32[] memory l1ToL2Msgs = new bytes32[](Constants.NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
bytes32[] memory l2ToL1Msgs;

uint256 offset = 0;

// Commitments
uint256 count = read4(_body, offset);
offset += 0x4 + count * 0x20;

// Nullifiers
count = read4(_body, offset);
offset += 0x4 + count * 0x20;

// Public data writes
count = read4(_body, offset);
offset += 0x4 + count * 0x40;

// L2 to L1 messages
count = read4(_body, offset);
l2ToL1Msgs = new bytes32[](count);
assembly {
calldatacopy(add(l2ToL1Msgs, 0x20), add(_body.offset, add(offset, 0x4)), mul(count, 0x20))
}
offset += 0x4 + count * 0x20;

// Contracts
count = read4(_body, offset);
offset += 0x4 + count * 0x54;

// L1 to L2 messages
count = read4(_body, offset);
// `l1ToL2Msgs` is fixed size so if `lengths.l1Tol2MsgsCount` < `Constants.NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP` the array
// will contain some zero values.
assembly {
calldatacopy(add(l1ToL2Msgs, 0x20), add(_body.offset, add(offset, 0x04)), mul(count, 0x20))
}

bytes32 l1ToL2MsgsHash = sha256(abi.encodePacked(l1ToL2Msgs));
bytes32 l2ToL1MsgsHash = sha256(abi.encodePacked(l2ToL1Msgs));

return (l1ToL2MsgsHash, l2ToL1MsgsHash, l2ToL1Msgs, l1ToL2Msgs);
}

/**
* @notice Reads 4 bytes from the data
* @param _data - The data to read from
* @param _offset - The offset to read from
* @return The 4 bytes read as a uint256
*/
function read4(bytes calldata _data, uint256 _offset) internal pure returns (uint256) {
return uint256(uint32(bytes4(_data[_offset:_offset + 4])));
}
}
Loading