optimism-rollup 技術(shù)原理

optimism-rollup 做為目前最流行的以太坊L2解決方案梧喷,最近研究了下,寫個筆記。

另外,layer2并不是側(cè)鏈柳畔,而是以太坊的擴展,layer1到layer2的交易郭赐,并不是跨鏈交易薪韩,而是跨域交易.

optimism 的項目源碼在 https://github.com/ethereum-optimism/optimism
雖然是一個項目捌锭,但是分了很多層俘陷。為了方便理解,把每層的作用先記錄下.

一.代碼結(jié)構(gòu)

代碼結(jié)構(gòu).jpg

項目分了5層:

  • l2geth
  • contracts
  • data-transport-layer
  • batch-submitter
  • message-relayer

上面的5層共同構(gòu)成了optimism-rollup 這個系統(tǒng)观谦。下來分別了解下:

l2geth:

這是fork的以太坊的1.9.10版本拉盾,里面增加了個rollup包,實現(xiàn)了layer2上的兩種角色:Sequncer,Verifier.

  • Sequencer 用于偵聽layer1的跨域消息豁状,并且將交易改為OVMMessage到 虛擬機(OVM)中運行捉偏,

  • Verifier 用于驗證layer2上的Sequencer提交的交易的正確性。

以下是兩種角色的啟動代碼:

 func (s *SyncService) Start() error {
    if !s.enable {
        return nil
    }
    log.Info("Initializing Sync Service", "eth1-chainid", s.eth1ChainId)

    // When a sequencer, be sure to sync to the tip of the ctc before allowing
    // user transactions.
    if !s.verifier {
        err := s.syncTransactionsToTip()
        if err != nil {
            return fmt.Errorf("Cannot sync transactions to the tip: %w", err)
        }
        // TODO: This should also sync the enqueue'd transactions that have not
        // been synced yet
        s.setSyncStatus(false)
    }

    if s.verifier {
        go s.VerifierLoop()
    } else {
        go s.SequencerLoop()
    }
    return nil
}

packages包下有幾個文件夾泻红,不過主要的模塊是: batch-submitter,contracts,data-transport-layer,message-relayer. 我們分別說明下先了解下這些結(jié)構(gòu)所伴演的角色:

batch-submitter

向layer1的CTC chain和 SCC chain分別提交layer2的交易和交易的狀態(tài)根夭禽。里面分別實現(xiàn)了兩個typescript文件,state-batch-submitter.ts 和 tx-batch-submitter.ts 這兩個文件就是通過
分別向 scc chain 和 ctc chain提交狀態(tài)和交易的兩個文件谊路。另外讹躯,在CTC chain中,區(qū)塊叫batch,也是交易的集合缠劝。 batch-sumitter.ts就是每隔一段時間潮梯,從layer2中,從當前ctc的index開始惨恭,獲取一批交易.組成一個batch, 提交到ctc chain的 appendSequencerBatch 中去秉馏。代碼如下:

  public async _submitBatch(
    startBlock: number,
    endBlock: number
  ): Promise<TransactionReceipt> {
    // Do not submit batch if gas price above threshold
    const gasPriceInGwei = parseInt(
      ethers.utils.formatUnits(await this.signer.getGasPrice(), 'gwei'),
      10
    )
    if (gasPriceInGwei > this.gasThresholdInGwei) {
      this.log.warn(
        'Gas price is higher than gas price threshold; aborting batch submission',
        {
          gasPriceInGwei,
          gasThresholdInGwei: this.gasThresholdInGwei,
        }
      )
      return
    }

    const [
      batchParams,
      wasBatchTruncated,
    ] = await this._generateSequencerBatchParams(startBlock, endBlock)
    const batchSizeInBytes = encodeAppendSequencerBatch(batchParams).length / 2
    this.log.debug('Sequencer batch generated', {
      batchSizeInBytes,
    })

    // Only submit batch if one of the following is true:
    // 1. it was truncated
    // 2. it is large enough
    // 3. enough time has passed since last submission
    if (!wasBatchTruncated && !this._shouldSubmitBatch(batchSizeInBytes)) {
      return
    }
    this.log.debug('Submitting batch.', {
      calldata: batchParams,
    })

    const nonce = await this.signer.getTransactionCount()
    const contractFunction = async (gasPrice): Promise<TransactionReceipt> => {
      const tx = await this.chainContract.appendSequencerBatch(batchParams, {
        nonce,
        gasPrice,
      })
      this.log.info('Submitted appendSequencerBatch transaction', {
        nonce,
        txHash: tx.hash,
        contractAddr: this.chainContract.address,
        from: tx.from,
        data: tx.data,
      })
      return this.signer.provider.waitForTransaction(
        tx.hash,
        this.numConfirmations
      )
    }
    return this._submitAndLogTx(contractFunction, 'Submitted batch!')
  }
  

