owned this note
owned this note
Published
Linked with GitHub
# PoE
[TOC]
## Glossary
- **PoE** --> Proof-of-Efficiency model, consensus mechanism
- **bridge L1** --> contract on ethereum mainnet that handles asset transfers between rollups
- **bridge L2** --> channel to communicate with mainnet deployed on the rollup which controls minting/burning assets
## Motivation
For the implementation of the zk-EVM, a consensus mechanism for a decentralized L2 protocol is necessary.
This consensus mechanism defines a two-step model where:
- Permissionless Sequencers as benefited participants in the protocol
- The computation of a "virtual" state from the data availability and a "final" state based on the validity proofs
- Space for permissionless Aggregators as the agents to perform the specialized task of cryptographic proof generation
## Specification
This protocol separates batch creation into two steps. Therefore, we find two parts:
- **Sequencers**: collect L2 transactions from users. They select and create L2 batch in the network (sending ethereum transaction (encoded as RLP) with data of all selected L2 txs)
- collect L2 txs and propose batches
- pay MATIC to SC to propose a new batch (proportional to the number of transactions)
- only propose transactions (no propose states)
- 1 sequencer / 1 chainID
> The sequencer needs to register to get a chainID (Users needs to chose a chainID to sign. That chainID could be sequencer specific or global. Meaning global that any sequencer could send that transactions and it will be valid)
- **Aggregators**: create validity proof of a new state of the L2 (for one or multiple batches)
- they have specialized hardware to create ZKP
- the first aggregator that sends a valid proof wins the race and get rewarded (MATIC sent by the sequencer)
- the zkp must contain the ethereum address of the aggregator
- invalid transactions are skipped
- The transactions will be processed, but if it is not valid it will have no effect on the state
Then, the two steps are done one for each part:
- `sendBatch`: the **sequencer** sends a group of L2 transactions
- `validateBatch`: the **aggregator** validates de batch
![](https://i.imgur.com/dzDt6Zd.png)
There are two state types:
- **Virtual state**: state calculated from all pending transactions
- **Confirmed state**: state confirmed by zpk
## Smart contract
### Actions
- registerSequencer
- sendBatch
- validateBatch
#### registerSequencer
- Parameters:
- sequencer RPC URL
- Smart Contract Actions:
- staking --> maybe in the future (MATIC)
- `mapping[address => Sequencer]` --> `Sequencer = {sequencerURL,chainID}`
```
registerSequencer(sequencerURL) {
mappingSeq[address] = { sequencerURL, chainID }
}
```
#### sendBatch
- Parameters:
- calldata [transaction bytes EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md) ([RLP encoded](https://eth.wiki/fundamentals/rlp)) --> `bytes`
- matic max amount --> `uint256`
- Smart contract Actions:
- State updates --> `mapping[numBatch] => struct = { H(txs, currentGlobalExitRoot), chainId/ethAddr}`
- input in the verify proof
- `sequencerCollateral` in MATIC
- pay MATIC aggregator
```
sendBatch(bytes l2TxsData, uint256 maticAmount){
maticCollateral = calculateMaticCollateral
transfer(maticCollateral)
lastBatchSent++
currentGlobalExitRoot = bridge.currentGlobalExitRoot();
mappingSentBatches[lastBatchSent] = {H(abi.encodePacked(l2TxsData, currentGlobalExitRoot)), msg.sender, maticCollateral}
emit event SendBatch
}
```
> invalid L2 tx are selected as NOP
> In mappingSentBatches the `msg.sender` is saved only if the `chainID != DEFAULT_CHAINID`
#### validateBatch
- Parameters:
- `newLocalExitRoot`
- `newStateRoot`
- `batchNum` (sanity check)
- `proofA`, `proofB`, `proofC`
- Smart contract Actions:
- input:
- `currentStateRoot`: current state root
- `currentLocalExitRoot`: current local (rollup) exit root
- `newStateRoot`: new state root
- `newLocalExitRoot`: new local (rollup) exit root
- `sequencerAddress`
- `hash(l2TxsData # lastGlobalExitRoot)`
- ```
**Buffer bytes notation**
[ calldata bits ] l2TxsData
[ 256 bits ] lastGlobalExitRoot
```
- `chainID`: CHAIN_ID_DEFAULT + sequencerID
```
**Buffer bytes notation**
[ 256 bits ] currentStateRoot
[ 256 bits ] currentLocalExitRoot
[ 256 bits ] newStateRoot
[ 256 bits ] newLocalExitRoot
[ 160 bits ] sequencerAddress
[ 256 bits ] keccak256(l2TxsData # lastGlobalExitRoot)
[ 32 bits ] chainID (sequencerID)
[ 32 bits ] batchNum
```
- verify proof
- update roots
- Communicate with bridge
- push newLocalExitRoot
- get globalExitRoot
```
validateBatch(newLocalExitRoot, newStateRoot, batchNum, proofA, proofB, proofC) {
require(batchNum == lastConfirmedBatch + 1)
input = calculateInput()
require(verifyProof)
lastVerifiedBatch++
currentStateRoot = newStateRoot;
currentLocalExitRoot = newLocalExitRoot;
bridge.updateRollupExitRoot(currentLocalExitRoot);
lastGlobalExitRoot = bridge.currentGlobalExitRoot();
matic.transfer()
emit event VerifyBatch
}
```
### State
- `lastGlobalExitRoot`
- `currentLocalExitRoot`
- `currentStateRoot`
- `lastVerifiedBatch`
- `lastBatchSent`
- `numSequencers`
- `mapping(uint256 => BatchData) sentBatches` ==> `BatchData = {sequencerAddress, batchL2HashData, maticCollateral}`
- `mapping(address => Sequencer) sequencers` ==> `Sequencer = {sequencerURL, chainID}`
### Sequencer collateral
- Adaptative algoritm to calculate the sequencer collateral
- Depends on the congestion of the network
- More tx/block, more collateral is needed for tx
- Recalculated every batch is aggregated
## Considerations / Simplifications
- sendBatch:
- pay MATIC aggregator --> 1MATIC/tx (SIMPLIFIED)
- 1.3 bridgeL1:
- no bridge contract (it only returns true)
- genesisBlock