Skip to main content

How does State Sync work?

State management sends the state from the Ethereum chain to the Bor chain. It is called state-sync.

State transfer from Ethereum to Bor happens through system call. Suppose, a user deposits USDC to the deposit manager on Ethereum. Validators listen to those events, validate, and store them in Heimdall state. Bor gets the latest state-sync records and updates the Bor state (mints equal amount of USDC on Bor) using a system call.

State sender

Source: https://github.com/maticnetwork/contracts/blob/develop/contracts/root/stateSyncer/StateSender.sol

To sync state, the contract calls following method state sender contract on Ethereum chain.

contract StateSender {
/**
* Emits `stateSynced` events to start sync process on Ethereum chain
* @param receiver Target contract on Bor chain
* @param data Data to send
*/
function syncState (
address receiver,
bytes calldata data
) external;
}

receiver contract must be present on the child chain, which receives state data once the process is complete. syncState emits StateSynced event on Ethereum, which is the following:

/**
* Emits `stateSynced` events to start sync process on Ethereum chain
* @param id State id
* @param contractAddress Target contract address on Bor
* @param data Data to send to Bor chain for Target contract address
*/
event StateSynced (
uint256 indexed id,
address indexed contractAddress,
bytes data
);

Once the StateSynced event emitted on the stateSender contract on the Ethereum chain, Heimdall listens to those events and adds to the Heimdall state after 2/3+ validators agree on the.

After every sprint (currently 64 blocks on Bor), Bor fetches new state-sync record and updates the state using a system call. Here is the code for the same: https://github.com/maticnetwork/bor/blob/6f0f08daecaebbff44cf18bee558fc3796d41832/consensus/bor/genesis_contracts_client.go#L51

During commitState, Bor executes onStateReceive, with stateId and data as args, on target contract.

State receiver interface on Bor

receiver contract on Bor chain must implement following interface.

// IStateReceiver represents interface to receive state
interface IStateReceiver {
function onStateReceive(uint256 stateId, bytes calldata data) external;
}

Only 0x0000000000000000000000000000000000001001StateReceiver.sol, must be allowed to call onStateReceive function on target contract.

System Call

Only system address, 2^160-2, allows making a system call. Bor calls it internally with the system address as msg.sender. It changes the contract state and updates the state root for a particular block. Inspired from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-210.md and https://wiki.parity.io/Validator-Set#contracts

System call is helpful to change state to contract without making any transaction.

State-sync logs and Bor Block Receipt

Events emitted by system calls are handled in a different way than normal logs. Here is the code: https://github.com/maticnetwork/bor/pull/90.

Bor produces a new tx / receipt just for the client which includes all the logs for state-sync. Tx hash is derived from block number and block hash (last block at that sprint):

keccak256("matic-bor-receipt-" + block number + block hash)

This doesn't change any consensus logic, only client changes. eth_getBlockByNumber, eth_getTransactionReceipt, and eth_getLogs include state-sync logs with derived. Note that the bloom filter on the block doesn't include inclusion for state-sync logs. It also doesn't include derived tx in transactionRoot or receiptRoot.