state-batch-submitter.ts 過程和tx-batch-submitter一樣,不過state-batch-submitter提交的是區(qū)塊的狀態(tài)根(state root),方法是_generateStateCommitmentBatch(startBlock:number,endBlock: number);

調(diào)用的是scc chain的 appendStateBatch方法.代碼如果下:

public async _submitBatch(
  startBlock: number,
  endBlock: number
): Promise<TransactionReceipt> {
  const batch = await this._generateStateCommitmentBatch(startBlock, endBlock)
  const tx = this.chainContract.interface.encodeFunctionData(
    'appendStateBatch',
    [batch, startBlock]
  )
  const batchSizeInBytes = remove0x(tx).length / 2
  this.log.debug('State batch generated', {
    batchSizeInBytes,
    tx,
  })

  if (!this._shouldSubmitBatch(batchSizeInBytes)) {
    return
  }

  const offsetStartsAtIndex = startBlock - BLOCK_OFFSET // TODO: Remove BLOCK_OFFSET by adding a tx to Geth's genesis
  this.log.debug('Submitting batch.', { tx })

  const nonce = await this.signer.getTransactionCount()
  const contractFunction = async (gasPrice): Promise<TransactionReceipt> => {
    const contractTx = await this.chainContract.appendStateBatch(
      batch,
      offsetStartsAtIndex,
      { nonce, gasPrice }
    )
    this.log.info('Submitted appendStateBatch transaction', {
      nonce,
      txHash: contractTx.hash,
      contractAddr: this.chainContract.address,
      from: contractTx.from,
      data: contractTx.data,
    })
    return this.signer.provider.waitForTransaction(
      contractTx.hash,
      this.numConfirmations
    )
  }
  return this._submitAndLogTx(contractFunction, 'Submitted state root batch!')
}

contracts

Layer2系統(tǒng)中使用的各種智能合約脱羡,不過有些需要在layer1上布署萝究,有些要在layer2上布署.需要注意的是: 這些合約要用 optimistic-solc 編譯器進行編譯母廷,目的是為了保證無論何時,在執(zhí)行同一個交易的時候糊肤,輸出結(jié)果都是一樣的。 因為 OVM_ExecutionManager.sol 中對于一些動態(tài)的opcode進行了重寫氓鄙, 比如: timestamp 在evm中獲取的是當前區(qū)塊的時間戳馆揉,而ovm中是按交易來的,執(zhí)行哪個交易抖拦,是哪個交易的時間戳升酣。

除了實現(xiàn)了ovm外,還包括一些賬戶态罪,跨域橋噩茄,layer1上的驗證者,預編譯合約和ctc,scc chain 复颈。這些都是optimism系統(tǒng)的核心绩聘。 所有的跨域消息都是通過調(diào)用這些合約和偵聽合約的事件進行工作的。

data-transport-layer

數(shù)據(jù)傳輸層耗啦,其實這層就是個事件索引器凿菩,通過rpc訪問layer1的rpc接口,索引layer1的合約事件帜讲,比如:
CTC chain的 TransactionEnqueued事件和SequencerBatchAppended事件衅谷,另外還有SCC chain的StateBatchAppended事件,索引到這些事件后似将,就會存在本地數(shù)據(jù)庫下获黔。然后再提供個rpc接口,供layer2也就是l2geth 來獲取這些事件在验。當然這層也會提供相當?shù)膔pc接口玷氏,也就是實現(xiàn)了個client專門供layer2來獲取數(shù)據(jù)。
**TransactionEnqueued 事件就是CTC chain的enqueue方法執(zhí)行完畢腋舌,將一個交易提交到了CTC chain 的queue隊列.SequencerBatchAppended 就是squencer提交了個batch到CTC chain中预茄。是 appendSequencerBatch 這個接口的事件。StateBatchAppended 當然就是 交易的狀態(tài)根提交到了SCC chain中 是方法 _appendBatch 的執(zhí)行事件侦厚。

相應的代碼如下:

