Optimism OVM

OVM ( Optimistic Virtual Machine)

OVM是相對于EVM的概念,是Layer2上交易的執(zhí)行環(huán)境,但既然Layer2上也有EVM,為什么還要做個OVM呢?

作用 (欺詐證明)

這是因為Layer2上的交易最終要把交易的執(zhí)行狀態(tài)存儲在layer1上(SCC Chain 合約)。但為了防止有人做惡,要驗證這個交易的狀態(tài)是否正確。就需要在Layer1上 重新運行這個Layer2的交易(重放交易)。所以Layer2上的chainID要和Layer1上的chainID要保持一致。

OVM OPCODE

為了保持這個交易在Layer2上和Layer1上運行的結(jié)果一樣,就需要修改EVM上的某些OpCode,,例如加載或存儲狀態(tài)或獲取當前時間戳,則它們在L1和L2上的行為將不同。例如:

L2交易調(diào)用TIMESTAMP 操作碼,例如返回1610889676, 一個小時后,如果我要在layer1上驗證這個交易,交易都必須在以太坊L1上重放,如果繼續(xù)使用EVM 執(zhí)行這個OPCODE的話,則返回的是 1610889676 +3600 。這就會導致兩次執(zhí)行的交易結(jié)果不一致。達不到 驗證交易的效果。如果使用OVM的TIMESTAMP,則可以返回
1610889676 。因為OVM重寫這個操作碼后,返回的是當時這個交易的timestamp.這就保證一致性了。這個TIMESTAMP OPCODE在OVM叫 ovmTIMESTAMP。

所有的這種與當時環(huán)境相關(guān)的opcde會改為ovm{opcode}

這些不安全操作碼包括下面的操作,如果EVM中沒有實現(xiàn)的操作符也是不安全因素。也是不允許的。

The following opcodes are disallowed:

ADDRESS
BALANCE
ORIGIN
EXTCODESIZE
EXTCODECOPY
EXTCODEHASH
BLOCKHASH
COINBASE
TIMESTAMP
NUMBER
DIFFICULTY
GASLIMIT
GASPRICE
CREATE
CREATE2
CALLCODE
DELEGATECALL
STATICCALL
SELFDESTRUCT
SELFBALANCE
SSTORE
SLOAD
CHAINID
CALLER*
CALL*
REVERT*
* The CALLER, CALL, and REVERT opcodes are also disallowed, except in the special case that they appear as part of one of the following strings of bytecode:

CALLER PUSH1 0x00 SWAP1 GAS CALL PC PUSH1 0x0E ADD JUMPI RETURNDATASIZE PUSH1 0x00 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x00 REVERT JUMPDEST RETURNDATASIZE PUSH1 0x01 EQ ISZERO PC PUSH1 0x0a ADD JUMPI PUSH1 0x01 PUSH1 0x00 RETURN JUMPDEST
CALLER POP PUSH1 0x00 PUSH1 0x04 GAS CALL

OPCODE驗證

那如何驗證某個合約是否包含“非法”的opcode呢?在OVM 中實現(xiàn)了 OVM_SafetyChecker.sol,里面的isBytecodeSafe方法會在create布署合約的時候進行opcode的檢查。如果包含非法操作符則return 0.否則返回1.

OVM編譯器

既然OVM有單獨的opcode,但我們又是用solidity寫的合約。所以為了和EVM的opcode進行區(qū)分。Optimism自己實現(xiàn)了個OVM編譯器。 https://github.com/ethereum-optimism/solidity

Layer2 客戶端 (Optimistic Geth)

因為我們要操作laye2,比如,創(chuàng)建和發(fā)送交易??傄袀€入口。為了更好的利用現(xiàn)有的資源,otimistic 就直接使用了以太坊最流行的客戶端(go-ethereum). 我們叫做 L2Geth,在L2Geth中執(zhí)行交易的過程和在Layer1上執(zhí)行的過程是一樣的。不一樣的是交易的結(jié)構(gòu)進行了修改,從下面的代碼中可以看到增加了TransactionMeta這個 關(guān)于當前Layer1的信息。

type Transaction struct {
    data txdata
    meta TransactionMeta
    // caches
    hash atomic.Value
    size atomic.Value
    from atomic.Value
}

type TransactionMeta struct {
    L1BlockNumber     *big.Int          `json:"l1BlockNumber"`
    L1Timestamp       uint64            `json:"l1Timestamp"`
    L1MessageSender   *common.Address   `json:"l1MessageSender" gencodec:"required"`
    SignatureHashType SignatureHashType `json:"signatureHashType" gencodec:"required"`
    QueueOrigin       *big.Int          `json:"queueOrigin" gencodec:"required"`
    // The canonical transaction chain index
    Index *uint64 `json:"index" gencodec:"required"`
    // The queue index, nil for queue origin sequencer transactions
    QueueIndex     *uint64 `json:"queueIndex" gencodec:"required"`
    RawTransaction []byte  `json:"rawTransaction" gencodec:"required"`
}

通過這些信息,如果這個交易是從Layer1傳過來的,則會由Sequener進行填充。如果是直接在Layer2上創(chuàng)建,則在 batch_submitter提交的時候,根據(jù)當 前Layer1的信息進行填充。

  private async _getBlock(blockNumber: number): Promise<L2Block> {
    const block = (await this.l2Provider.getBlockWithTransactions(
      blockNumber
    )) as L2Block
    // Convert the tx type to a number
    block.transactions[0].txType = txTypePlainText[block.transactions[0].txType]
    block.transactions[0].queueOrigin =
      queueOriginPlainText[block.transactions[0].queueOrigin]
    // For now just set the l1BlockNumber based on the current l1 block number
    if (!block.transactions[0].l1BlockNumber) {
      block.transactions[0].l1BlockNumber = this.lastL1BlockNumber
    }

    return block
  }
  

L2Geth打包交易后,會被batch_submitter 每隔一段時間,將 一批交易組成batch 交易,提交到layer1的CTC Chain合約中。調(diào)用的CTC Chain的合約方法是:appendSequencerBatch

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容