PoE

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

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:
  • 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
Select a repo