Optimsim Rollup詳解

Optimism Rollup是目前最流行的以太坊L2解決方案。本文將解釋Optimism Rollup每個設計決策背後的動機,剖析Optimism的系統實現,並提供指向每個分析組件的相應代碼的鏈接,適用於希望瞭解Optimism解決方案的工作原理並評估所提議系統的性能和安全性的開發人員。

區塊鏈開發教程鏈接:以太坊 | 比特幣 | EOS | Tendermint | Hyperledger Fabric | Omni/USDT | Ripple | Tron

1、軟件重用原則在Optimism Rollup中的重要性

以太坊已經圍繞其開發者生態系統發展了護城河。開發人員的技術棧包括:

  • Solidity/ Vyper:這是兩種最流行的智能合約編程語言,有很多工具鏈圍繞它們構建,例如 EthersHardhatdappslither等。
  • 以太坊虛擬機:最流行的區塊鏈虛擬機,其內部設計比任何其他區塊鏈VM都要好得多。
  • Go-ethereum:主流以太坊協議實現,採用率 > 75%,經過了廣泛的測試。

由於Optimism Rollup將以太坊作爲其第1層,因此如果我們可以無需修改即可重用現有工具,那就太好了。這將改善開發人員的體驗,因爲開發人員無需學習新技術。雖然已經多次提出,但是我想強調軟件重用的另一個含義:安全性。

2、Optimistic虛擬機

Optimism Rollup依賴於使用欺詐證明來防止發生無效的狀態轉換。這需要在以太坊上執行Optimsim交易。簡而言之,如果交易結果存在爭議,例如修改了Alice的ETH餘額,Alice將嘗試在以太坊上重放該確切的交易,以證明那裏的結果是正確的。但是,如果某些EVM操作碼依賴於系統範圍內的參數,這些參數可能隨時都會改變,例如加載或存儲狀態或獲取當前時間戳,則它們在L1和L2上的行爲將不同。

因此,Optimsim的第一個技術,就是處理L1上的L2爭端的機制,該機制保證可以重現在L1上執行L2事務時存在的任何“上下文”,並且在理想情況下不引入太多開銷。

目標是實現一個沙盒環境,可確保在L1和L2之間確定性地執行智能合約。

Optimism的解決方案是Optimistic虛擬機。OVM是通過將上下文相關的EVM操作碼替換爲其對應的OVM操作碼來實現的。

一個簡單的例子是:

  • L2交易調用TIMESTAMP操作碼,例如返回1610889676
  • 一個小時後,由於某種原因,交易都必須在以太坊L1上重放
  • 如果要在EVM中正常執行該交易,則TIMESTAMP操作碼將返回1610889676 +3600。這不是我們希望的,因爲這將導致交易執行上下文的變化。
  • 在OVM中,在L2上執行交易時,TIMESTAMP操作碼將替換爲ovmTIMESTAMP,因此將顯示正確值的操作碼。

所有與上下文相關的EVM操作碼在OVM核心合約在ExecutionManager中都有一個對應的ovm{OPCODE}。合約的執行是從EM的入口點run函數開始的。這些操作碼也已修改爲可以與可插拔狀態數據庫交互,其作用我們將在“欺詐證明”部分中進行介紹。

某些在OVM中“無意義”的操作碼會通過Optimism的SafetyChecker合約禁用,Optimism合約採用靜態分析技術,可以有效地判斷合約是否OVM安全並返回1或0。

請查閱附錄部分以瞭解每個被修改/禁用的EVM操作碼。

Optimism Rollup看起來像這樣:

在這裏插入圖片描述

上圖中問號標註的組件將在下面的欺詐證明部分說明,但在此之前,我們需要進一步解釋一些基礎知識。

3、Optimisitic Solidity編譯器

現在我們有了OVM沙箱,接下來要做的就是將智能合約編譯爲OVM字節碼。下面是一些可選的方案:

  • 發明一種新的可以編譯爲OVM的智能合約語言:這個思路很容易被放棄,因爲它需要從頭開始重新做所有事情,而且 我們已經就這一點達成一致,即儘可能重用已有的技術棧。
  • 將EVM字節碼轉換爲OVM字節碼:已嘗試但由於複雜性而被放棄。
  • 修改Solidity和Vyper編譯器以生成OVM字節碼。

