Cross layer communication

This section describes the communication mechanism used to ensure trustworthy communications between L1 and L2. It lays the foundation for high-level application protocols like token bridges.

Merkle Mountain Ranges (MMR)

Merkle Mountain Ranges (MMR) are an alternative to Merkle trees. Same as Merkle trees, their leaves are data hashes and parent nodes are the hashes of their two children. MMR is strictly append-only, and consist of a list of perfectly balanced binary trees. Comparing with Merkle trees, this kind of structure is more convenient for incremental construction, and therefore suitable for situations where not all leaf nodes are decided at the beginning.

Goshen employs MMR to store cross layer messages sent between L1 and L2, and MMR proof is used to verify the existence of sent message on the target chain.

Send cross layer messages

Cross layer messages is stored in CrossLayerWitness system contract, deployed on both L1 and L2 chain. Contracts index the messages automatically to distinguish them from application layer messages with the same content. Witness contract provide the following method to send cross layer messages:

interface ICrossLayerWitness {
    /**
     * @dev Send cross layer message
     * @param _target call target address on the receiver chain
     * @param _message EVM call data
     */
    function sendMessage(address _target, bytes calldata _message) external;
    ...
}

When called, Witness contract will assign an index (starting from 0) to this message, and use the following function to calculate the message hash and insert it into the MMR.

function crossLayerMessageHash(
    address _target,
    address _sender,
    uint64 _messageIndex,
    bytes memory _message
) internal pure returns (bytes32) {
    // @notice we encode _messageIndex as uint256 to let the total encoded data size > 64byte
    // so when appended in MMR, it can be distinguished from the inner node.
    return keccak256(abi.encodePacked(_target, _sender, uint256(_messageIndex), _message));
}

Relay cross layer messages

When cross layer messages is sent from the source chain, a relay transaction must be sent to the target chain to deliver that message. Anyone has the permission to relay cross layer message and pay the transaction fee.

Relay L1 -> L2 messages

Relaying L1 to L2 messages will be triggered automatically. When L1CrossLayerWitness.sendMessage is called, Witness contract will generate a relay transaction with gas limit set to 3000000, and append it to the pending transaction queue of RollupInputBatch contract. The relay transaction will be included in rollup batch by Sequencer. If the execution of this transaction on L2 is reverted because of out of gas or rejected by the message target contract, user can replay this message manually.

interface IL2CrossLayerWitness is ICrossLayerWitness {
    /**
     * @dev Relay L1 -> L2 message that in L1CrossLayerWitness contract.
     * @param _target EVM call Target
     * @param _sender EVM call sender
     * @param _message EVM call data
     * @param _messageIndex index in l1 merkle mountain range's leaf
     * @param _messageIndex l1 merkle mountain range rootli
     * @param _mmrSize l1 merkle mountain range tree size
     * @return whether relay message call success
     * @notice Revert if:
     * - sender isn't L1CrossLayerWitness.
     * - message already relayed
     */
    function relayMessage(
        address _target,
        address _sender,
        bytes memory _message,
        uint64 _messageIndex,
        bytes32 _mmrRoot,
        uint64 _mmrSize
    ) external returns (bool);

    /**
     * @dev Relay L1 -> L2 message when previous relayed failed
     * @param _target EVM call Target
     * @param _sender EVM call sender
     * @param _message EVM call data
     * @param _messageIndex index in l1 merkle mountain range's leaf
     * @param _proof Merkle mountain range inclusion proof
     * @param _mmrSize L1 merkle mountain range tree size
     * @return whether return call success
     * @notice Revert if:
     * - Provided mmrSize have no related mmrRoot.(which means first relay message didn't successful finish or relay succeed)
     * - Provided _proof cant proof message indeed exist in l1 mmr root got by local recorded
     * - Provided message already relayed
     */
    function replayMessage(
        address _target,
        address _sender,
        bytes memory _message,
        uint64 _messageIndex,
        bytes32[] memory _proof,
        uint64 _mmrSize
    ) external returns (bool);
}

Relay L2 -> L1 messages

When L2CrossLayerWitness.sendMessage is called, the contract will append the messsage hash to the MMR, then the MMR size and root will be set in L2 block's nonce and mixHash field. The proposer will submit the block hash as L2 state to L1. When the challenge period has passed, the L2 state committed by the proposer is finalized. User can relay the message with MMR proof and L2 block header.

interface IL1CrossLayerWitness is ICrossLayerWitness {
    /**
     * @dev Relay L2 -> L1 message that in L2CrossLayerWitness contract.
     * @param _target EVM call Target
     * @param _sender EVM call sender
     * @param _message EVM call data
     * @param _messageIndex index in l2 merkle mountain range's leaf
     * @param _rlpHeader L2 block header contains l2 mmr root and size
     * @param _stateInfo L2 stateInfo contains block hash
     * @param _proof MMR proof that used to proof provided info surly exists in l2 block mmr
     * @return whether relay call message success
     * @notice Revert if:
     * - reentrancy
     * - target is rollup system contract.
     * - provide wrong state info(not exist in StateCommitChain)
     * - provided state info not confirmed.(only confirmed state is right)
     * - provided block is not consistent with state recorded
     * - provided _proof can't proof message indeed exist in l2 block
     * - message already relayed
     * - message blocked
     */
    function relayMessage(
        address _target,
        address _sender,
        bytes memory _message,
        uint64 _messageIndex,
        bytes memory _rlpHeader,
        Types.StateInfo memory _stateInfo,
        bytes32[] memory _proof
    ) external returns (bool);
    ...
}

Receive cross layer messages

When the witness contract invoked by relay transaction receives the cross layer message, it will check the MMR proof and then invoke the target. Witness contract provide the following method to query the message sender:

function crossLayerSender() external view returns (address);

Last updated