一文對比 Optimism Bedrock 和 Arbitrum Nitro 的設計差異

原文作者:OP Labs 研發人員 Norswap,本文由 DeFi 之道編譯

這是一篇有關 Optimism Bedrock 以及 Arbitrum Nitro‌ 之間設計差異的分析文章。

這一切都源於我對 Nitro 白皮書‌的閱讀,以及我對 Bedrock 設計的感性認識。

這變得非常技術性, 如果你想關注並感到困惑,我建議你參考一下 Bedrock 概述以及我關於 Cannon 故障證明系統的演示文稿‌,當然還有 Nitro 白皮書。

準備好了之後,讓我們開始吧!

一文對比 Optimism Bedrock 和 Arbitrum Nitro 的設計差異

首先,Nitro 白皮書很棒,讀起來令人愉快, 我建議所有感興趣的人都去看看。

說到這裡,我的印象是 Bedrock 和 Nitro 大致使用了相同的架構,但有一些較小的差異。

白皮書大體上證實了這一點。 儘管如此,還是有很多的不同之處,包括一些我沒想到的。這就是這篇文章要講的東西。

(A)固定與可變區塊時間

最有趣和最重要的事情之一是, Nitro 將像當前版本的 Optimism 一樣工作,每筆交易一個區塊,並且區塊之間的時間可變。

我們放棄了這一點,因為它背離了以太坊的工作方式,也是開發人員的痛點。而 Bedrock 將有“真正”的區塊,並且固定時間為 2 秒。

不規則的區塊時間使很多常見的合約變得不穩定,因為它們是使用區塊而不是時間戳來表示時間。這尤其包括源自 Sushiswap 的分配 LP 獎勵的 Masterchef 合約。

我不確定為什麼這些合約用區塊而不是時間戳來表示時間!以太坊礦工在操縱時間戳方面有一些迴旋餘地,但默認情況下,客戶端不會構建距離 wallclock (Geth 為 15 秒)太遠的區塊,所以沒有問題。

無論如何,在 Optimism 上,這導致 StargateFinance 獎勵比其他鏈提前幾個月用完,因為他們沒有考慮到這種特殊性!

“每筆交易一個區塊”模型還有其他的問題。首先,存儲鏈的開銷很大(每筆交易一個區塊頭)。其次,這意味着狀態根需要在每次交易后更新。

更新狀態根是一項非常昂貴的操作,其成本要在多筆 tx 中進行分攤。

(B) Geth 作為庫或作為執行引擎

Nitro 使用 Geth “作為一個庫”,通過鉤子(hooks)對其進行了最低限度的修改,以調用適當的功能。

在 Bedrock 中,一個經過最少修改的 Geth 作為“執行引擎”獨立運行,它從 rollup 節點接收指令,就像執行層從 Eth2 中的共識層接收指令一樣。 我們甚至使用完全相同的 API!

這有一些重要的影響。 首先,我們能夠使用除 Geth 之外的其他客戶端,在它們之上應用類似的最小差異。 這不僅僅是理論,我們已經準備好了 Erigon‌。

其次,這讓我們可以重用整個 Geth(或其他客戶端)堆棧,包括在網絡層,這可以實現對等發現和狀態同步等功能,而無需進行任何額外的開發工作。

(B) 狀態存儲

Nitro 將一些狀態(“ArbOS 的狀態”)保存在一個特殊帳戶中(它本身存儲在 Arbitrum 的鏈狀態中),使用特殊的內存布局將密鑰映射到存儲槽。

(這純粹是架構,對用戶沒有影響。)

從這個意義上說,Bedrock 並沒有太多的狀態,它只有很少的狀態存儲在普通 EVM 合約中(公平地說,你可以使用 EVM 實現 ArbOS 狀態布局,但我認為他們並不是這樣做的)。

在確定/執行下一個 L2 塊時,一個 Bedrock 副本會查看:

  1. L2 鏈頭部的區塊頭;
  2. 從 L1 讀取的數據;
  3. L2 鏈上 EVM 合約中的一些數據,目前只有 L1 費用參數;

在 Bedrock 中,節點可能會崩潰並立即優雅地重啟。它們不需要維護額外的數據庫,因為所有必要的信息都可以在 L1 和 L2 區塊中找到。我認為 Nitro 的工作原理是一樣的(架構使這成為可能)。

但很明顯, Nitro 比 Bedrock 做了更多的記賬工作。

(C) L1 到 L2 的消息包含延遲