Optimism當前使用的方法是第三種,Optimsim更改了socl大約500行代碼。

Solidity編譯器的工作原理是將Solidity轉換爲Yul,然後轉換爲EVM指令,最後轉換爲字節碼。Optimism所做的更改既簡單又優雅:對於每個操作碼,在編譯爲EVM彙編後,如有必要,嘗試以ovm變體“重寫”它(如果被禁止則拋出錯誤)。

解釋起來有點複雜,下面讓我們比較一個簡單合約的EVM和OVM字節碼:

在這裏插入圖片描述

用solc編譯一下:

$ solc C.sol --bin-runtime --optimize --optimize-runs 200
6080604052348015600f57600080fd5b506004361060285760003560e01c8063c298557814602d575b600080fd5b60336035565b005b60008054600101905556fea264697066735822122001fa42ea2b3ac80487c9556a210c5bbbbc1b849ea597dd6c99fafbc988e2a9a164736f6c634300060c0033

我們可以反彙編此代碼看一下得到的彙編代碼,括號內表示Program Counter:

...
[025] 35 CALLDATALOAD
...
[030] 63 PUSH4 0xc2985578 // id("foo()")
[035] 14 EQ
[036] 60 PUSH1 0x2d // int: 45
[038] 57 JUMPI // jump to PC 45
...
[045] 60 PUSH1 0x33
[047] 60 PUSH1 0x35 // int: 53
[049] 56 JUMP // jump  to PC 53
...
[053] 60 PUSH1 0x00
[055] 80 DUP1
[056] 54 SLOAD // load the 0th storage slot
[057] 60 PUSH1 0x01
[059] 01 ADD // add 1 to it
[060] 90 SWAP1
[061] 55 SSTORE // store it back
[062] 56 JUMP
...

上述彙編代碼的意思是,如果calldata匹配函數foo()的選擇器,則使用SLOAD操作碼載入0x00處的存儲變量,加上0x01,最後將結果使用SSTORE操作碼存回去。聽起來不錯!

在OVM中看起來如何?首先用修改後的solc編譯:

$ osolc C.sol --bin-runtime --optimize --optimize-runs 200
60806040523480156100195760008061001661006e565b50505b50600436106100345760003560e01c8063c298557814610042575b60008061003f61006e565b50505b61004a61004c565b005b6001600080828261005b6100d9565b019250508190610069610134565b505050565b632a2a7adb598160e01b8152600481016020815285602082015260005b868110156100a657808601518282016040015260200161008b565b506020828760640184336000905af158601d01573d60011458600c01573d6000803e3d621234565260ea61109c52505050565b6303daa959598160e01b8152836004820152602081602483336000905af158601d01573d60011458600c01573d6000803e3d621234565260ea61109c528051935060005b60408110156100695760008282015260200161011d565b6322bd64c0598160e01b8152836004820152846024820152600081604483336000905af158601d01573d60011458600c01573d6000803e3d621234565260ea61109c5260008152602061011d56

得到的字節碼更長了,讓我們再次反彙編一下,看看有什麼變化:

...
[036] 35 CALLDATALOAD
...
[041] 63 PUSH4 0xc2985578 // id("foo()")
[046] 14 EQ
[047] 61 PUSH2 0x0042
[050] 57 JUMPI // jump to PC 66
...
[066] 61 PUSH2 0x004a
[069] 61 PUSH2 0x004c // int: 76
[072] 56 JUMP // jump to PC 76

這一部分還是檢查是否匹配指定的函數選擇器,讓我們看看之後會發生什麼。

