Nonce Bitmap - 為並行區塊鏈啟用並行交易提交

本文為機器翻譯
展示原文

TL;DR

想象一下,你是一位擁有多個時間敏感型交易機會的交易者。你同時發現了三個套利機會,但你每次只能提交一筆交易。當你的第三筆交易被處理時,機會就消失了。這就是順序性問題。我們提出了一種新穎的解決方案——Nonce Bitmap,用於在區塊鏈系統中實現並行交易提交,同時保持安全性並與現有錢包兼容。

  • 並行性。Nonce Bitmap 引入了一種基於位圖的方法,允許用戶利用傳統 nonce 字段中未充分利用的位並行發送最多 256 個交易。
  • 最小存儲開銷。與其他方法相比,Nonce Bitmap 需要最小的存儲開銷(每個地址 32 字節)。
  • 向後兼容性。舊版錢包和普通用戶的行為不會發生任何變化。
  • 重放保護。Nonce Bitmap 在消除順序瓶頸的同時,保留了針對重放攻擊的安全保障。
  • 更好的用戶體驗/用戶體驗。Nonce Bitmap 對於高頻用戶(如交易者、MEV 搜索者以及需要併發交易提交的協議)尤其有價值。

傳統 Nonce 機制

EVM Nonce:不僅僅是一個計數器

本質上,nonce 與每個外部擁有賬戶 (EOA) 相關聯,用於表示從該賬戶發送的交易數量。每個 StateAccount 維護一個最多 8 個字節的 nonce 字段。

// StateAccount is the Ethereum consensus representation of accounts. // These objects are stored in the main account trie. type StateAccount struct {Nonce uint64 Balance *uint256.IntRoot common.Hash // merkle root of the storage trie CodeHash [] byte }

對於新賬戶,nonce 從零開始,每當該賬戶發起的交易被打包到區塊中時,nonce 就會加 1。這個計數器看似簡單,卻對 EVM 的完整性和確定性運行至關重要。

  • 安全性。Nonce 用於防止重放攻擊,要求每個來自同一地址的交易都必須使用唯一的 Nonce。因此,任何嘗試兩次提交同一交易的行為都將失敗,因為網絡已經確認使用了該 Nonce。這種安全保障對於用戶資產和區塊鏈交互的安全至關重要。

  • 排序。以太坊中的 nonce 值嚴格遞增。也就是說,每筆交易的 nonce 值必須比前一筆交易的 nonce 值大 1。

    next := opts.State.GetNonce(from) if next > tx.Nonce() { return fmt.Errorf( "%w: next nonce %v, tx nonce %v" , core.ErrNonceTooLow, next, tx.Nonce())} // Ensure the transaction doesn't produce a nonce gap in pools that do not // support arbitrary orderings if opts.FirstNonceGap != nil { if gap := opts.FirstNonceGap(from); gap < tx.Nonce() { return fmt.Errorf( "%w: tx nonce %v, gapped nonce %v" , core.ErrNonceTooHigh, tx.Nonce(), gap)}}
  • 交易計數。雖然這在以太坊黃皮書中被定義為 nonce 的定義,但它僅僅是當前 nonce 設計的一種暗示。

    隨機數(Nonce) 。一個標量值,等於從該地址發送的交易數量,或者,對於具有關聯代碼的帳戶,等於該帳戶創建的合約數量。

    使用當前設計,給定地址的交易計數( eth_getTransactionCount )是通過簡單地返回當前帳戶的隨機數來完成的。

    // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error ) { // Ask transaction pool for the nonce which includes pending transactions if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {nonce, err := sbGetPoolNonce(ctx, address) if err != nil { return nil , err} return (*hexutil.Uint64)(&nonce), nil } // Resolve block number and use its state to ask for the nonce state, _, err := sbStateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil , err}nonce := state.GetNonce(address) return (*hexutil.Uint64)(&nonce), state.Error()}

總體而言,該設計提供了一種簡單而有效的方法來解決重放攻擊,從而實現高效的交易驗證並減少存儲佔用空間。

順序性瓶頸

當前的 nonce 設計在賬戶層面造成了不可避免的順序性。也就是說,即使交易n+1 n + 1n n不相關(可以並行執行),交易n+1 n + 1仍然必須等待交易n n ,因為同一發送者的交易是按照 nonce 的順序處理的。