Nitro 會延遲 10 分鐘處理 L1 到 L2 的消息(我們稱之為“存款交易”或簡稱“存款”)。在 Bedrock 上,通常應具有幾個區塊的小確認深度(可能是 10 個 L1 區塊,所以大約是 2 分鐘)。

我們也有一個稱為“排序器漂移”(sequencer drift)的參數,它允許 L2 區塊的時間戳在其 L1 原點之前漂移(L1 區塊標誌着 L1 區塊範圍的結束,批次和存款是從中派生的)。

我們仍然需要確定最終的數值,但我們也傾向於 10 分鐘,這意味着最壞的情況是 10 分鐘。然而,此參數旨在確保在與 L1 的連接暫時丟失期間 L2 鏈的活性。

然而,通常在確認深度後會立即包含存款。

Nitro 的白皮書中提到,這 10 分鐘的延遲是為了避免 L1 上的存款因重組而消失。這讓我對白皮書沒有談到的一個方面感到好奇,那就是:L2 鏈如何處理 L1 的重組。我認為答案是它沒有處理。

這並非不合理:合併后,L1 的最終性延遲大約是 12分鐘 。因此,如果存款延遲 10/12 分鐘是可接受的,那麼這個設計就是可行的。

因為 Bedrock 更接近 L1,我們需要在需要時通過重組 L2 來處理 L1 重組。確認深度應避免這種情況過於頻繁地發生。

另一個小的區別是,如果 Nitro 排序器在 10 分鐘后不包含存款,你可以通過 L1 合約調用“強制包含”它。

在 Bedrock 上,這不是必需的:擁有一個 L2 區塊而不包括其 L1 起源的存款是無效的。

並且由於 L2 只能比原點提前 10 分鐘(排序器漂移),因此 10 分鐘后不納入存款的一條鏈是無效的,它將被驗證器拒絕,並受到故障證明機制的挑戰。

(D) L1-to-L2 消息重試機制

Nitro 為 L1 到 L2 的消息實施了“可重試票證”(retryable tickets)機制。 假設你正在跨鏈,tx 的 L1 部分可以工作(鎖定你的代幣),但 L2 部分可能會失敗。 因此,你需要能夠重試 L2 部分(可能需要更多的 gas),否則你已經丟失了代幣。

Nitro 在節點的 ArbOS 部分實現了這一點。 在 Bedrock 中,這一切都是在 Solidity 本身中完成的。

如果你使用我們的 L1 跨域 messenger 合約向 L2 發送 tx,該 tx 會到達我們的 L2 跨域 messenger,後者將記錄其哈希值,使其可重試。 Nitro 的工作方式相同,只是在節點中實現。

我們還通過我們的 L1 Optimism Portal 合約,公開了一種較低 level 的存款方式。

這並沒有為你提供 L2 跨域 messenger 重試機制的安全網,但另一方面,這意味着你可以在 Solidity 中實現自己的應用程序特定重試機制。 這很酷!

(E) L2費用算法

在 Bedrock 以及 Nitro 這兩個系統上,費用都有 L2 部分(執行 gas,類似於以太坊)以及 L1 部分(L1 calldata 的成本)。 對於 L2 費用,Nitro 使用了一個定製系統,而 Bedrock 重複使用了 EIP-1559。 Nitro 必須這樣做,因為他們有上述提到的 1 tx/區塊 系統。

我們仍然需要調整 EIP-1559 參數,以使其在 2 秒的出塊時間內正常工作。 今天,Optimism 只收取低且固定的 L2 費用, 我認為我們可能也會出現價格飆升,但在實踐中從未發生過。

重用 EIP-1559 的一個優點是,它應該使錢包和其他工具計算費用稍微容易一些。

而 Nitro 的 gas 計量公式非常優雅,他們似乎已經對此進行了大量思考。

(F) L1 費用算法

那 L1 費用如何呢?這裡的區別會更大一些。 Bedrock 使用向後查看的 L1 基礎費用數據。這些數據非常新鮮,因為它通過與存款相同的機制傳遞(即幾乎是即時的)。

由於仍然存在 L1 費用飆升的風險,所以我們收取預期費用的一個小倍數。

有趣的事實:這個倍數(自啟動鏈以來我們已經多次降低)是所有當前排序器收入的來源!使用 EIP-4844 后,這將縮小,收入將來自 MEV 提取。

Nitro 做的事情要複雜得多。我並沒有聲稱了解它的所有複雜性,但基本要點是他們有一個控制系統,可以從 L1 實際支付的費用中獲得反饋。