...
[076] 60 PUSH1 0x01 // Push 1 to the stack (to be used for the addition later)
[078] 60 PUSH1 0x00
[080] 80 DUP1
[081] 82 DUP3
[082] 82 DUP3
[083] 61 PUSH2 0x005b
[086] 61 PUSH2 0x00d9 (int: 217)
[089] 56 JUMP // jump to PC 217
...
[217] 63 PUSH4 0x03daa959       // <---|  id("ovmSLOAD(bytes32)")
[222] 59 MSIZE                  //     |                                       
[223] 81 DUP2                   //     |                                       
[224] 60 PUSH1 0xe0             //     |                                       
[226] 1b SHL                    //     |                                       
[227] 81 DUP2                   //     |                                       
[228] 52 MSTORE                 //     |                                       
[229] 83 DUP4                   //     |                                       
[230] 60 PUSH1 0x04             //     | CALL to the CALLER's ovmSLOAD
[232] 82 DUP3                   //     |                                       
[233] 01 ADD                    //     |                                       
[234] 52 MSTORE                 //     |                                       
[235] 60 PUSH1 0x20             //     |                                       
[237] 81 DUP2                   //     |  
[238] 60 PUSH1 0x24             //     |                                     
[240] 83 DUP4                   //     |                                       
[241] 33 CALLER                 //     |                                       
[242] 60 PUSH1 0x00             //     |                                       
[244] 90 SWAP1                  //     |                                       
[245] 5a GAS                    //     |                                       
[246] f1 CALL                   // <---|

[247] 58 PC                     // <---|  
[248] 60 PUSH1 0x1d             //     |                                       
[250] 01 ADD                    //     |                                       
[251] 57 JUMPI                  //     |                                       
[252] 3d RETURNDATASIZE         //     |                                       
[253] 60 PUSH1 0x01             //     |                                       
[255] 14 EQ                     //     |                                       
[256] 58 PC                     //     |                                       
[257] 60 PUSH1 0x0c             //     |                                       
[259] 01 ADD                    //     |                                       
[260] 57 JUMPI                  //     |  Handle the returned data             
[261] 3d RETURNDATASIZE         //     |                                       
[262] 60 PUSH1 0x00             //     |                                       
[264] 80 DUP1                   //     |                                       
[265] 3e RETURNDATACOPY         //     |                                       
[266] 3d RETURNDATASIZE         //     |                                       
[267] 62 PUSH3 0x123456         //     |                                       
[271] 52 MSTORE                 //     |                                       
[272] 60 PUSH1 0xea             //     |                                       
[274] 61 PUSH2 0x109c           //     |                                       
[277] 52 MSTORE                 // <---|          

上面代碼包含很多操作,要點在於這裏不是使用SLOAD操作碼,而是構造一個棧以便執行CALL操作碼。調用的接收者通過CALLER操作碼被壓入棧。每一個調用都是來自EM,因此實際上CALLER是調用EM的有效方法。調用的數據以ovmSLOAD(bytes32)函數的選擇器開頭,接下來是參數(在這個示例中,就是佔用32字節的字)。之後,將處理返回的數據並將其添加到內存中。

讓我們繼續:

...
[297] 82 DUP3
[298] 01 ADD // Adds the 3rd item on the stack to the ovmSLOAD value
[299] 52 MSTORE
[308] 63 PUSH4 0x22bd64c0  // <---| id("ovmSSTORE(bytes32,bytes32)")
[313] 59 MSIZE             //     |                                                           
[314] 81 DUP2              //     |                                                            
[315] 60 PUSH1 0xe0        //     |                                                                  
[317] 1b SHL               //     |                                                           
[318] 81 DUP2              //     |                                                            
[319] 52 MSTORE            //     |                                                              
[320] 83 DUP4              //     |                                                            
[321] 60 PUSH1 0x04        //     |                                                                  
[323] 82 DUP3              //     |                                                            
[324] 01 ADD               //     |  CALL to the CALLER's ovmSSTORE
[325] 52 MSTORE            //     |  (RETURNDATA handling is omited
[326] 84 DUP5              //     |   because it is identical to ovmSSLOAD)
[327] 60 PUSH1 0x24        //     |                                                                  
[329] 82 DUP3              //     |                                                            
[330] 01 ADD               //     |                                                           
[331] 52 MSTORE            //     |                                                              
[332] 60 PUSH1 0x00        //     |                                                                  
[334] 81 DUP2              //     |                                                            
[335] 60 PUSH1 0x44        //     |                                                                  
[337] 83 DUP4              //     |                                                            
[338] 33 CALLER            //     |                                                              
[339] 60 PUSH1 0x00        //     |                                                                  
[341] 90 SWAP1             //     |                                                             
[342] 5a GAS               //     |                                                           
[343] f1 CALL              // <---|                                                            
...