交易卡住是另一個問題。如果用戶提交的交易n n的 Gas Price 過低,無法被打包進區塊,那麼這筆交易就會一直停留在內存池中,直到被打包、丟棄或替換。在此期間,無論 Gas Price 是多少,該用戶提交的任何後續交易都將失敗。這種情況是用戶不希望看到的,會給用戶體驗帶來不好的影響。

  • 例如,用戶可能有一筆手續費相對較低的 ETH 轉賬交易待處理,但隨後急需平倉即將被清算的 DeFi 倉位。根據目前的 nonce 設計,他必須等待這筆卡住的交易,或者用另一筆使用相同 nonce 但 Gas Price 更高的交易來替換這筆卡住的交易。無論哪種情況,都會增加很多複雜性,普通用戶可能無法應對。

此外,高級用戶(例如高頻交易者)通常需要並行發送交易。為此,他們通常必須仔細管理鏈下的隨機數 (nonce)。然而,這並非易事。即使使用鏈下管理,也必須查詢 1) 當前隨機數或 2) 上一筆交易的收據,才能選擇提交當前交易。在延遲至關重要的情況下,此查詢也會導致一些延遲。尤其是在 RISE、MegaETH、Monad、Flashblocks 等交易處理速度極快( <10ms 分解時間)的區塊鏈中,查詢鏈上數據可能會導致數十到數百毫秒的延遲。

更糟糕的是,嘗試並行發送多筆交易(即使使用不同的 nonce)可能會導致無效 nonce 間隙錯誤。發生這種情況的原因是,當前的 P2P 廣播無法保證 nonce 為n n 的交易會在交易n+1 n + 1之前到達節點的內存池。

這些限制在低延遲鏈上變得尤為嚴重。
讓我們探索一下其他系統如何嘗試解決這個問題。

打破順序性

高性能區塊鏈可以以極低的延遲處理數萬TPS。然而,如果沒有更好的錢包/客戶端用戶體驗,就無法充分發揮其潛力。這些區塊鏈能夠併發處理大量交易,但目前交易提交是順序的,這可能是充分釋放其潛力的瓶頸。

在本節中,我們將探討一些解決這種順序性問題的潛在替代設計。這些設計主要關注 nonce 的安全性,其中 nonce 是一個一次性使用的數字,用於防止重放攻擊。

我們注意到,雖然交易排序和計數很重要,但可以通過不同的方法來實現。例如,使用eth_getTransactionCount的服務(通常是瀏覽器)可以通過其本地數據庫提取此數據。

隨機數

這種設計並非嚴格遞增 nonce,而是允許交易攜帶任意唯一標識符,從而實現並行交易提交和處理。每個賬戶都維護所有先前使用過的 nonce 的記錄,而不是單個標量計數器。例如,這可以表示為從地址到已使用 nonce 集合的映射。

UsedNonces = map [Address] map [ uint64 ] bool

提交交易時,節點會檢查 nonce 是否已被該賬戶使用,從而驗證其有效性。如果 nonce 未被使用,則接受該交易,並將 nonce 標記為已使用。

這種方法允許處理節點並行執行來自同一用戶的交易(如果交易不相關),從而顯著提升性能。對於客戶端,錢包可以生成隨機數,而無需仔細跟蹤或同步隨機數計數器,從而簡化並行交易提交。可以實現一個額外的 RPC,例如eth_usedNonces ,來檢索給定地址的已用隨機數。此外, eth_getTransactionCount可以返回給定地址的UsedNonces的長度。

然而,這種方法也帶來了一些不容忽視的缺點。最顯著的缺點是存儲開銷,因為節點必須為每個賬戶存儲並維護一組可能很大的已用 nonce,這增加了狀態大小和存儲複雜性。此外,根據隨機 nonce 生成器算法,可能會出現 nonce 重複的情況。

Hyperliquid 的設計

Hyperliquid不會存儲給定地址所有已使用的 nonce,而是僅存儲每個地址使用率最高的 100 個 nonce,並且需要額外的規則來檢查 nonce 的重用性。也就是說,新交易的 nonce 必須大於此集合中最小的 nonce,並且之前從未使用過(即不存在於集合中)。此外,nonce 必須在交易所在區塊的 UNIX 毫秒時間戳的 1 天內。例如,對於新交易,可以將 nonce 設置為當前時間戳。

