Skip to content

Commit

Permalink
feat: Add data availability oracle (AztecProtocol#3897)
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind authored Jan 9, 2024
1 parent 544d24e commit 5441753
Show file tree
Hide file tree
Showing 9 changed files with 600 additions and 16 deletions.
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

0 comments on commit 5441753

Please sign in to comment.