類似於將SLOAD調整到外部調用ovmSLOAD,SSTORE也調整到外部調用ovmSSTORE。調用的數據不同,因爲ovmSSTORE需要兩個參數,即存儲插槽和要存儲的值。下面是兩者的比較:

在這裏插入圖片描述

實際上,我們先調用Execution Manager的ovmSLOAD方法,然後再調用其ovmSTORE方法,而不是SLOAD和SSTORE。

通過比較EVM與OVM的執行(我們僅顯示執行的SLOAD一部分),我們可以看到通過Execution Manager進行的虛擬化:

在這裏插入圖片描述

這種虛擬化技術有一個“陷阱”:

會導致更快達到合約大小上限 :通常,以太坊合約的字節碼最大24KB 。使用Optimistic Solidity Compiler編譯的合約最終比原來大,這意味着必須重構接近24KB限制的合約,以便其OVM大小仍適合24KB限制,因爲它們需要在以太坊主網上執行。

4、Optimistic Geth

以太坊最流行的實現是go-ethereum(即geth)。讓我們看看通常如何在Geth中執行交易。

在每個塊上,調用狀態處理器的Process方法,該方法對每個交易執行ApplyTransaction方法。在內部,交易被轉換爲 消息,消息被應用於當前狀態,最後將新產生的狀態存儲回數據庫中。

此核心數據流在Optimistic Geth上保持不變,但進行了一些修改以保持交易“對OVM友好”:

修改1:通過Sequencer入口點的OVM消息

交易被轉換爲OVM消息。由於除去了消息的簽名,因此消息數據被修改爲包括交易簽名以及原始交易的其餘字段。to字段將替換爲“Sequencer入口點”合約的地址。這樣做是爲了使交易格式緊湊,因爲它將被髮布到以太坊,並且我們已經確定,越緊湊伸縮性就越好。

修改2:通過執行管理器的OVM沙箱

爲了通過OVM沙箱運行交易,必須將它們發送到Execution Manager的run 功能。不要求用戶僅提交符合該限制的交易,所有消息都被修改爲在內部發送到Execution Manager。這裏很簡單:消息的to字段被替換爲執行管理器的地址,並且消息的原始數據被打包爲參數傳入run。

這可能有點不直觀,因此我們提供了代碼以給出一個具體示例

修改3:攔截對狀態管理器的調用

StateManager是一個特殊的合約,在Optimistic Geth 上並不存在。僅在欺詐證明期間部署它。細心的讀者會注意到當打包參數以進行run調用時,Optimism的geth還將打包一個硬編碼的State Manager地址。這就是最終被用作任何ovmSSTORE或ovmSLOAD(或類似)調用的最終目的地的原因。在L2上運行時,以State Manager合約爲目標的所有消息都將被攔截,並且它們被連接爲直接與Geth的StateDB對話(或不執行任何操作)。

對於尋求整體代碼更改的人們來說,最好的方法是搜索UsingOVM並比較geth 1.9.10的差異。

修改4:基於epoch的批次而不是塊

OVM沒有塊,它僅維護交易的有序列表。因此,沒有區塊gas限制的概念;取而代之的是,根據時間段(稱爲epoch)限制總的gas消耗率。在執行交易之前,要檢查是否需要啓動一個新的epoch,在執行之後,將其gas小號添加到該epoch所使用的累積gas用量上。對於Equenecer提交的交易和“ L1至L2”交易,每個epoch都有單獨的gas限制。任何超過gas限值的交易將提前返回。這意味着操作員可以在一個鏈上批次中發佈多個具有不同時間戳的交易(時間戳由Sequencer定義,但有一些限制,我們將在“數據可用性批處理”部分中說明)。