這意味着使用此數據將交易從 L1 發送回 L2。如果排序器支付不足,它可以開始向用戶收取更少的費用。如果它多付了錢,它可以開始向用戶收取更多費用。

順便說一句,你可能想知道為什麼我們需要將費用數據從 L1 傳輸到 L2。這是因為我們希望費用計劃成為協議的一部分,並接受故障證明的挑戰。否則,流氓排序器可通過設置任意高的費用來拒絕鏈!

最後,交易批次在兩個系統中都被壓縮。 Nitro 根據對交易壓縮程度的估計收取 L1 費用。Bedrock 目前沒這樣做,但我們有這樣做的計劃。

原因在於,不這樣做,會加劇在 L2 存儲中緩存數據的不正當動機,從而導致有問題的狀態增長。

(G) 故障證明指令集

故障/欺詐證明! Nitro的工作方式與 Cannon(我們目前正在實施的位於 Bedrock 之上的防故障系統)的工作方式有相當多的差異。

Bedrock 編譯為 MIPS 指令集架構 (ISA),Nitro 編譯為 WASM。由於編譯為他們稱為 WAVM 的 WASM 子集,他們似乎對輸出進行了更多的轉換。

例如,他們通過庫調用替換浮點 (FP) 操作。我懷疑他們不想在鏈上解釋器中實現粗糙的 FP 操作。我們也這樣做,但 Go 編譯器會替我們處理!

另一個例子:與大多數只有跳轉的 ISA 不同,WASM 具有適當的(可能嵌套的)控制流(if-else、while 等)。從 WASM 到 WAVM 的轉換消除了這一點以返回跳轉,這可能也是為了解釋器的簡單性。

他們還將 Go、C 和 Rust 混合編譯為 WAVM(在不同的“模塊”中),而我們只編譯 Go。顯然 WAVM 允許“語言的內存管理不受干擾”,我將其解釋為每個 WAVM 模塊都有自己的堆。

我很好奇是:他們是如何處理併發和垃圾收集的。我們能夠在 minigeth(我們精簡的 geth)中相當容易地避免併發,所以這部分可能很簡單(本文末尾將詳細介紹 Bedrock 和 Nitro 如何使用 geth)。

然而,我們對 MIPS 所做的唯一轉換之一是修補垃圾收集調用。這是因為垃圾收集在 Go 中使用了併發,而併發和故障證明不能很好地結合在一起。 Nitro 也是做了同樣的事嗎?

(H) 二分博弈結構

Bedrock 故障證明將用於驗證發布到 L1 的狀態根(實際上是輸出根)的有效性的 minigeth 運行。此類狀態根不經常發布,並且包括許多區塊/批次的驗證。

Cannon 中的二分遊戲是在這個(長期)運行的執行軌跡上進行的。

另一方面,在 Nitro 中,狀態根與發布到 L1 的每組批次 (RBlock) 一起發布。

Nitro 中的二分遊戲分為兩部分。首先找到挑戰者和防禦者不同意的第一個狀態根。然後,在驗證器運行中找到他們不同意的第一個 WAVM 指令(它只驗證單個 tx )。

權衡之處是在 Nitro 執行期間進行更多的哈希運算(參見上面的(A)部分),但在故障證明期間進行更少的哈希運算:在執行跟蹤的二分遊戲中的每個步驟,都需要提交內存 Merkle 根。

像這樣的故障證明結構也減少了對驗證器內存膨脹的擔憂,其可能會超過當前運行 MIPS 的 4G 內存限制。

這不是一個很難解決的問題,但我們需要在 Bedrock 中小心,而驗證單筆交易可能永遠不會接近這個限制。

(i)原像預言機(Preimage oracle)

用於故障證明的驗證器軟件需要從 L1 和 L2 讀取數據。因為它最終將在 L1 上“運行”(儘管只有一條指令),所以需要通過 L1 訪問 L2 本身 – 通過發布到 L1 的狀態根和區塊哈希。

你如何從狀態或鏈中讀取(無論是 L1 還是 L2)?

Merkle 根節點是其子節點的哈希,因此如果你可以請求原像,則可以遍歷整個狀態樹。同樣,你可以通過請求區塊頭的原像來向後遍歷整個鏈。 (每個區塊頭都包含其父區塊的哈希值。)

在鏈上執行時,這些原像可以預先提供給 WAVM/MIPS 解釋器。 (鏈下執行時,可以直接讀取L2狀態!)

(請注意,你只需要訪問一個這樣的原像,因為在鏈上你只執行一條指令!)

這就是你在 Nitro 和 Bedrock 上閱讀 L2 的方式。