protected async _start(): Promise<void> {
  // This is our main function. It's basically just an infinite loop that attempts to stay in
  // sync with events coming from Ethereum. Loops as quickly as it can until it approaches the
  // tip of the chain, after which it starts waiting for a few seconds between each loop to avoid
  // unnecessary spam.
  while (this.running) {
    try {
      const highestSyncedL1Block =
        (await this.state.db.getHighestSyncedL1Block()) ||
        this.state.startingL1BlockNumber
      const currentL1Block = await this.state.l1RpcProvider.getBlockNumber()
      const targetL1Block = Math.min(
        highestSyncedL1Block + this.options.logsPerPollingInterval,
        currentL1Block - this.options.confirmations
      )

      // We're already at the head, so no point in attempting to sync.
      if (highestSyncedL1Block === targetL1Block) {
        await sleep(this.options.pollingInterval)
        continue
      }

      this.logger.info('Synchronizing events from Layer 1 (Ethereum)', {
        highestSyncedL1Block,
        targetL1Block,
      })

      // I prefer to do this in serial to avoid non-determinism. We could have a discussion about
      // using Promise.all if necessary, but I don't see a good reason to do so unless parsing is
      // really, really slow for all event types.
      await this._syncEvents(
        'OVM_CanonicalTransactionChain',
        'TransactionEnqueued',
        highestSyncedL1Block,
        targetL1Block,
        handleEventsTransactionEnqueued
      )

      await this._syncEvents(
        'OVM_CanonicalTransactionChain',
        'SequencerBatchAppended',
        highestSyncedL1Block,
        targetL1Block,
        handleEventsSequencerBatchAppended
      )

      await this._syncEvents(
        'OVM_StateCommitmentChain',
        'StateBatchAppended',
        highestSyncedL1Block,
        targetL1Block,
        handleEventsStateBatchAppended
      )

      await this.state.db.setHighestSyncedL1Block(targetL1Block)

      if (
        currentL1Block - highestSyncedL1Block <
        this.options.logsPerPollingInterval
      ) {
        await sleep(this.options.pollingInterval)
      }
    } catch (err) {
      if (!this.running || this.options.dangerouslyCatchAllErrors) {
        this.logger.error('Caught an unhandled error', { err })
        await sleep(this.options.pollingInterval)
      } else {
        // TODO: Is this the best thing to do here?
        throw err
      }
    }
  }
}

_syncEvents就是通過某個合約的某個事件耻陕,然后通過相應的handle的存儲在本地的數(shù)據(jù)庫中。

到這里刨沦,有了batch-submitter和data-transport-layer就可以把layer1和layer2上的交易形成循環(huán)诗宣。

如果有人在layer2上執(zhí)行了交易,交易在打包后想诅,會通過batch-submitter 提交到layer1的CTC chain,然后data-transport-layer偵聽到事件后召庞,會存在本地數(shù)據(jù)庫岛心,這時l2geth可以通過rpc獲取 data-transport-layer存的數(shù)據(jù)。然后再到 layer2上嘗試執(zhí)行篮灼,拿結(jié)果和layer2已經(jīng)確定的交易進行比較忘古,如果一樣,說明layer1上的交易是正確的诅诱,如果不一樣髓堪,則需要layer1上的驗證者去做驗證。這是verifier的功能娘荡。

l2geth是另一個角 色是Sequencer,他是把data-transport-layer中偵聽到的quence的交易干旁,提交到layer2中打包。

然后batch-submitter獲取區(qū)塊的stateroot再提交到layer1的SCC chain中炮沐。**
這塊邏輯有點繞争群。需要慢慢理解。大年。

message-relayer

這是一個 中繼服務换薄,是將layer2中的提現(xiàn)交易中繼到layer1上。其實現(xiàn)過程翔试,就是利用rpc接口偵聽layer2的SentMessages事件专控,這個事件就是跨域轉(zhuǎn)賬或其他跨域消息。然后,relayer偵聽到這個事件后遏餐,會根據(jù)事件的參數(shù)伦腐。在layer1上調(diào)用OVM_L1CrossDomainMessenger的relayMessage方法,進行relay.然后就會到相應的合約上執(zhí)行相應的方法失都。以達到跨域轉(zhuǎn)賬的目的.

我們先介紹這幾個主要的模塊代碼柏蘑。希望以大家理解有幫助。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粹庞,一起剝皮案震驚了整個濱河市咳焚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌庞溜,老刑警劉巖革半,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異流码,居然都是意外死亡又官,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門漫试,熙熙樓的掌柜王于貴愁眉苦臉地迎上來六敬,“玉大人,你說我怎么就攤上這事驾荣⊥夤梗” “怎么了普泡?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長审编。 經(jīng)常有香客問我撼班,道長,這世上最難降的妖魔是什么垒酬? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任砰嘁,我火速辦了婚禮,結(jié)果婚禮上伤溉,老公的妹妹穿的比我還像新娘。我一直安慰自己妻率,他們只是感情好乱顾,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宫静,像睡著了一般走净。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孤里,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天伏伯,我揣著相機與錄音,去河邊找鬼捌袜。 笑死说搅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的虏等。 我是一名探鬼主播弄唧,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霍衫!你這毒婦竟也來了候引?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤敦跌,失蹤者是張志新(化名)和其女友劉穎澄干,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柠傍,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡麸俘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了惧笛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疾掰。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖徐紧,靈堂內(nèi)的尸體忽然破棺而出静檬,到底是詐尸還是另有隱情炭懊,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布拂檩,位于F島的核電站侮腹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稻励。R本人自食惡果不足惜父阻,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望望抽。 院中可真熱鬧加矛,春花似錦、人聲如沸煤篙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辑奈。三九已至苛茂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸠窗,已是汗流浹背妓羊。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留稍计,地道東北人躁绸。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像臣嚣,于是被迫代替她去往敵國和親涨颜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容