State sync
State Sync
is the native mechanism for a user in the Polygon PoS chain to read the latest Ethereum data.
Validators on the Heimdall layer pickup the StateSynced event and pass it on to the Bor layer.
The receiver contract inherits IStateReceiver, and custom logic sits inside onStateReceive function.
This is the flow required from dapps / users to work with state-sync:
- Call the smart contract function present here: https://github.com/maticnetwork/contracts/blob/19163ddecf91db17333859ae72dd73c91bee6191/contracts/root/stateSyncer/StateSender.sol#L33
- Which emits an event called
StateSynced(uint256 indexed id, address indexed contractAddress, bytes data);
- All the validators on the Heimdall chain receive this event and one of them, whoever wishes to get the tx fees for state sync sends this transaction to Heimdall.
- Once
state-sync
transaction on Heimdall has been included in a block, it is added to pending state-sync list. - After every sprint on
bor
, the Bor node fetches the pending state-sync events from Heimdall via an API call. - The receiver contract inherits
IStateReceiver
interface, and custom logic of decoding the data bytes and performing any action sits insideonStateReceive
function: https://github.com/maticnetwork/genesis-contracts/blob/master/contracts/IStateReceiver.sol
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 ⅔+ 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 0x0000000000000000000000000000000000001001
— StateReceiver.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 by https://github.com/ethereum/EIPs/blob/master/EIPS/eip-210.md.
System call is helpful to change state to contract without making any transaction.
State-sync logs and Bor block receipts¶
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
.