但是,你需要為 L1 做類似的事情。因為交易批次存儲在 L1 調用數據中,無法從 L1 智能合約訪問。

Nitro 將其批次的哈希存儲在 L1 合約中(這就是為什麼他們的“Sequencer Inbox”是一個合約,而不是像 Bedrock 那樣的 EOA)。所以他們至少需要這樣做,我不知道為什麼沒有提到。

在 Bedrock 中,我們甚至不存儲批次哈希(從而節省了一些 gas)。相反,我們使用 L1 區塊頭返回 L1 鏈,然後沿着交易 Merkle 根向下查找 calldata 中的批次。

(同樣,在鏈上,最多需要提供一個原像。)

第 4.1 節的結尾,提醒我們 Arbitrum 發明了“哈希預言機技巧”‌。不安全不應該成為忘記 Arbitrum 團隊貢獻的理由!

(J) 大原像(Large preimages)

Nitro 白皮書還告訴我們,L2 原像(Preimage)的固定上限是 110 kb,但沒有引用 L1 的數字。

在 Cannon 中,我們有一個稱為“大原像問題”的問題,因為要反轉的潛在原像之一是收據原像,其中包含 Solidity 事件發出的所有數據(EVM 級別的“日誌”)。

在收據中,所有日誌數據連接在一起。這意味着攻擊者可以發出大量日誌,並創建一個非常大的原像。

我們需要讀取日誌,因為我們使用它們來存儲存款( L2-to-L1 消息)。這並不是絕對必要的:Nitro 通過存儲消息的哈希來避免這個問題(它比這更複雜,但最終結果是相同的)。

我們不存儲哈希,因為計算和存儲它的成本很高,存儲要消耗大約 20k gas ,每計算 32 個字節要消耗 6 gas。平均一筆交易大約是 500 字節,因此一批 200 筆交易的哈希成本大約為 20k gas 。以 2000美元的 ETH 和 40 gwei basefee 計算,額外的哈希和存儲成本為 3.2$。以 5000 美元的 ETH 和 100 gwei 計算,成本即 20 美元。

我們目前解決大原像問題的計劃,是使用簡單的 zk-proof 來證明原像中某些字節的值(因為這是一條指令在實踐中需要訪問的全部內容)。

(K) 批次和狀態根

Nitro 將批次和狀態根緊密相連。 他們在包含狀態根的 RBlock 中發布一組批次。

另一方面,Bedrock 將其批次與狀態根分開發布。 關鍵優勢是再次降低了發布批次的成本(無需與合約交互或存儲數據)。 這讓我們可以更頻繁地發布批次,並減少狀態根的頻率。

另一個影響是,使用 Nitro,如果 RBlock 受到挑戰,它包含的交易將不會在新鏈上重放(新的正確狀態根)。

在 Bedrock 中,我們目前正在討論在成功挑戰狀態根的情況下該怎麼做:在新的狀態根上重放舊 tx,還是完全回滾? (當前的實現意味着完全回滾,但在推出故障證明之前可能會發生更改。)

(L) 其他雜項

影響較小的差異:

(i) Nitro 允許排序器發布的單筆交易可以是“垃圾”(無效簽名等)。為了盡量減少對 Geth 的更改,我們總是丟棄包含任何垃圾交易的批次。

排序器總是能夠提前找到那些,所以揮之不去的垃圾交易要麼是不當行為要麼是bug。排序器運行與故障證明相同的代碼,因此它們對無效內容的定義應該相同。

(ii) Nitro 引入了預編譯合約,尤其是用於 L2 到 L1 的消息傳遞。我們目前不使用任何預編譯,而是更喜歡它們“預部署”,即存在於創世區塊特殊地址的實際 EVM 合約。

事實證明,我們可以在 EVM 中做我們需要的事情,這使得節點邏輯稍微簡單一些。不過,我們並不堅決反對預編譯,也許我們會在某個時候需要用到預編譯。

(iii) Nitro 故障證明使用了 d向剖析( d-way dissection)。概念驗證 Cannon 實現使用了二分法,但我們也可能會轉向 d向剖析。

Nitro 白皮書中有一個非常好的公式,它解釋了基於固定成本和可變成本的 d 的最優值。然而,我希望他們在實踐中包括了如何估算這些成本的具體例子!

結尾

沒有什麼宏大的結論!或者更確切地說:請自己總結出結論:)

本文鏈接:https://www.8btc.com/article/6780990

轉載請註明文章出處

(0)
上一篇 2022-10-08 16:26
下一篇 2022-10-08 16:34

相关推荐