修改5:Rollup同步服務

該同步服務是一個新的進程運行,它與“正常” GETH同時運行。Rollup同步服務負責監視以太坊日誌,對其進行處理,並通過geth的worker注入要在L2狀態下應用的相應L2交易。

5、Optimistic Rollup

Optimistic Rollup的主要特性包括:

  • OVM作爲其運行時/狀態遷移函數
  • 擁有單個Sequencer的Optimistic Geth作爲L2客戶端
  • 在以太坊上部署的Solidity智能合約用於:
    • 數據可用性
    • 爭議解決和欺詐證明,我們將深入研究實現數據可用性層的智能合約,並探索端到端的欺詐證明流程。

數據可用性批次

如前所述,交易數據被壓縮,然後發送到L2上的Sequencer Entrypoint合約。然後,Sequencer負責“彙總”這些交易,並在以太坊上發佈數據,提供數據可用性,以便即使Sequencer消失了,也可以啓動新的Sequencer以從中斷的地方繼續。

依靠以太坊實現該邏輯的智能合約稱爲權威交易鏈(CTC:Canonical Transaction Chain)。權威交易鏈是一個追加型日誌,它代表Rollup鏈的“正式歷史”(所有交易以及其順序)。交易可以由Sequencer等提交給CTC。爲了保留L1的抗審查能力,任何人都可以將交易提交到此隊列,並在一定滯後期之後將其包括在CTC中。

CTC爲每批發布的L2交易提供數據可用性。可以通過兩種方式創建批處理:

  • 預計每隔幾秒鐘,Sequencer就會檢查接收到的新交易,將它們分批匯總,以及所需的任何其他元數據。然後,他們 利用appendSequencerBatch將該數據發佈到以太坊。這是由批處理提交者服務自動完成的。
  • 當Sequencer審查其用戶或當用戶執行從L1到L2的交易,用戶需要調用enqueue和appendQueueBatch,這會強制在CTC中 包含交易

這裏的一個極端情況是:如果Sequencer廣播了一個批次,則用戶可以強制包含涉及與該批次衝突的狀態的交易,從而可能使該批次的某些交易無效。爲了避免這種情況,我們引入了時間延遲,在此延遲之後可以由非Sequencer帳戶將批處理追加到隊列中。對此進行考慮的另一種方法是,給利用appendeSequencerBatcher 添加的交易一個“寬限期”,否則用戶使用appendQueueBatch。

鑑於大多數交易預計將通過Sequencer提交,因此有必要深入研究批處理結構和執行流程。

你可能會注意到,appendSequencerBatch沒有任何參數。批次以緊密打包的格式提交,而使用ABI編碼和解碼則效率要低得多。它使用內聯彙編來對calldata進行切片,並以預期的格式將其解壓縮。

一個批次由以下部分組成:

  • 批次頭
  • 批處理上下文(> = 1,請注意:此上下文與我們在上面的“ OVM”部分中提到的消息/交易/全局上下文不同)
  • 交易(> = 1)

在這裏插入圖片描述

批次頭指定了上下文的數量,因此序列化的批處理看起來像是 [header, context1, context2, …, tx1, tx2, ... ]

該函數繼續執行以下兩項操作:

  • 驗證所有與上下文相關的不變量是否適用
  • 根據已發佈的交易數據創建默克爾樹

如果通過了上下文驗證,則該批次將轉換爲OVM鏈批次頭,然後將其存儲在CTC中。

存儲的批次頭包含該批次的merkle根,這意味着證明已包含交易是提供針對針對CTC中存儲的merkle根進行驗證的merkle證明的簡單問題。

這裏的自然問題是:這似乎太複雜了!爲什麼需要上下文?

上下文對於Sequencer來說是必要的,以便知道是否應在已排序交易之前或之後執行已排隊的交易。讓我們來看一個例子:

在時間T1,Sequencer已接收到2個交易,它們將包括在其批次中。在T2(> T1)用戶也排隊的交易時,將它添加到L1到L2交易隊列(但不將其添加到批次!)。在T2,Sequencer又接收到1個交易,另外2個交易也入隊列。換句話說,待處理交易的批處理看起來像:

