Data Tunnel

Overview

Matic validators continuously monitor a contract on Ethereum chain called StateSender. Each time a registered contract on Ethreum chain calls this contract, it emits an event. Using this event Matic validators relay the data to another contract on Matic chain. This StateSync mechanism is used to send data from Ethereum to Matic.

Matic validators also periodically submit a hash of all transactions on Matic chain to Ethereum chain. This Checkpoint can be used to verify any transaction that happened on Matic. Once a transaction is verified to have happened on Matic chain, action can be taked accordingly on Ethereum.

These 2 mechanisms can be used togather to enable two way data transfer between Ethereum and Matic. To abstract out all these interactions, you can directly inherit our BaseRootTunnel (on Ethereum) and BaseChildTunnel (on Matic) contracts.

Root Tunnel Contract

Use the BaseRootTunnel contract from here. This contract gives access to following functions -

  • _sendMessageToChild(bytes memory message) - This function can be called internally with any bytes data as message. This data will be sent as is to ChildTunnel.

  • _processMessageFromChild(bytes memory message) - This is a virtual function that needs to be implemented to handle data being sent from ChildTunnel.

  • receiveMessage(bytes memory inputData) - This function needs to be called to receive the message emitted by ChildTunnel. The proof of transaction needs to be provided as calldata.

Child Tunnel Contract

Use the BaseChildTunnel contract from here. This contract gives access to following functions -

  • _sendMessageToRoot(bytes memory message) - This function can be called internally to send any bytes message to RootTunnel.

  • _processMessageFromRoot(bytes memory message) - This is a virtual function that needs to implement the logic to handle message sent from RootTunnel.

Implementation

As an example we will implement contracts where we want to calculate sum till a provied number. The sum can be calculated on Ethereum chain by calling calculateSum function. This will be too expensive due to high gas fees.

Alternatively we can call calculateSumOnChildChain. This will internally send the number to ChildTunnel by calling _sendMessageToChild. Then on Matic chain _processMessageFromRoot will receive this number and do required calculation. After calculation is done call _sendMessageToRoot to emit this calculated result. Now finally call receiveMessage on RootTunnel giving proof of emitted result. This will internally call _processMessageFromChild to store it in variable.

Both these methods will give the same final result. Second method takes some more transactions but overall gas cost will be less because all heavy processing is done on Matic chain and only result is stored on Ethereum.

pragma solidity 0.6.6;
import {BaseRootTunnel} from "./BaseRootTunnel.sol";
contract RootTunnel is BaseRootTunnel {
uint256 public sum;
uint256 public sumFromChildChain;
function _processMessageFromChild(bytes memory message) internal override {
(uint256 n) = abi.decode(message, (uint256));
sumFromChildChain = n;
}
function calculateSumOnChildChain(uint256 number) external {
_sendMessageToChild(abi.encode(number));
}
// reference function to do full calculation on ethereum
function calculateSum(uint256 number) external {
sum = 0;
for (; number > 0; number--) {
sum += number;
}
}
}
pragma solidity 0.6.6;
import {BaseChildTunnel} from "./BaseChildTunnel.sol";
contract ChildTunnel is BaseChildTunnel {
uint256 public sum;
function _processMessageFromRoot(bytes memory message) internal override {
uint256 number = abi.decode(message, (uint256));
sum = 0;
for (; number > 0; number--) {
sum += number;
}
}
function sendSumToRoot() external {
_sendMessageToRoot(abi.encode(sum));
}
}

Once you are ready with implementing your version of RootTunnel and ChildTunnel contracts, you need to register them on StateSender contract.