Bor is state chain in Polygon architecture. It is a fork of Geth https://github.com/ethereum/go-ethereum with new consensus called Bor.
Bor uses new improved consensus, inspired by Clique consensus https://eips.ethereum.org/EIPS/eip-225
More details on consensus and specifications:
The genesis block contains all the essential information to configure the network. It's basically the config file for Bor chain. To boot up Bor chain, the user needs to pass in the location of the file as a param.
genesis.json as Genesis block and params. Here is an example for Bor genesis
EVM/Solidity as VM
Bor uses un-modified EVM as a VM for a transaction. Developers can deploy any contract they wish using the same Ethereum tools and compiler like
solc without any changes.
Matic as Native token (Gas token)
Bor has a Matic token as a native token similar to ETH in Ethereum. It is often called the gas token. This token works correctly as to how ETH works currently on the Ethereum chain.
In addition to that, Bor provides an in-built wrapped ERC20 token for the native token (similar to WETH token), which means applications can use wrapped MATIC ERC20 token in their applications without creating their own wrapped ERC20 version of the Matic native token.
Wrapped ERC20 token is deployed at
[MRC20.sol](https://github.com/maticnetwork/contracts/blob/develop/contracts/child/MRC20.sol) on Bor as one of the genesis contracts.
Native token is used as fees while sending transaction on Bor. This prevents spam on Bor and provides incentives to Block Producers to run the chain for longer period and discourages bad behaviour.
A transaction sender defines
GasPrice for each transaction and broadcasts it on Bor. Each producer can define how much minimum gas price they can accept using
--gas-price while starting Bor node. If user-defined
GasPrice on the transaction is the same or greater than producer defined gas price, the producer will accept the transaction and includes it in the next available block. This enables each producer to allow its own minimum gas price requirement.
Transaction fees will be deducted from sender's account in terms of Native token.
Here is the formula for transaction fees:
Collected fees for all transactions in a block are transferred to the producer's account using coinbase transfer. Since having more staking power increases your probability to become a producer, it will allow a validator with high staking power to collect more rewards (in terms of fees) accordingly.
Transfer receipt logs
Each Plasma compatible ERC20 token on Bor adds a special transfer receipt log. The Matic token is no exception to that.
LogTransfer is a special log that is added to all plasma compatible ERC20/721 tokens. Consider it as one 2-inputs-2-outputs UTXO for transfer. Here,
output1 = input1 - amount and
output2 = input2 + amount This allows plasma fraud-proof contracts to verify a transfer of Matic ERC20 tokens (here, Native token) on the Ethereum chain.
Since Matic token is Native token and doesn't have Native ERC20 token, Bor adds receipt log for each transfer made for Native token using following Golang code. Source: https://github.com/maticnetwork/bor/blob/develop/core/state_transition.go#L241-L252
Deposit native token
A user can receive Native token by depositing Matic tokens on Ethereum main-chain to
DepositManager contract (deployed on Ethereum chain). Source: https://github.com/maticnetwork/contracts/blob/develop/contracts/root/depositManager/DepositManager.sol#L68
depositERC20 tokens, users can move Matic ERC20 token (Native token) or any other ERC20 tokens from the Ethereum chain to Bor chain.
Withdraw native token
Withdraw from Bor chain to Ethereum chain works exactly like any other ERC20 tokens. A user can call
withdraw function on ERC20 contract, deployed on Bor, at
0000000000000000000000000000000000001010 to initiate withdraw process for the same. Source: https://github.com/maticnetwork/contracts/blob/develop/contracts/child/MaticChildERC20.sol#L47-L61
In-built contracts (Genesis contracts)
Bor starts with three in-built contracts, often called genesis contracts. These contracts are available at block 0. Source: https://github.com/maticnetwork/genesis-contracts
These contracts are compiled using
solc --bin-runtime. Example, following command emits compiled code for
Genesis contract is defined in
genesis.json. When bor starts at block 0, it loads all contracts with the mentioned code and balance.
Here are details for each genesis contract:
Bor validator set
BorValidatorSet.sol contract manages validator set for spans. Having a current validator set and span information into a contract allows other contracts to use that information. Since Bor uses producers from Heimdall (external source), it uses system call to change the contract state.
For first sprint all producers are defined in
setInitialValidators is called when the second span is being set. Since Bor doesn't support constructor for genesis contract, the first validator set needs to be set to
First span details are following:
Solidity contract definition:
proposeSpan can be called by any valid validator with zero fees. Bor allows
proposeSpan transaction to be free transaction since it is part of the system.
commitSpan is being called through the system call.
The state receiver contract manages incoming state sync records. The
state-sync mechanism is basically a way to move state data from the Ethereum chain to Bor.
proposeState will be called by any valid validator with zero fees. Bor allows
proposeState transaction to be free transaction since it is part of the system.
commitState is being called through the system call.
Matic ERC20 token
This is special contact that wraps Native coin (like ETH in Ethereum) and provides an ERC20 token interface. Example:
transfer on this contract transfer Native coins.
withdraw method in ERC20 token allows users to move their tokens from Bor to Ethereum chain.
Note: This contract doesn't support
allowance. This is same for every plasma compatible ERC20 token contracts.
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.
Limitation: Currently events emitted by system call are not observable and not-included in any transaction or block.
Span is a logically defined set of blocks for which a set of validators is chosen from among all the available validators. Heimdall will select the committee of producers out of all validators. The producers will include a subset of validators depending upon the number of validators in the system.
Propose Span Tx
Type: Heimdall transaction
spanProposeTx sets validators’ committee for a given
span in case of successful transaction inclusion. One transaction for each span must be included in Heimdall. It is called
spanProposeTx on Heimdall.
spanProposeTx must revert if being sent frequently or there is no less than 33% stake change occurred within the current committee (for, given
bor module on Heimdall handles span management. Here is how Bor chooses producers out of all validators:
- Bor creates multiple slots based on validators' power. Example: A with power 10 will have 10 slots, B with power 20 with have 20 slots.
- With all slots,
shufflefunction shuffles them using
seedand selects first
bormodule on Heimdall uses ETH 2.0 shuffle algorithm to choose producers out of all validators. Each span
nuses block hash of Ethereum (ETH 1.0) block
seed. Note that slots based selection allows validators to get selected based on their power. The higher power validator will have a higher probability to get selected. Source: https://github.com/maticnetwork/heimdall/blob/develop/bor/selection.go
Commit span Tx
Type: Bor transaction
There are two way to commit span in Bor.
Automatic span change
At the end of the current span, at last block of the last sprint, Bor queries the next span from Heimdall and set validators and producers for the next span using a system call.function commitSpan(bytes newSpan,address proposer,uint256 startBlock,uint256 endBlock,bytes validatorBytes,bytes producerBytes) public onlySystem;
Bor uses new producers as block producers for their next blocks.
spanproposed on Heimdall, the validator can force push span if span needs to be changed before the current span ends. A transaction to propose a
spanmust be committed to Bor by any validator. Bor then updates and commits the proposed span at end of the current sprint using a system call.
State management (State-sync)
State management sends the state from the Ethereum chain to Bor chain. It is called
state-sync. This is a way to move data from the Ethereum chain to Bor chain.
To sync state sync, call following method state sender contract on Ethereum chain. The
state-sync mechanism is basically a way to move state data from the Ethereum chain to Bor.
A user, who wants to move
data from contract on Ethereum chain to Bor chain, calls
syncSate method on
receiver contract must be present on the child chain, which receives state
data once the process is complete.
StateSynced event on Ethereum, which is the following:
StateSynced event emitted on
stateSender contract on the Ethereum chain, any validator sends
MsgEventRecord transaction on Heimdall.
After confirmation of a tx on Heimdall, a validator proposes
proposeState on Bor with the simple transaction and at end of the sprint, Bor commits and finalizes
state-sync by calling
commitState using a
commitState, Bor executes
data as args, on target contract.
State receiver interface
receiver contract on Bor chain must implement following interface.
StateReceiver.sol, must be allowed to call
onStateReceive function on target contract.
Bor currently works as expected with ~2 to 4 seconds' block time with 100 validators and 4 block producers. After multiple stress testing with huge number of transactions, exact block time will be decided.
Using sprint-based architecture helps Bor to create faster bulk blocks without changing the producer during the current sprint. Having delay between two sprints gives other producers to receive a broadcasted block, often called as
Note that time between two sprints is higher than normal blocks to buffer to reduce the latency issues between multiple producers.
Bor uses a very small set of producers to create faster blocks. It means it is prone to more censorship attacks than Heimdall. In order to deal with that, multiple testing will be done to find out the max number of producers for acceptable block time in the system.
Apart from that there are few attacks possible:
One producer is censoring the transaction
In that case, the transaction sender can wait for the next producer's sprint and try to send the transaction again.
All validators are colluding with each-other and censoring particular transaction
In this case, Polygon system will provide a way to submit a transaction on Ethereum chain and ask validators to include the transaction in next
xcheckpoints. If validators fail to include it during that time window, the user can slash the validators. Note that this is not currently implemented.
Producers can include invalid transaction during their turn. It can be possible at multiple levels:
One producer is fraudulent
If a producer includes invalid transaction at any height, other producers can create a fork and exclude that transaction since their valid node ignores invalid blocks
Span producers are fraudulent
If other producers don't create a fork, other validators who are validating the block can forcefully change the span by creating their own fork. This is not currently implemented since it requires how Geth works internally. However, this is in our future roadmap.
All validators are fraudulent
Assumption is that ⅔+1 validators must be honest to work this system correctly.