[(sequencer, T1), (sequencer, T1), (queue, T2), (sequencer, T2), (queue, T3), (queue, T4)]

爲了保持時間戳和塊號信息,同時又保持序列化格式的緊湊性,我們使用了“上下文”,即Sequencer和排隊交易之間的共享信息集合。上下文必須嚴格增加塊數和時間戳。在上下文中,所有Sequencer交易共享相同的塊號和時間戳。對於“隊列交易”,將時間戳和塊號設置爲調用隊列時的值。在這種情況下,該批交易的上下文爲:

[{ numSequencedTransactions: 2, numSubsequentQueueTransactions: 1, timestamp: T1}, {numSequencedTransactions: 1, numSubsequentQueueTransactions: 2, timestamp: T2}]

狀態承諾

在以太坊中,每個交易都會導致對狀態以及全局狀態根的修改。通過在某個區塊提供狀態根並通過默克爾證明來證明某個帳戶在某個區塊擁有一些ETH,以證明該賬戶的狀態與所聲明的值匹配。因爲每個塊包含多個交易,並且我們只能訪問狀態根,所以這意味着我們只能在執行整個塊後才聲明狀態。

一段歷史:

在EIP98和Byzantium分叉之前,以太坊交易在每次執行後產生中間狀態根,這些根通過交易收據提供給用戶刪除中間狀態根能夠提高性能,雖然有一點小缺陷,因此很快就採用了它。EIP PR658中提供的其他動機解決了該問題:收據的PostState字段(指示與tx執行後的狀態相對應的狀態根)被布爾狀態字段(指示交易的成功狀態)替換。

事實證明,警告並非無關緊要。EIP98寫道:

所做的更改確實意味着,如果礦工創建了一個區塊,其中一個狀態轉換的處理不正確,那麼就不可能針對該交易 提供欺詐證明;相反,欺詐證明必須包含整個區塊。

此更改的含義是,如果一個區塊有1000個交易,並且你在第988個交易中檢測到欺詐,則在實際執行你感興趣的交易之前,需要在前一個區塊的狀態之上運行987個交易,這會使欺詐證明效率極低。以太坊本身沒有欺詐證明,所以沒關係!

另一方面,Optimism的欺詐證據是至關重要的。在前面,我們提到Optimism沒有區塊,那只是個小謊言:Optimism有區塊,但是每個區塊只有1個交易,我們稱之爲“微區塊”。由於每個微塊包含1個交易,因此每個塊的狀態根實際上是單個交易產生的狀態根。烏拉!我們已經重新引入了中間狀態根,而不必對協議進行任何重大更改。當然,由於微塊在技術上仍然是塊並且包含冗餘的其他信息,因此當前當然具有恆定的性能開銷,但是這種冗餘可以在將來刪除(例如,使所有微塊都具有0x0作爲塊哈希,並且僅填充RPC中的修剪字段以便向後兼容)。

現在,我們可以介紹狀態承諾鏈(SCC:State Commitment Chain)。SCC包含狀態根列表,在樂觀情況下,該列表對應於針對先前狀態在CTC中應用每個交易的結果。如果不是這種情況,則欺詐驗證過程將刪除無效的狀態根,然後刪除所有無效的狀態根,以便可以爲這些交易提出正確的狀態根。

與CTC相反,SCC沒有任何酷炫的數據表示形式。它的目的很簡單:給定狀態根列表,它會對其進行存儲並保存批處理中包含的中間狀態根的merkle根,以供以後通過appendStateBatch用作欺詐證明。

欺詐證明

既然我們瞭解了OVM的基本概念以及將其狀態錨定在以太坊上的支持功能,那麼讓我們深入探討爭端解決程序,也就是欺詐證明。

Sequencer執行3件事:

  • 接收用戶提交的交易
  • 批量彙總這些交易並將其發佈在權威交易鏈中
  • 在狀態承諾鏈中將交易產生的中間狀態根發佈爲狀態批。

