Skip to content

Commit

Permalink
feat: decoder return l1<->l2 msgs (#597)
Browse files Browse the repository at this point in the history
* feat: decoder return l1<->l2 msgs

* chore: forge fmt

* fix: address comments

* fix: test clarity nit

* fix: address nits

* fix: improve consistency on messages vs msgs
  • Loading branch information
LHerskind authored May 17, 2023
1 parent c1fa0a1 commit 83a0f63
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 65 deletions.
134 changes: 77 additions & 57 deletions l1-contracts/src/core/Decoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,31 @@ pragma solidity >=0.8.18;
* |--- |--- | ---
*/
contract Decoder {
struct ArrayLengths {
uint256 commitmentCount;
uint256 nullifierCount;
uint256 dataWritesCount;
uint256 l2ToL1MsgsCount;
uint256 contractCount;
uint256 l1Tol2MsgsCount;
}

struct ArrayOffsets {
uint256 commitmentOffset;
uint256 nullifierOffset;
uint256 publicDataOffset;
uint256 l2ToL1MsgsOffset;
uint256 contractOffset;
uint256 contractDataOffset;
uint256 l1ToL2MsgsOffset;
}

uint256 internal constant COMMITMENTS_PER_KERNEL = 4;
uint256 internal constant NULLIFIERS_PER_KERNEL = 4;
uint256 internal constant PUBLIC_DATA_WRITES_PER_KERNEL = 4;
uint256 internal constant L2_TO_L1_MSGS_PER_KERNEL = 2;
uint256 internal constant CONTRACTS_PER_KERNEL = 1;
uint256 internal constant L1_TO_L2_MESSAGES_PER_ROLLUP = 16;
uint256 internal constant L1_TO_L2_MSGS_PER_ROLLUP = 16;

// Prime field order
uint256 internal constant P =
Expand All @@ -81,6 +100,8 @@ contract Decoder {
* @return startStateHash - The state hash expected prior the execution.
* @return endStateHash - The state hash expected after the execution.
* @return publicInputHash - The hash of the public inputs
* @return l2ToL1Msgs - The L2 to L1 messages
* @return l1ToL2Msgs - The L1 to L2 messages
*/
function _decode(bytes calldata _l2Block)
internal
Expand All @@ -89,40 +110,44 @@ contract Decoder {
uint256 l2BlockNumber,
bytes32 startStateHash,
bytes32 endStateHash,
bytes32 publicInputHash
bytes32 publicInputHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
)
{
l2BlockNumber = _getL2BlockNumber(_l2Block);
// 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, 0x4, _l2Block);
endStateHash = _computeStateHash(l2BlockNumber, 0x120, _l2Block);
publicInputHash = _computePublicInputsHash(_l2Block);

bytes32 diffRoot;
bytes32 l1ToL2MsgsHash;
(diffRoot, l1ToL2MsgsHash, l2ToL1Msgs, l1ToL2Msgs) = _computeConsumables(_l2Block);
publicInputHash = _computePublicInputHash(_l2Block, diffRoot, l1ToL2MsgsHash);
}

/**
* Computes a hash of the public inputs from the calldata
* @notice Computes the public input hash
* @dev Uses sha256 to field
* @param _l2Block - The L2 block calldata.
* @return sha256(header[0x4: 0x23c], diffRoot, l1Tol2MessagesHash)
* @param _diffRoot - The root of the diff merkle tree
* @param _l1ToL2MsgsHash - The hash of the L1 to L2 messages
* @return publicInputHash - The hash of the public inputs (sha256 to field)
*/
function _computePublicInputsHash(bytes calldata _l2Block) internal pure returns (bytes32) {
// header size - block number size + one value for the diffRoot + one value for l1ToL2MessagesHash
function _computePublicInputHash(
bytes calldata _l2Block,
bytes32 _diffRoot,
bytes32 _l1ToL2MsgsHash
) internal pure returns (bytes32) {
uint256 size = 0x23c - 0x04 + 0x20 + 0x20;

// Compute the public inputs hash
bytes memory temp = new bytes(size);
assembly {
calldatacopy(add(temp, 0x20), add(_l2Block.offset, 0x04), size)
}

// Diff root
(bytes32 diffRoot, bytes32 l1ToL2messagesHash) = _computeDiffRootAndMessagesHash(_l2Block);
assembly {
let endOfTreesData := sub(0x23c, 0x04)
mstore(add(temp, add(0x20, endOfTreesData)), diffRoot)
mstore(add(temp, add(0x40, endOfTreesData)), l1ToL2messagesHash)
mstore(add(temp, add(0x20, endOfTreesData)), _diffRoot)
mstore(add(temp, add(0x40, endOfTreesData)), _l1ToL2MsgsHash)
}

return bytes32(uint256(sha256(temp)) % P);
}

Expand Down Expand Up @@ -165,36 +190,21 @@ contract Decoder {
return sha256(temp);
}

struct ArrayLengths {
uint256 commitmentCount;
uint256 nullifierCount;
uint256 dataWritesCount;
uint256 l2ToL1MessagesCount;
uint256 contractCount;
uint256 l1Tol2MessagesCount;
}

struct ArrayOffsets {
uint256 commitmentOffset;
uint256 nullifierOffset;
uint256 publicDataOffset;
uint256 l2ToL1MsgsOffset;
uint256 contractOffset;
uint256 contractDataOffset;
uint256 l1ToL2MessagesOffset;
}

/**
* @notice Creates a "diff" tree and compute its root
* @notice Computes consumables for the block
* @param _l2Block - The L2 block calldata.
* @return diffRoot - The root of the diff tree (new commitments, nullifiers etc)
* @return l1ToL2MsgsHash - 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 _computeDiffRootAndMessagesHash(bytes calldata _l2Block)
function _computeConsumables(bytes calldata _l2Block)
internal
pure
returns (bytes32, bytes32)
returns (bytes32, bytes32, bytes32[] memory, bytes32[] memory)
{
// Find the lengths of the different inputs
// TOOD: Naming / getting the messages root within this function is a bit weird
// TODO: Naming / getting the messages root within this function is a bit weird
ArrayLengths memory lengths;
ArrayOffsets memory offsets;
{
Expand All @@ -206,35 +216,47 @@ contract Decoder {
offset := add(add(offset, 0x4), mul(nullifierCount, 0x20))
let dataWritesCount := and(shr(224, calldataload(offset)), 0xffffffff)
offset := add(add(offset, 0x4), mul(nullifierCount, 0x40))
let l2ToL1Count := and(shr(224, calldataload(offset)), 0xffffffff)
offset := add(add(offset, 0x4), mul(l2ToL1Count, 0x20))
let l2ToL1MsgsCount := and(shr(224, calldataload(offset)), 0xffffffff)
offset := add(add(offset, 0x4), mul(l2ToL1MsgsCount, 0x20))
let contractCount := and(shr(224, calldataload(offset)), 0xffffffff)
offset := add(add(offset, 0x4), mul(contractCount, 0x54))
let l1Tol2MessagesCount := and(shr(224, calldataload(offset)), 0xffffffff)
let l1Tol2MsgsCount := and(shr(224, calldataload(offset)), 0xffffffff)

// Store it in lengths
mstore(lengths, commitmentCount)
mstore(add(lengths, 0x20), nullifierCount)
mstore(add(lengths, 0x40), dataWritesCount)
mstore(add(lengths, 0x60), l2ToL1Count)
mstore(add(lengths, 0x60), l2ToL1MsgsCount)
mstore(add(lengths, 0x80), contractCount)
mstore(add(lengths, 0xa0), l1Tol2MessagesCount) // currently included to allow optimisation where empty messages are not included in calldata
mstore(add(lengths, 0xa0), l1Tol2MsgsCount) // currently included to allow optimisation where empty messages are not included in calldata
}
}

bytes32[] memory baseLeafs = new bytes32[](
lengths.commitmentCount / (COMMITMENTS_PER_KERNEL * 2)
);
bytes32[] memory l2ToL1Msgs = new bytes32[](
lengths.l2ToL1MsgsCount
);

// Data starts after header. Look at L2 Block Data specification at the top of this file.
{
offsets.commitmentOffset = 0x240;
offsets.nullifierOffset = offsets.commitmentOffset + 0x4 + lengths.commitmentCount * 0x20;
offsets.publicDataOffset = offsets.nullifierOffset + 0x4 + lengths.nullifierCount * 0x20;
offsets.l2ToL1MsgsOffset = offsets.publicDataOffset + 0x4 + lengths.dataWritesCount * 0x40;
offsets.contractOffset = offsets.l2ToL1MsgsOffset + 0x4 + lengths.l2ToL1MessagesCount * 0x20;
offsets.contractOffset = offsets.l2ToL1MsgsOffset + 0x4 + lengths.l2ToL1MsgsCount * 0x20;
offsets.contractDataOffset = offsets.contractOffset + lengths.contractCount * 0x20;
offsets.l1ToL2MessagesOffset = offsets.contractDataOffset + 0x4 + lengths.contractCount * 0x34;
offsets.l1ToL2MsgsOffset = offsets.contractDataOffset + 0x4 + lengths.contractCount * 0x34;

// load the l2 to l1 msgs (done here as offset will be altered in loop)
assembly {
calldatacopy(
add(l2ToL1Msgs, 0x20),
add(_l2Block.offset, mload(add(offsets, 0x60))),
mul(mload(add(lengths, 0x60)), 0x20)
)
}

for (uint256 i = 0; i < baseLeafs.length; i++) {
/**
Expand Down Expand Up @@ -341,25 +363,23 @@ contract Decoder {
}

bytes32 diffRoot = _computeRoot(baseLeafs);

bytes32 messagesHash;
bytes32[] memory l1ToL2Msgs;
bytes32 l1ToL2MsgsHash;
{
uint256 messagesHashPreimageSize = 0x20 * L1_TO_L2_MESSAGES_PER_ROLLUP;
bytes memory messagesHashPreimage = new bytes(
messagesHashPreimageSize
);
uint256 l1ToL2MsgsHashPreimageSize = 0x20 * L1_TO_L2_MSGS_PER_ROLLUP;
l1ToL2Msgs = new bytes32[](L1_TO_L2_MSGS_PER_ROLLUP);
assembly {
calldatacopy(
add(messagesHashPreimage, 0x20),
add(l1ToL2Msgs, 0x20),
add(_l2Block.offset, mload(add(offsets, 0xc0))),
messagesHashPreimageSize
l1ToL2MsgsHashPreimageSize
)
}

messagesHash = sha256(messagesHashPreimage);
l1ToL2MsgsHash = sha256(abi.encodePacked(l1ToL2Msgs));
}

return (diffRoot, messagesHash);
return (diffRoot, l1ToL2MsgsHash, l2ToL1Msgs, l1ToL2Msgs);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ contract Rollup is Decoder {
* @param _l2Block - The L2Block data, formatted as outlined in `Decoder.sol`
*/
function process(bytes memory _proof, bytes calldata _l2Block) external {
(uint256 l2BlockNumber, bytes32 oldStateHash, bytes32 newStateHash, bytes32 publicInputHash) =
_decode(_l2Block);
(
uint256 l2BlockNumber,
bytes32 oldStateHash,
bytes32 newStateHash,
bytes32 publicInputHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
) = _decode(_l2Block);

// @todo Proper genesis state. If the state is empty, we allow anything for now.
if (rollupStateHash != bytes32(0) && rollupStateHash != oldStateHash) {
Expand Down
36 changes: 32 additions & 4 deletions l1-contracts/test/Decoder.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ contract DecoderTest is Test {
"Invalid messages hash"
);

(uint256 l2BlockNumber, bytes32 startStateHash, bytes32 endStateHash, bytes32 publicInputsHash)
= helper.decode(block_empty_1);
(
uint256 l2BlockNumber,
bytes32 startStateHash,
bytes32 endStateHash,
bytes32 publicInputsHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
) = helper.decode(block_empty_1);

assertEq(l2BlockNumber, 1, "Invalid block number");
assertEq(
Expand All @@ -64,6 +70,13 @@ contract DecoderTest is Test {

rollup.process(bytes(""), block_empty_1);

for (uint256 i = 0; i < l2ToL1Msgs.length; i++) {
assertEq(l2ToL1Msgs[i], bytes32(0), "Invalid l2ToL1Msgs");
}
for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertEq(l1ToL2Msgs[i], bytes32(0), "Invalid l1ToL2Msgs");
}

assertEq(rollup.rollupStateHash(), endStateHash, "Invalid rollup state hash");
}

Expand All @@ -82,8 +95,14 @@ contract DecoderTest is Test {
"Invalid messages hash"
);

(uint256 l2BlockNumber, bytes32 startStateHash, bytes32 endStateHash, bytes32 publicInputsHash)
= helper.decode(block_mixed_1);
(
uint256 l2BlockNumber,
bytes32 startStateHash,
bytes32 endStateHash,
bytes32 publicInputsHash,
bytes32[] memory l2ToL1Msgs,
bytes32[] memory l1ToL2Msgs
) = helper.decode(block_mixed_1);

assertEq(l2BlockNumber, 1, "Invalid block number");
assertEq(
Expand All @@ -102,6 +121,15 @@ contract DecoderTest is Test {
"Invalid public input hash"
);

for (uint256 i = 0; i < l2ToL1Msgs.length; i++) {
// recreate the value generated by `integration_l1_publisher.test.ts`.
bytes32 expectedValue = bytes32(uint256(0x300 + 32 * (1 + i / 2) + i % 2));
assertEq(l2ToL1Msgs[i], expectedValue, "Invalid l2ToL1Msgs");
}
for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertEq(l1ToL2Msgs[i], bytes32(uint256(0x401 + i)), "Invalid l1ToL2Msgs");
}

rollup.process(bytes(""), block_mixed_1);

assertEq(rollup.rollupStateHash(), endStateHash, "Invalid rollup state hash");
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/test/DecoderHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ contract DecoderHelper is Decoder {
function decode(bytes calldata _l2Block)
external
pure
returns (uint256, bytes32, bytes32, bytes32)
returns (uint256, bytes32, bytes32, bytes32, bytes32[] memory, bytes32[] memory)
{
return _decode(_l2Block);
}
Expand All @@ -19,7 +19,7 @@ contract DecoderHelper is Decoder {
pure
returns (bytes32, bytes32)
{
(bytes32 diffRoot, bytes32 l1ToL2MessagesHash) = _computeDiffRootAndMessagesHash(_l2Block);
(bytes32 diffRoot, bytes32 l1ToL2MessagesHash,,) = _computeConsumables(_l2Block);
return (diffRoot, l1ToL2MessagesHash);
}
}

0 comments on commit 83a0f63

Please sign in to comment.