本文介紹了Rollup的交易抗審查機制—Force Inclusion,並以幾個著名的Rollup設計與實例為例。在Rollup的設計中,強制包含機制允許使用者繞過中心化的Sequencer,將交易上傳到歷史記錄中,確保不會被審查且能夠正常使用。 Rollup的抗審查能力取決於L1的能力,因此需要Force Inclusion來增強安全性。不同Rollup平台如Optimism、Arbitrum、StarkNet和zkSync對強制包含機制的實作方式略有不同,但都旨在增加交易的可靠性和安全性。強制包含機制可以讓使用者自主控制交易的記錄與執行,提升整體系統的穩健性。
本文將介紹Rollup 的交易抗審查機制— Force Inclusion,並以幾個著名的Rollup 的設計與實際為例。
照片由Artur Tumasjan 在Unsplash 上拍攝
先備知識:
知道Rollup 的功能以及為什麼Rollup 需要把交易上傳到L1 上交易的抗審查能力
交易抗審查(審查抵抗)的能力對一條區塊鏈來說非常重要,如果一條區塊鏈能夠為任何審查用戶交易,那麼這條區塊鏈就和一個Web2伺服器沒有兩樣。以太坊目前的交易抗審查能力來自於為數眾多的驗證者們,如果Alice審查了想要Bob的交易、不讓他的交易上鍊,那麼Alice要不得嘗試買通網路中的每一個驗證者,要不得垃圾郵件整條網路、不斷發送比Bob交易高的垃圾交易來塞滿區塊。無論是哪一種方式,她的成本都會非常高。
注意:在以太坊目前的PBS 架構中,審查的成本確實降低明顯,可以參考OFAC 審查Tornado Cash 交易的區塊比例。目前的抗審查能力仰賴於OFAC 及政府管轄範圍之外的獨立驗證者及中繼。
但Rollup呢? Rollup需要一大堆的驗證者來保證它的安全性,補充Rollup只有一個中心化的角色(Sequencer,稱為排序器或排序器)來批量區塊,也和L1一樣安全但安全和抗審查能力是兩回事,建構了一個Rollup和以太坊一樣安全,而且只有一個中心化的序列器,那麼該序列器想要審查任何用戶的交易都行。
Sequencer 可以拒絕收入用戶交易,導致用戶無法使用也無法離開Rollup
強制指揮機制
同時要求Rollup 和L1 一樣多的驗證者來確保抗審查能力,不如直接利用L1 的抗審查能力:巡檢儀也將交易壓縮數據到L1 的Rollup 契約上,不如在Rollup 契約裡加入一個讓監控使用者也可以插入交易到排序,這樣的辦法就叫強制包含。只要Sequencer無法審查用戶的「L1交易」,它並不能阻止用戶透過L1強制插入Rollup交易,而Rollup的運作及安全性基礎是基於的L1的抗審查能力。
排序器無法審查使用者的L1 交易,除非有複雜的成本
註:強制包含是要該Rollup 有設計才會有,使用者一定可以突破L1 強制插入Rollup 交易。如果Rollup 沒有提供強制包含機制,那麼用戶就只能祈禱Sequencer 審查不會自己交易。
交易立即生效vs. 延遲生效
如果我們允許透過Force Inclusion 插入的交易可以直接寫入到Rollup 的交易歷史中(突然立即生效),那麼Rollup 的狀態就會立即被改變,例如Bob 透過Force Inclusion 插入筆「他轉1000 DAI 給「Carol 」的交易,如果此時交易立即生效,那最新的狀態中Bob的餘額就會少1000 DAI而Carol會多1000 DAI(當然前提是Bob的餘額超過1000 DAI)。
如果Bob交易能直接被寫進交易歷史中,立刻生效,那麼Rollup的狀態就會立刻改變
如果此時Sequencer正在鏈下集合交易,等到把下一筆支付交易發送到Rollup合約上,那麼可能被Bob強制插入並立即生效的交易給影響到。例如Bob只需預先發送了付款“他”轉1000 DAI 給Alice”的交易到Sequencer 那,Sequencer 實際上驗證沒問題並承諾會收入,Alice 和Bob 都可以向Sequencer 查詢交易是否會被收入併計算出最新狀態(Alice 多1000 DAI,Bob 少1000但等到Sequencer 發現Bob 搶先去強制插入交易後,它手上的交易的執行狀態都改變了,那知道筆已“Bob 轉1000 DAI 給Alice”的交易已經變成會執行失敗,Alice 也拿不下來到1000 DAI。
Sequencer 收入Bob 的交易,Alice 因此相信自己會收到1000 DAI
結果Bob直接在L1上強制收入另一筆衝突的交易,導致Sequencer手上的交易無法被收入,Alice也拿不到DAI
這可能不是一個好的使用者體驗,Rollup 一般會強制強制交易立即生效,從而讓交易高級到一個「準備中」的狀態。 Sequencer 可以在發送交易時選擇是否要順便塞進這些“ “準備中”狀態的交易到批量的最後面,如果Sequencer 一直都沒有處理這些“準備中”的狀態,那麼這些交易在過了一段時間後就可以強制插入交易歷史。
鮑伯想透過強制收入交易來欺騙愛麗絲,但實際上交易會先進到隊列等待
Sequencer 可以自己決定何時「順便入境」隊列等待中交易,所以Bob 給Alice DAI 的交易會先執行
如果Sequencer一直沒有等待隊列中的交易,在一段時間內後用戶(或任何人)就可以自己去強制收入。
Sequencer 拒絕收入Bob 交易,所以Bob 從L1 將交易等待隊列中
排序器仍然可以持續拒絕佇列中的交易一段時間
但Sequencer 無法永遠拒絕等待隊列中的交易,一段時間後任何人都可以觸發強制收入,接下來將依次介紹Optimism、Arbitrum、StarkNet 及zkSync 等較有名的Rollup 的原力對抗機制實踐。
樂觀的力量遏制
首先先介紹Optimism 的Deposit 流程,這個Deposit 不單是指把存錢進Optimism 的意思,而不是更一般的「把給L2 的消息」存入L2。 L2 節點在接收到新存入的訊息後就傳送訊息轉換成連接埠L2交易去執行,傳送訊息指定的接收方。
使用者從L1 提交給L2 的訊息
L1CrossDomainMessenger 合約
當一個用戶決定ETH 或ERC-20 代幣存進樂觀時,他會(交叉網頁)與L1 上的L1StandardBridge 合約交易,指定要存多少數量以及由哪個地址接收等等。連接L1StandardBridge 連接訊息傳遞至下第二層的L1CrossDomainMessenger合約,這個合約主要是作為一般通用的L1與L2之間相互通訊的合約,L1StandardBridge就是使用這個通用的通訊合約來和L2上的L2StandardBridge通訊,決定誰可以從L2鑄造代幣或可以從L1提領代幣開始。如果開發者需要開發一個L1與L2之間互通、同步狀態的合約,那麼他就可以搭建在L1CrossDomainMessenger之上。
用戶的訊息傳遞CrossChainMessenger 合約從L1 傳遞到L2
OptimismPortal 合約
L1CrossDomainMessenger契約會再將訊息發送至最底層的OptimismPortal契約,OptimismPortal契約處理完成後會發出一個TransactionDeposited事件,事件參數包含「發送訊息的人」、「接收訊息的人」,以及相關的執行參數。
然後L2 的Optimism 節點會監聽OptimismPortal 約定所發布的TransactionDeposited 事件,並把事件裡的參數轉換為記下L2 的交易,這個L2 交易的發起者可能是TransactionDeposited 事件裡的「送訊息的人」、交易的接收者就可能是事件裡的「接收訊息的人」,其他執行參數也由事件參數監聽。
L2 節點插入OptimismPortal 發布的TransactionDeposited 事件轉換成註解L2 交易
例如這一筆是某個用戶滲透L1StandardBridge 合約存款0.01 ETH 的交易,這個消息及ETH 一路傳到OptimismPortal 合約(地址是0xbEb5…06Ed),接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;接收四十後轉換成L2 交易然後消息發起者是L1CrossDomainMessenger 合約;者是L2上的L2CrossDomainMessenger合約;附0.01 ETH。
強制指揮
當使用者想要強制收入他的交易進行樂觀執行時,他不需要從L1發送訊息到L2,所以他不會去使用L1CrossDomainMessenger合約。他要達到的效果是讓帳單具體「他從他的」L2地址在L2上送出並要執行的交易」能夠順利執行,所以他會直接去和OptimismPortal合約互動,並且讓他去地址呼叫OptimismPortal合約,這樣到時候TransactionDeposited事件轉換成L2交易的“交易發起”者將會是他的L2地址”,才能重新看到那筆交易。
從TransactionDeposited 事件轉換而成的L2 交易中,發起人會是Bob 自己;接收人是Uniswap 合約;而且會附帶指定的ETH,就像Bob 自己發起L2 交易一樣
使用L2位址呼叫OptimismPortal簽約的存款交易函數,記清楚L2交易的參數一一填入
我做了一個簡單的強制包含交易:以我L2的地址(0xeDc1…6909)轉錢給我自己,並附帶一個「強制包含」的文字訊息。這是我透過OptimismPortal契約執行depositTransaction函數的L1交易,可以在發送的TransactionDeposited事件中看到,從和到都是我自己,剩餘的opaqueData欄位裡的值(0000000000000000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000015BE00000000000000000000000000000015BE000000000000C35000666F7263650006F7696Faction的人附帶了多少ETH」、「L2 交易發起者」要附帶ETH 給L2 接收者」、「L2 交易Gas Limit」及「給L2 接收者的資料」等等資訊。
L1交易發行的TransactionDeposited事件
將這opaqueData幾個資訊的數值解讀後分別會得到:
「呼叫DepositTransaction的人附帶了多少ETH」:,0因為我沒有要從L1充值ETH進到L2「L2 交易發起者要附帶多少ETH 給L2 接收者」:5566(wei),因為我要轉錢給我自己「L2交易氣體屏蔽」:50000「給L2接收者的資料」:0x666f72636520696e636c7573696f6e,語音「強制包含」這個字串的聽力編碼編碼
接下來沒多久就出現轉換後的L2 交易:記下我轉錢給自己的L2 交易,金額是5566,資料是「強制包含」字串。並且可以注意到在倒數第二行的其他屬性中Txn Type(交易類型)是系統交易126(System),表示不是我自己在L2發起的交易,是由L1TransactionDeposited事件轉換而來。
轉換完成的L2 交易
如果使用者要召集其他合約、帶不同的數據,那同樣就是將參數一一填寫到depositTransaction函中,只需記住是用L2地址來去L1上執行,這樣到時候L2交易發起人可能就是該L2地址。
音序器視窗
前面提到的Optimism L2 節點將TransactionDeposited 事件轉換成L2 交易,其實這個Optimism 節點指Sequencer 節點,畢竟是關乎交易排序,所以只有Sequencer 轉換決定什麼時候要成L2 交易。當監聽到TransactionDeposited 事件時,音序器不一定會立即將事件轉換成L2 交易,但是可以決定要進行轉換,這段時間稱為音序器窗口,目前Optimism 主網上的音序器窗口為24 小時,那麼當用戶從L1 開始時補款時或者一個提示,或者強制包含補款交易時,最糟糕的情況會是24小時後才被正式收入進L2交易歷史中,不過至少這勝過交易永遠沒辦法被收入的結果。
Arbitrum的強制防守
在樂觀中L1 的存款操作會發出一個TransactionDeposited 事件,剩下的就是等待Sequencer 收入這個操作;但在任何中L1 的操作(存錢或傳訊息給L2 等等)都會存在L1 約定的一個被隊列裡,而不是一個發行事件。而定序器會被給予時間來將這個佇列裡的一段操作交易歷史中,如果時間到了定序器都作為,那麼任何人都沒有去替換定序器就可以完成完成。
Arbitrum會在L1 遵守一個隊列,如果Sequencer 沒有主動隊列裡的交易,時間到了任何人都可以強制隊列裡的交易進行交易歷史中
L1操作全部都是Delayed Inbox的合約,顧名思義這裡的操作會延遲失效;另一個合約是Sequencer Inbox,是給Sequencer上傳L2交易的介面。 Sequencer的上傳交易會直接寫入交易歷史中,而另外Sequencer上傳時候都可以選擇要順便從Delayed Inbox拿多少個L1操作一起寫入交易歷史記錄中,讓這些L1操作生效。
SequencerInbox 裡是交易歷史,通常只有Sequencer 可以直接寫入新交易;DelayedInbox 裡等待被收入的交易
Sequencer 寫入新交易時可以順便從DelayedInbox 發起交易一起寫入
複雜的設計以及凡善可陳的文件
如果讀者直接參考Arbitrum 官方關於Sequencer 及Force Inclusion 的章節,會看到裡面提到的Force Inclusion 如何相容,以及一些參數名稱和函數名稱:使用者先Inbox 約定呼叫發送UnsignedTransaction 函數,如果Sequencer 未在約24小時內收入,那用戶就可以去呼叫Sequencer Inbox簽約的forceInclusion函數。就這樣,連連結也沒有附在裡面,只能自己去約定方案碼裡相對應的函數。
當找到sendUnsignedTransaction函數後,你發現其實要自己填nonce值還有maxFeePerGas值。是哪個網址的nonce?是哪個網路上的maxFeePerGas值?怎麼填比較好?沒有文件記錄,連Natpsec都沒有。然後你還順便發現了一個堅固外殼的函數:sendL1FundedUnsignedTransaction、sendUnsignedTransactionToFork、sendContractTransaction、sendL1FundedContractTransaction,同樣沒有文件告訴你這些函式的區別、使用方法、參數如何填寫,連Natpsec 都沒有。
綜合考慮並嘗試一次的心態來試填參數並發送交易,想用反复嘗試的方式看你能否找到正確的方法,但發現這些函數都會把你的L1地址做地址別名,導致最終L2上的交易發起人根本上是不一樣的地址,所以你的L2地址一動也不動。
發送L2訊息
後來偶然點開Google 搜尋頁面中的某個連結才發現Arbitrum 自己原來有一個教程程式庫,裡面有腳本示範怎麼從L1 送L2 交易(或者Force Inclusion 的意思),它的函數完全不是上面然後提的到了任何一個,不過是一個叫的sendL2Message式函件,而且訊息參數要帶入的居然是簽名的任意L2交易? ? ?誰要「傳送L2知道的訊息」其實會是備註「簽完名的」 L2 交易」??還有一次,沒有任何文件及Natspec 解釋什麼時候用及如何使用這個函數。
結論:要手動產生一個Arbitrum 的強制收入交易比較麻煩,建議就照著官方教程跑Arbitrum SDK 唄。 Arbitrum 不像其他Rollup 明確有的開發者文件及計畫碼附註,許多函式的用途和參數缺乏說明,導致開發者需要花費比預期更多的時間來存取和使用。我同時Arbitrum Discord 上詢問Arbitrum 的人,但並沒有得到令人滿意的答案。
註:在Discord上詢問,對方也只是讓我聽sendL2Message,不需要解釋其他函式(甚至是強制包含文件裡提到的sendUnsignedTransaction)是什麼用途、怎麼用、什麼時候用。
很遺憾,StarkNet目前還沒有強制收錄。只有兩篇在官方論壇上討論到審查及強制收錄機制的文章。
無法證明交易失敗
是因為StarkNet的到底零知識證明系統沒辦法證明記帳失敗的交易,所以不能允許強制記入帳。因為如果有人惡意(或無意)強制記記記入失敗、無法被證明記帳失敗的交易,那麼StarkNet就會直接直接卡住:因為交易被強制收入後,Prover就必須證明該筆失敗交易,但它卻沒辦法證明。
而StarkNet預期在v0.15.0版本引進證明失敗交易的功能,之後應該可以進一步實現強制包含機制。
zkSync 的強制包含機制
zkSync 的L1->L2 訊息傳遞以及強制包含機制都要求MailBox 合約的requestL2Transaction 函數進行,使用者要指定呼叫的L2 位址、通話資料、帶上的ETH 數量、L2 Gas Limit 值等,建構requestL2Transaction 這些參數組合成一個L2交易然後模擬優先佇列(優先隊列)中,Sequencer會在交易備份上傳到L1時(commitBatches函數)指定要順便從優先佇列中拿出多少筆交易一起收入。
zkSync 在強制包含界面和樂觀方面很像,都是以用戶的L2 地址去呼叫,並填入相關資料(被呼叫者、通話資料等),而不是像Arbitrum 一樣是填記簽完名的L2 交易;但在設計上交易和Arbitrum一樣都在L1上一個實體的隊列,並由Sequencer從隊列中取出交易寫入交易歷史中。
使用者透過requestL2Transaction插入L2交易到優先佇列中,Sequencer在commitBatches時可以從優先佇列中順便對話交易
如果你透過zkSync 的官方橋接儲值ETH,此時交易,它就是去呼叫MailBox 契約的requestL2Transaction 函式,它會保留這個充值ETH 的L2 交易優先佇列中並發出一個NewPriorityRequest 事件。 L2 交易資料編碼成一串字節字符串所以不易讀,改成看一下L1 交易的參數的話,會看到參數中L2 的接收方也是交易的發起人因為(是存款給自己),所以過一陣子兌換L2交易被序列器從優先列支出並收入進交易歷史時,它會在L2上被轉換成記下自己轉帳給自己的交易,而轉帳的金額就是交易發起人在L1的ETH交易所帶的ETH 金額。
L1儲值交易中,交易發起者與接收者均為0xeDc1…6909,金額為0.03 ETH,呼叫資料為空
L2 上會出現記帳0xeDc1…6909 自己轉帳給自己的交易,交易類型(Txn Type)是255,玄系統交易
接下來我直接照本宣科呼叫請求L2交易函,發送了記下自己呼叫自己的交易:不帶任何ETH,調用資料帶入「強制包含」字符串的十六進位編碼。佇列的式子被轉換成L2上記下自己呼叫自己的交易,呼叫資料裡是「強制包含」字串:0x666f72636520696e636c7573696f6e。
當排序器把交易從優先隊列拿出來並寫入進交易歷史中時,在L2上就可以轉換成相對應的L2交易
透過requestL2Transaction函數,使用者可以以L2位址在L1請求L2交易,指定L2接收者、帶上的ETH金額以及通話資料。如果使用者要呼叫其他合約、帶不同數據,那同樣就是將參數一一填入requestL2Transaction函數,只要記得是用L2位址來去L1上執行,這樣到時候L2交易發起者就會是該L2位址。
還沒有讓用戶強制收入的功能
雖然L2交易放在優先佇列中會順便計算出L2交易要被Sequencer的收入期限,但目前zkSync設計中並沒有讓用戶能夠自己執行的強制包含相關函數,實際上可以做半套。 「收入有效期限」,但實際上還是「看Sequencer 要不要收入」:Sequencer 可以等到過渡後才收入,也可以永遠不再收入優先佇列中任何交易。未來zkSync 應該加入相關函數上,讓使用者可以在收入已經過了但還沒有被Sequencer收入時,能夠強制收入,這樣才是真正有效的強制約束機制。
總結L1靠為數群的人來保證網路的“安全性”及“抗審查能力”,而Rollup靠“L1的抗審查能力”來獲得“和L1相同的安全性”,但不包含Rollup的抗審查能力相反地,Rollup因為都是由少數或甚至單一的Sequencer來寫入交易,抗審查能力反而更弱。因此Rollup需要有Force Inclusion來讓用戶繞過Sequencer,將交易寫入歷史中,避免被Sequencer 審查而無法使用也無法離開Rollup強制包含讓用戶可以強制將交易寫入歷史中,但在設計上需在「交易是否能立即插入歷史、立即生效」上做選擇。如果交易立即生效的話,會造成排序器的麻煩和使用體驗上面的困難,因為L2上等待被收入的交易都可能會被L1強制收入的交易所影響因此目前Rollup 的強制包含都會機制先讓L1 上插入的交易先進到一個等待狀態等待中,並讓Sequencer 有一段時間窗口來反應、來選擇要不要收入這些中的交易zkSync 和Arbitrum 都是在L1 維護一個實體的隊列,用於管理用戶從L1 送出的L2 交易或給L2 的訊息。 Arbitrum 稱為延遲收件匣;zkSync 稱為優先佇列但zkSync發送L2交易的方式和Optimism比較像,都是以L2位址去L1發送,所以L2交易的發起人會是該L2位址。 Optimism發送L2交易的函數稱為depositTransaction;zkSync稱為requestL2Transaction。而Arbitrum製作填寫完整的L2交易並簽名,然後透發送L2訊息函的方式發送出去,Arbitrum在L2上會透簽名還原章者來作為L2交易的發起人StarkNet 目前還沒有強制包含做到;zkSync 撕像半套的強制包含— 有優先隊列且每個隊列裡的L2 交易都有收入有效期限,但這個有效期限目前只是裝飾用,實際上Sequencer 可以選擇完全不再收入任何優先隊列中的L2交易
資訊來源:0x資訊編譯自網際網路。版權歸作者NIC Lin所有,未經許可,不得轉載