例如,如果在CTC中發佈了8個交易,則對於每個狀態從S1到S8的轉換,在SCC中都會有8個狀態根。

在這裏插入圖片描述

但是,如果Sequencer是惡意的,他們可以在狀態Trie中將其帳戶餘額設置爲1000萬個ETH,這顯然是非法的操作,從而使狀態根及其後面的所有狀態根均無效。他們可以通過發佈看起來像這樣的數據來做到這一點:

在這裏插入圖片描述

我們註定要失敗嗎?我們必須做點什麼!

衆所周知,Optimistic Rollup假定存在驗證者:對於Sequencer發佈的每個交易,驗證者負責下載該交易並將其應用於本地狀態。如果一切都匹配,它們什麼也不做,但是如果不匹配,那就有問題了!爲了解決該問題,他們將嘗試在以太坊上重新執行T4以產生S4。然後,將修剪所有在S4之後發佈的狀態根,因爲無法保證它對應於有效狀態:

在這裏插入圖片描述

從較高層面來說,欺詐證明是“以S3作爲我的開始狀態,我想證明在S3上應用T4會導致S4,這與Sequencer發佈的內容不同(😈)。結果,我希望刪除S4及其之後的所有內容。”

如何實施?

在圖1中看到的是OVM在L2中以其“簡單”執行模式運行。在L1上運行時,OVM處於防欺詐模式,並且啓用了它的更多組件(在L1和L2上都部署了Execution Manager和Safety Checker ):

  • 欺詐驗證者:負責協調整個欺詐證明驗證過程的合約。它調用的狀態遷移工廠來初始化一個新的欺詐證據, 如果證據造假成功,它將修剪這是從狀態承諾鏈的爭議點之後發佈的任何批次。
  • State Transitioner(狀態轉換程序):當使用前置狀態的根創建爭議並且有爭議的交易時,由欺詐驗證程序部署。 其職責是調出執行管理器,並根據規則忠實地執行鏈上交易,以爲有爭議的交易產生正確的事後狀態根源。成功 執行的欺詐證明將導致狀態轉移器中的後狀態根與狀態承諾鏈中的狀態根不匹配。狀態轉換器可以處於以下3種狀態 中的任何一種:PRE EXECUTION, POST EXECUTION, COMPLETE。
  • 狀態管理器:用戶提供的任何數據都存儲在此處。這是一個“臨時”狀態管理器,僅部署用於欺詐證明,並且僅包含 有關有爭議的交易涉及的狀態的信息。

在防欺詐模式下運行的OVM如下所示:

在這裏插入圖片描述

欺詐證明分爲幾個步驟:

步驟1:聲明您要爭議的狀態轉換

  1. 用戶調用欺詐驗證者的initializeFraudVerification,提供狀態前的根(及其在狀態承諾鏈中的證明)和有爭議的 交易(及其在交易鏈中的證明)。
  2. State Transitioner合約是通過State Transitioner工廠部署的。
  3. 通過狀態管理工廠部署狀態管理合約。它不會包含整個L2狀態,而是僅填充交易所需的部分;你可以將其視爲“部分狀態管理員”。

State Transitioner現在處於PRE EXECUTION階段。

在這裏插入圖片描述

步驟2:上傳所有交易狀態

如果我們嘗試直接執行有爭議的交易,則該交易將立即失敗,並顯示INVALID_STATE_ACCESS錯誤,因爲從步驟1開始,在剛部署的L1狀態管理器上未加載任何涉及的L2狀態。OVM沙箱將檢測是否SM尚未填充某些觸摸狀態,並強制首先加載所有觸摸狀態需求。

例如,如果有爭議的交易是簡單的ERC20代筆轉移,則初始步驟爲:

  1. 在L1、L2上部署ERC20 :L2和L1合約的字節代碼必須匹配,才能在L1和L2之間執行相同的操作。我們保證在字節碼 前加一個“魔術”前綴,將其複製到內存中並存儲在指定地址。
  2. 調用proveContractState:這會將L2 OVM合約與新部署的L1 OVM合同鏈接在一起(合約已部署並鏈接,但仍未加載存儲)。鏈接是指將OVM地址用作映射中的鍵,其中值是包含合同帳戶狀態的結構。
  3. 調用proveStorageSlot:標準ERC20轉賬會減少發送者餘額,增加接收者的餘額。這將在執行交易之前上載接收方和發送方 的餘額。對於ERC20,餘額通常存儲在映射中,因此根據Solidity的存儲佈局,鍵將爲keccak256(slot + address)。