與隨機 nonce 設計類似,Hyperliquid 提供了大量的並行性。用戶體驗也得到了改進,因為錢包可以靈活選擇不同的 nonce 策略,並且支持並行提交交易。時間限制的有效性也降低了中繼攻擊的風險。與隨機 nonce 設計相比,這種方法的存儲開銷更小,因為只存儲 100 個已使用的 nonce。然而,它存在一個瓶頸,即無法並行發送超過 100 個交易。

然而,由於 nonce 的管理方式完全不同,某些操作可能無法與現有錢包向後兼容。事實上,這種 nonce 設計僅用於 HyperCore 層。HyperEVM 層仍然沿用傳統的 nonce 設計。此外,時間限制也限制了預定交易的持續時間(例如,無法進行預定在一天以上進行的預簽名交易)。

2D 隨機數

RIP-7712賬戶抽象(AA) 交易引入了二維隨機數 (2D Nonce) 機制。二維隨機數允許智能合約賬戶處理更靈活、更可並行化的隨機數系統。二維隨機數是一個n位(例如,在 AA 中n = 256 的值,它被拆分為兩個邏輯部分:

┌─────────────────────────────────┬──────────────────────────┐│ Upper k bits │ Lower nk bits ││ (Nonce Key) │ (Nonce Sequence) │└─────────────────────────────────┴──────────────────────────┘
  • nonceKey nonceKey作為一個 nonce 類別,將 nonce 空間劃分為多個獨立的類別。
    • 經典的 nonce 對應一個 1D nonce,相當於只有一個 nonce 類別(即nonceKey = 0
  • nonceSequence 。nonceSequence 是該類別中的nonceSequence ,必須按順序增加才能排序。

具有不同nonceKeys的交易可以按任意順序執行或包含,從而實現並行處理。另一方面,具有相同nonceKey的交易必須按順序執行,並且nonceSequences值必須遞增。

nonceKey決定了賬戶的並行度, k越大,可以並行發送的交易越多。理論上,這種結構允許一個賬戶維護最多2^{k}類別每個類別可以容納2^{nk}連續nonce 。這意味著一個賬戶可以同時發送最多2^{k}交易,而無需擔心 nonce 的排序問題。

然而, nonceKey主要用作一組相關交易(例如會話密鑰、基於時間的交易)的標識符。如果以這種方式使用,同一組內的交易將無法並行化。例如,如果nonceKeys 1、2、3 分別用於交換、ETH 轉賬和 ERC-20 轉賬,則一個賬戶無法並行發送兩筆交換交易、兩筆 ETH 轉賬或兩筆 ERC-20 轉賬。它只能同時發送一筆交換交易、一筆 ETH 轉賬和一筆 ERC-20 轉賬(相對於 nonce 管理)。此外,每個新的nonceKey都會引入另一個n n位存儲開銷來存儲nonceKeynonceSequence

Nonce 位圖提案

在本節中,我們提出了一種新穎的隨機數管理機制,允許一個賬戶並行發送最多 256 筆交易,每筆並行交易的存儲開銷超過 1 位。此外,該提案完全向後兼容,並且在交易提交過程中無需引入任何額外字段。

設計

Nonce 位圖設計
Nonce 位圖設計1300×1416 89.8 KB

我們的解決方案擴展了標準賬戶狀態,將傳統的 nonce 與Bitmap字段相結合,以跟蹤 nonce 的使用情況。對於每個Nonce值,我們最多允許 256 筆具有不同Index交易。Bitmap 字段是一個Bitmap位的字段,其中每位代表當前Nonce下 256 個可用 slot(每個 slot 對應一個Index )中的一個,用於並行交易。如果交易修改了Nonce ,則Bitmap字段將被清零,並將Index位置的位設置為 1。

// NonceBitmapStateAccount is the Ethereum consensus representation of accounts accommodating with nonce bitmap. // These objects are stored in the main account trie. type NonceBitmapStateAccount struct {Nonce uint64 // Anchor nonce: the last finalized sequential checkpoint, always have first 8 bits as 0s. Balance *uint256.IntRoot common.Hash // merkle root of the storage trie CodeHash [] byte Bitmap *uint256.Int // NEW: Tracks used slots for parallel transactions; nil for legacy accounts }

具體來說, Nonce仍然是嚴格遞增的,與傳統實現相同。但是, Nonce的前 8 位始終為 0。我們觀察到當前的Nonce值是 64 位,我們預計一個賬戶永遠不會用完。這是因為一個 64 位的 Nonce 值最多可以佔用2^{64} = 18446744073709551616如果一個賬戶每天發送 10 億筆交易,則該賬戶需要 50+M 年才能用完所有 Nonce。因此,我們認為 56 位空間對一個賬戶來說已經足夠了( 2^{56} = 72057594037927936

Bitmap中的每個位都指示該位對應的索引是否已被使用。例如,如果索引 10 處的位圖設置為 1,則表示索引 10 已用於當前Nonce 。256 位的Bitmap字段允許一個賬戶一次最多發送 256 筆交易,且順序不限(無需嚴格排序)。使用Bitmap可以非常高效地檢查索引是否已被使用。此外,這種方法每個並行交易只需要一個額外的位。

交易創建

一個關鍵挑戰是如何在不破壞現有交易格式的情況下,發出所選並行槽的信號。用戶在創建新交易時,必須能夠配置Index ,以支持發送並行交易。一種簡單的方法是在現有交易類型中引入一個新的 8 位Index變量。該Index字段用於定位發送者賬戶狀態中Bitmap字段的相應位值。

// LegacyTx is the transaction data of the original Ethereum transactions. type LegacyTx struct {Nonce uint64 // nonce of sender account GasPrice *big.Int // wei per gas Gas uint64 // gas limit To *common.Address `rlp:"nil"` // nil means contract creation Value *big.Int // wei amount Data [] byte // contract invocation input data V, R, S *big.Int // signature values ~~Index uint8 ~~ // NEW: The index value for parallelism? }

幸運的是,有一種更好的方法可以將Index信息實際合併到交易中,而無需引入新的字段。我們採用了一種位打包技術來解決這個問題,該技術充分利用了 64 位Nonce字段的巨大且未被充分利用的空間。具體方法是將交易中的Nonce字段邏輯地拆分成兩個不同的部分,然後通過簡單的按位運算來提取:

func ExtractNonce (nonce uint64 ) (index uint8 , actualNonce uint64 ) {index = uint8 (nonce >> 56 ) // Extract first 8 bits (most significant bits) actualNonce = nonce & 0x00FFFFFFFFFFFFFF // Mask lower 56 bits return }
  • Nonce的前 8 位用作Index
  • 最後 56 位作為實際的 nonce 值。正如我們上面分析的那樣,56 位的 nonce 空間對於任何賬戶來說都足夠了。

對於普通用戶來說,前 8 位始終為 0。actualNonce 是常見的順序隨機actualNonce 。從他們的角度來看,整個系統似乎沒有變化。對於高級用戶,他們可以將前 8 位設置為任意值,且順序任意。這意味著高級用戶可以並行發送最多 256 筆交易。

Nonce 驗證

驗證邏輯是系統的核心,確保安全和進度。當新交易到達時,將執行以下邏輯(請注意,此處不考慮費用替代交易):

func ValidateNonce (account *NonceBitmapStateAccount, txPackedNonce uint64 ) bool {index, actualNonce := ExtractNonce(txPackedNonce) if actualNonce == account.Nonce + 1 { // --- CASE 1: Advancing the Sequence --- // This transaction is the next in the sequential chain. // It is valid. This will cause the account's Nonce to increment. // The bitmap is reset, as we are moving to a new base state. return true } else if actualNonce == account.Nonce { // --- CASE 2: Parallel Transaction --- // This transaction operates at the current account's Nonce. // Check if the requested parallel slot is available. if account.Bitmap == nil { // Bitmap is not initialized; this is the first parallel tx at this nonce. return true } return account.Bitmap.Bit( int (index)) == 0 // True if the slot is free } else { // --- CASE 3: Invalid Nonce --- // actualNonce is either too old (less than account's Nonce) or has a gap. // This mirrors the existing Ethereum validation rule. return false }}

讓我們考慮以下交易序列。它始於一個賬戶,其當前Nonce為 5,並且Bitmap顯示 slot 0 已被佔用。用戶使用不同的 slot( Index 2 和 3)在Nonce 5 處成功提交了兩筆並行交易(TxA 和 TxB),驗證器更新位圖以將這些 slot 標記為已使用。在驗證器處理 TxB 期間,TxA 已完成,但未產生任何衝突。

Nonce Bitmap 實現並行交易
Nonce Bitmap 啟用並行交易878×1388 90.6 KB

然而,當用戶嘗試提交另一筆交易 (TxC) 並試圖重用已被佔用的Index 2 時,驗證器會正確地將其視為重複交易而拒絕。之後,當用戶提交Nonce為 6 的交易 (TxD) 時,系統會繼續前進,這將觸發 Nonce 更新:當前Nonce遞增至 6,並且Bitmap會根據交易的Index進行更新。此 Nonce 遞增至關重要,因為當用戶嘗試提交使用現已過時的Nonce 5 的交易 (TxE) 時,驗證器會拒絕該交易,因為系統已向前推進,從而防止任何舊交易的重放。

分析與思考

Nonce Bitmap 方法直接解決了困擾高頻用戶的順序性問題,同時保持了 EVM 區塊鏈必不可少的安全保障和向後兼容性。

簡單

該設計實現起來非常簡單。其核心原理直觀易懂:為普通用戶維護一個順序的Nonce ,同時使用Bitmap跟蹤每個步驟中的並行操作。檢查和更新Bitmap位操作邏輯計算簡單,使用處理器原生支持的高效位操作。與隨機 Nonce、Hyperliquid 的方法或二維 Nonce 管理相比,整體複雜度仍然較低。

效率和性能

與 Hyperliquid 的 nonce 系統或賬戶抽象中使用的二維 nonce 管理相比,位圖方法在相同並行度下將存儲開銷降低了 64 倍。這種高效性實現了高度的並行性(儘管並非無限),同時保持了最小的狀態擴展(每筆交易一位)。該設計巧妙地利用了巨大的 64 位 nonce 空間,對其進行了分區,同時又不損害確保安全性的順序級數。

向後兼容性

這種方法的一個關鍵優勢在於其無縫的向後兼容性。對於絕大多數用戶和應用程序而言,系統無需任何更改。舊版錢包將繼續運行,因為它們的交易會自動使用Bitmap結構中的Index零。系統為現有應用程序保留了熟悉的順序隨機數語義,同時為升級後的錢包解鎖了並行性。這種雙模式操作確保了生態系統的平穩過渡,而無需所有參與者進行協調升級。

然而,值得一提的是,用戶不應假設eth_getTransactionCount總是返回賬戶的總交易數。這個問題也存在於 2D Nonces 或 Hyperliquid 的方法中。需要注意的是, eth_getTransactionCount主要用於PendingNonceAt函數。隨著PendingNonceAt函數的重新設計,高級用戶可以丟棄/禁用此 RPC。對於使用此eth_getTransactionCount的其他服務(通常是瀏覽器),他們可以使用其本地數據庫來提取此數字。

誰能從 Nonce Bitmap 中受益?

  • 高頻交易者。無需複雜的 nonce 管理,即可同時提交多筆交易。無需在交易之間查詢 nonce 狀態,從而降低延遲。
  • MEV 搜索器。發送可並行處理的交易包,提高執行速度和成功率。
  • DeFi 高級用戶/鯨魚。同時執行涉及多種協議的複雜策略,無需擔心交易排序或交易卡頓。
  • 開發人員。構建能夠更高效地提交批量交易的應用程序,從而改善用戶體驗並降低操作複雜性。
  • 普通用戶。體驗沒有變化,系統仍然向後兼容現有錢包。

與其他方法的比較

方法原來的隨機的超液體2D 隨機數隨機數位圖
訂購是的部分的部分的部分的
驗證簡單的簡單的更復雜(結合基於時間的) - 與普通用戶的原始方法相同
- 對於高級用戶來說有點複雜
- 與普通用戶的原始方法相同
- 對於高級用戶來說有點複雜
並行性是的,無限制是的,約 100 筆交易是的,取決於nonceKey的大小。是的,最多 256 筆交易
交易次數簡單的簡單的不平凡中等,需要 API 更新不平凡
錢包兼容性是的對於普通用戶來說對於普通用戶來說
額外存儲空間跟蹤所有使用的隨機數8*100 字節(每個地址跟蹤 100 個已使用的隨機數) 8 * 活動nonceKeys每個地址 32 字節

結論

Nonce Bitmap 方法表明,經過深思熟慮的協議設計可以實現
在不影響安全性和兼容性的情況下,顯著提升了用戶體驗。通過觀察 64 位 nonce 空間的利用率,我們只需為每個帳戶額外增加 32 個字節即可解鎖 256 路並行。

對於低延遲鏈來說,交易延遲不再受區塊時間的限制,而是受用戶提交交易的速度限制。幸運的是,Nonce Bitmap 消除了這一瓶頸。


來源
免責聲明:以上內容僅為作者觀點,不代表Followin的任何立場,不構成與Followin相關的任何投資建議。
喜歡
收藏
評論