在這裏插入圖片描述

步驟3:一旦提供所有預狀態,請運行交易

然後,用戶必須通過調用State Transitioner的applyTransaction來觸發交易的執行。在此步驟中,執行管理器開始使用欺詐證明的狀態管理器執行交易。執行完成後,狀態轉換程序過渡到該POST EXECUTION階段。

在這裏插入圖片描述

步驟4:提供後期狀態

在L1上執行期間(步驟3),合同存儲位或帳戶狀態(例如,隨機數)中的值將更改,這將導致狀態轉換程序的後狀態根更改。但是,由於狀態轉換器/狀態管理器對不知道整個L2狀態,因此它們無法自動計算新的後狀態根。

爲了避免這種情況,如果存儲插槽或帳戶狀態的值發生更改,則將存儲插槽或帳戶標記爲“ changed”,並增加未提交的存儲插槽或帳戶的計數器。我們要求對於每個更改的項目,用戶還必須提供L2狀態的防彎證明,表明這確實是所觀察到的值。每次“提交”存儲插槽更改時,都會更新合約帳戶的存儲根目錄。在提交所有更改的存儲插槽後,合約的狀態也將提交從而更新過渡器的後狀態root。對於發佈的每個後期狀態數據,該計數器相應地遞減。

因此,可以預期,在交易中涉及的所有合約的狀態更改都已提交之後,結果後的狀態根是正確的。

在這裏插入圖片描述

步驟5:完成狀態轉換並最終確定欺詐證明

完成狀態轉換是一個簡單的completeTransition調用過程,它要求步驟4中的所有帳戶和存儲插槽都已提交(通過檢查未提交狀態的計數器等於0來進行)。

最後,在Fraud Verifier合約上調用finalizeFraudVerification,該合約檢查狀態轉換程序是否完成,如果是,則調用deleteStateBatch,該方法它繼續從SCC刪除(包括)有爭議的交易之後的所有狀態根批處理。CTC保持不變,因此原始交易將以相同順序重新執行。

在這裏插入圖片描述

激勵+債券

爲了使系統保持開放並無需許可,SCC旨在允許任何人成爲Sequencer併發布狀態批。爲避免SCC被垃圾數據淹沒,我們引入了1個限制:

Sequencer必須由債券管理器智能合約標記爲抵押品。你需要存入固定金額的抵押品,並且可以在7天后提取該金額。

但是,在抵押後,惡意的提議者可以反覆創建欺詐性的狀態根源,希望沒有人對此提出異議,從而使他們有錢。如果忽略用戶從Rollup和惡意Sequencer社交協調遷移的場景,那麼這裏的攻擊成本極低。

該解決方案在L2系統設計中是非常標準的:如果成功證明了欺詐,則X%的提議者的保證金會被燒掉13,剩餘的(1-X)%會按比例分配給每個爲第2步和第4步提供數據的用戶。現在,Sequencer的背叛成本要高得多,並且假設它們的行爲合理,則有望創造足夠的誘因來防止它們惡意行爲。即使有爭議狀態沒有直接影響他們,這也爲用戶提供了一個誘人的誘因,使他們提交數據以證明欺詐行爲。

nuisance gas

有一個單獨的gas維度,稱爲“有害gas”,用於限制欺詐證明的淨gas成本。特別是,L2 EVM gas成本表中未反映欺詐證明建立階段的證人數據(例如,默克爾證明)。ovmOPCODES針對nuisance gas需要另外付費,每當觸摸一個新的存儲槽或帳戶時,都會收取費用。如果消息嘗試使用超出消息上下文允許範圍的nuisance gas,則執行恢復。


原文鏈接:Optimism Rollup原理詳解 — 匯智網

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章