昨天的結論停在一句推卸責任的話:「歷史可取回性協定不提供,靠生態與 rollup 自己歸檔。」這句話是對的,但它把最重要的部分省略了——誰在歸檔、歸檔了什麼、機率是多少、缺口在哪裡。今天把這四題答完,而第一個要處理的是我昨天寫下的一個具體錯誤:我說「EIP-4444 的一年滾動窗口」。EIP-4444 現行條文裡沒有「一年」這個字。它寫的是 HISTORY_PRUNE_EPOCHS = 33024 epochs,換算 146.77 天,約四點八個月。舊版本確實是一年——舊值是 82,125 epochs,乘以 384 秒剛好等於 31,536,000 秒,精確的 365 天。新值把窗口砍成原本的 1/2.49,並且不再是一個獨立選定的數字,而是被綁在共識層的 MIN_EPOCHS_FOR_BLOCK_REQUESTS

這個綁定本身就是今天的第一個洞見。共識層那個常數的推導式是 MIN_VALIDATOR_WITHDRAWABILITY_DELAY + MAX_SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // 200,代入 256 + 100 × 65536 ÷ 200 = 256 + 32,768 = 33,024。它來自最大弱主觀性週期——也就是「一個離線太久的節點還能不能靠一個檢查點安全地追上鏈」的上限。所以以太坊對「歷史資料要留多久」的官方答案,不是「一年比較好記」,而是「留到弱主觀性同步不會壞掉為止,一秒都不多」。

把三個窗口並排,今天整篇文章的骨架就出來了:blob 的 4096 epochs(18.2 天)、執行層歷史的 33,024 epochs(146.77 天)、以及沒有上限的那一格。33,024 ÷ 4,096 = 8.0625,執行層歷史窗口正好是 blob 窗口的八倍多一點。而超過 146.77 天的一切,協定的答案是一個字都沒有。

📖 學

先把三條軸拆開:歷史區塊資料、歷史狀態、當前狀態

昨天我把「你以後拿得回來嗎」全部塞進 historical retrievability 一個詞裡。這不夠細。真正有三條互相獨立的軸,混在一起講就會得出錯誤的結論。

第一條軸:歷史區塊資料(historical block data)。 就是 header、body、receipt 這三樣東西。這是 EIP-4444 管的範圍,現在的規則是 33,024 epochs。丟掉它,你就不能回答「這筆交易當時上鏈了嗎」、「這個區塊裡有什麼」、「這筆交易的 log 是什麼」。

第二條軸:歷史狀態(historical state)。 就是「二〇二三年三月某一天,某個地址的餘額是多少」。這條軸和第一條的關係經常被講錯。以太坊基金會在 partial history expiry 的公告裡有一句話講得極精確:餘額在某個過去時點的值「不容易單靠歷史資料判定」,這種查詢需要帶有專門索引的 archive node。也就是說:就算你手上有完整的歷史區塊資料,你也不是自動就有歷史狀態——你得把它全部重放一次,或者去找一個已經替你重放過並且建了索引的 archive node。EIP-4444 完全沒有動這條軸,archive node 的數個 TB 是另一筆帳。

第三條軸:當前狀態的膨脹(state growth)。 就是每個全節點現在都必須持有的那份 state trie。這條軸的答案是 state expiry、stateless clients、Verkle/binary tree 那一整套,和前兩條軸的機制完全不共用。

所以那句流傳很廣的「EIP-4444 讓節點瘦身」要限定範圍:它省的是歷史 body 與 receipt,官方數字是 300 至 500 GB,目的是讓一個節點「舒服地裝進 2 TB 硬碟」。它不省 state,不省 archive,也不省 blob。

已經上線的:2025-05-01 起跑,2025-07-08 全客戶端到位

這件事的落地過程是一個很好的協定治理案例,而且時間點都可查。

決策是在二〇二四年十一月的曼谷 L1 Workshop 上達成的,實作計畫分兩階段。第一階段的日期是 2025-05-01——從那天起,執行層客戶端可以丟掉 Merge 之前的 block body 與 receipt。Sepolia 測試網被指定為預演場,同一天開始丟。2025-07-08,以太坊基金會發文宣布所有執行層客戶端都支援 partial history expiry。

Merge 的分界線是一個具體的區塊編號:15537393,二〇二二年九月十五日,那是最後一個 PoW 區塊。所以 Reth 的旗標寫成 --prune.bodies.pre-merge --prune.receipts.before 15537394(15537394 是第一個 PoS 區塊),Sepolia 對應的是 1450409。其他客戶端的介面各不相同,值得記一下形狀差異:

客戶端版本做法
Gethv1.16.0離線 geth prune-history;新節點 --history.chain postmerge
Nethermind1.32.2 起預設開啟只在新同步的節點上生效;關閉要 --Sync.AncientBodiesBarrier 0 --Sync.AncientReceiptsBarrier 0
Besu25.7.0storage prune-pre-merge-blocks + --history-expiry-prune,回收空間要等 24 至 48 小時
Erigonv3.0.12--history-expiry
Rethv1.5.0--prune.bodies.pre-merge --prune.receipts.before 15537394

Nethermind 從 1.32.2 起是預設開啟,這一格最值得注意。當一件事從「旗標」變成「預設」,它就從實驗變成了現實——而多數人是在完全沒有察覺的情況下,把自己節點上那段歷史丟掉了。

搭配的網路層協定是 EIP-7642(eth/69),狀態是 Final。它做了兩件事。第一,Status (0x00) 交握訊息裡拿掉了 Merge 之後就沒有意義的 td(總難度),換上 earliestBlocklatestBlocklatestBlockHash——節點現在會主動宣告自己手上有哪一段歷史,你不用先連上去試著要一次才知道對方有沒有。它還新增了 BlockRangeUpdate (0x11) 訊息,規範是「最多每 32 個區塊發一次」以免洗頻。EIP 的 Rationale 明講了加這個欄位的第三個理由:「為未來歷史過期窗口是動態的世界做準備。」

第二件事更荒謬也更有教育意義:eth/69 把 receipt 裡的 Bloom 欄位從網路傳輸格式中刪掉了。原因是沒有任何客戶端把 bloom 存在磁碟上——它是可以重算的。但舊的網路規格要求傳輸它,於是每一次同步,提供資料的節點都要現場重新生成大約 530 GiB 的 bloom filter(EIP 的算式是二十三億筆交易 × 256 位元組;親手算是 588.8 GB = 548.4 GiB,和 EIP 的數量級一致),把它們送過網路,接收方驗完之後也不存。壓縮後至少還是 95 GiB。一個純粹的儀式性支出,存在了好幾年。 eth/69 之後 receipt 的網路編碼變成扁平的 [tx-type, post-state-or-status, cumulative-gas, logs],接收方自己重算 bloom 來驗 receipt hash。

更正一:EIP-4444 不丟 header,所以恢復回來的歷史永遠可以驗證

這是最常被講反的一點,而它決定了整件事的安全性質。

實作計畫的「What Remains Unchanged」第一條寫得很清楚:Merge 之前的 block header 仍然必須經由 DevP2P 提供。 只有 body 和 receipt 可以丟。EIP-4444 的 Abstract 雖然把 header 也列在可修剪的清單裡(那是完整滾動窗口的最終目標),但第二階段的計畫明確把「丟 header」排除在 rolling window 的必要條件之外,理由是 header 佔的資料量很小、丟掉的收益低,而且「客戶端需要時間在新範式下運作」之後再評估。

為什麼這件事重要?因為 header 鏈就是驗證器。基金會的公告裡有一句話值得逐字記住:執行層將繼續提供所有 header,「這使得從創世區塊開始的密碼學驗證成為可能」,並且「有助於避免客戶端接受無效的歷史資料」。翻成執行語言:你從任何一個不可信的來源——torrent、S3 桶、某個機構的鏡像、一個陌生 peer——下載回來的歷史區塊,都可以對著你本地那條完整的 header 鏈逐一驗證。 資料的來源可以完全不可信,結果仍然是密碼學上確定的。

這就是基金會用的那個詞:1-of-N 信任假設。只要全世界有一個實體願意提供那段歷史,任何人都能取回並驗證。這和昨天講的 DAC 3/5 門檻是兩種完全不同的東西——DAC 是 M-of-N 的活性假設(要 M 個人同時願意配合),歷史歸檔是 1-of-N 的存在假設(只要有一份還在)。而且後者驗得動,前者驗不動。

era1 檔案格式把這件事做成了可攜的產物。era1 是 e2store 容器,以 8192 個區塊為一個檔案單位(這個上限來自它的 Accumulator 設計),覆蓋範圍從 2015-07-30 的創世到 15537393 這個 Merge 區塊。檔案結構是 Version | block-tuple* | other-entries* | Accumulator | BlockIndex,每個 block-tuple 內含壓縮後的 header、body、receipt 與 total difficulty。Accumulator 是把最多 8192 個 header-record 組成一個 SSZ list,再取 hash_tree_root——換句話說,每一個 era1 檔案自帶一個可獨立驗證的承諾。15,537,393 ÷ 8,192 = 1,896.65,所以整條 PoW 鏈是 1,897 個 era1 檔案。ethPandaOps 同時在推更新的 eraE 格式,那是涵蓋 Merge 前後完整歷史的版本,並提供 SHA256 checksum 檔案。

所以「EIP-4444 之後歷史就不可信了」是錯的。正確的說法是:歷史的取得從一個有協定保證的操作,降級成一個需要你自己找來源的操作;但驗證那一步完全沒有降級。

沒上線的:滾動窗口的時程是「Not set」,而客戶端分裂成三條路線

這是今天查證下來最該說出口的一件事。第二階段——真正的滾動窗口,也就是把「丟到 Merge 為止」變成「丟到距今 33,024 epochs 為止」——實作計畫上的 Timeline 欄位寫的字是 Not set

它列了四個前置條件:滾動窗口的實作、必須「清楚地證明我們有一個多層次的資料保存方案」、靠近鏈頭的區塊要有標準化的歸檔格式(如 ERA)、以及 post-merge 的 P2P 解法必須被客戶端一致同意並支援。最後這一項是真正的卡點,因為到目前為止並沒有一致同意。同一份文件裡各客戶端自報的方案是這樣:

客戶端方案狀態
GethPortal Network規劃階段
Reth短期靠 era 檔與 torrent;長期或許 Portal擔憂只能從其他 Reth 節點取 pre-merge 資料,同步會嚴重變慢
ErigonEIP-7801 + 原生 torrent7801 規劃階段,torrent 可能已上線
Besu短期看 EIP-7801,長期 Portal規劃階段
NethermindPortal 與 EIP-7801 雙軌實作階段
NimbusPortal Network實作階段
EthereumJSPortal Network實作階段

七個客戶端,三條路線,兩套網路堆疊。 Portal 走 Discv5 / UDP,EIP-7801 走 DevP2P / TCP,era 檔與 torrent 走 HTTP 與 BitTorrent。文件自己在「Networking Stack」那一節把這個問題原封不動地列成一個開放問題:「這兩者之間的取捨是什麼?」

窗口大小也還沒定。文件裡提的候選是 一百萬個區塊,理由是「讓執行層與共識層可以共用一個資料庫」,並附了一個很誠實的替代方案:「每年直接同意一個新門檻——實作簡單,但需要每年維護一次。」順帶算一下:一百萬個區塊 × 12 秒 = 138.89 天,和 33,024 epochs 的 146.77 天相差不到 6%。這兩個數字本質上是同一個窗口的兩種寫法。

還有兩份和主線相關但狀態值得知道的 EIP。EIP-7927(History Expiry Meta)是 Stagnant——我昨天把它寫成 EIP-4444 的「後續」,語感上暗示它是活的路線,這需要更正;它是 Piper Merriam 在 2025-03-28 建立的一份流程文件,已經停滯。EIP-7639(eth/70 - Cease serving history before PoS)也是 Stagnant——它原本要用一個新的 eth/70 版本來硬性禁止提供 15537393 之前的 body 與 receipt,最後沒走這條路,實際落地的是 eth/69 的「宣告自己有什麼」而不是「禁止提供」。這個轉向本身很有意思:協定選擇了描述性的機制,而不是規範性的機制。

EIP-7801:歸檔的抽樣機率,底數是 0.9

昨天推導抽樣機率時,結論是「底數來自碼率」——一維碼率 1/2 給你 (1/2)^k,二維 Reed-Solomon 給你 (3/4)^k。今天出現了同一個推導的第三個實例,而它的底數既不是 1/2 也不是 3/4。

EIP-7801 提議一個叫 etha 的新子協定,讓節點把整條鏈的歷史切片,並用一個十位元的 bitmask 宣告自己保管哪幾片。每一位代表 106,496 個區塊的跨距,bitmask 每 1,064,960 個區塊循環一次。這兩個數字不是隨便挑的:106,496 = 13 × 8,192,1,064,960 = 130 × 8,192 = 10 × 106,496——都是 era1 單檔上限 8192 的倍數,EIP 的 Rationale 明講理由就是「可以直接用 era1 檔案來儲存與表示」。順手算一下,1,064,960 個區塊 × 12 秒 = 147.91 天,和 33,024 epochs 的 146.77 天只差 0.8%,雖然 EIP 沒說這是刻意的。

規範要求每個節點至少開一位、也就是至少涵蓋 10% 的鏈歷史,並且承諾隨著新區塊產生繼續保管對應的片段。於是 Security Considerations 給出這個式子:

P(某一片段完全取不到) = (0.9)^n     # n = peer 數量

代進去:25 個 peer 是 7.18%,32 個 peer 是 3.43%,兩個數字和 EIP 寫的 7% 與 3.4% 一致。想壓到千分之一以下需要 ln(0.001) / ln(0.9) = 65.56,也就是 66 個 peer

把三個底數放在一起,昨天那個推導的普適性才顯出來。

機制冗餘來源單次失敗機率達到約 10⁻³ 所需次數
PeerDAS 一維 RS,碼率 1/2糾刪碼≤ 1/2約 10 次
Celestia 二維 RS糾刪碼≤ 3/4約 25 次
EIP-7801 歷史分片自願保管率 10%0.966 個 peer

底數從 0.5 掉到 0.9,代價就從十次抽樣暴增到六十六個 peer。這不是誰設計得比較差,而是一個範疇差異:DAS 的冗餘是密碼學造出來的,可以隨意調碼率;歸檔的冗餘是別人自願提供的,你只能拜託大家多存一點。 而且 EIP 自己在同一節裡把最大的假設寫了出來:這個式子「假設網路上參與 EIP-7801 分片的節點是隨機分佈的」,而且「要讓完整的分片歷史在網路上可得並充分複製,可能需要多數客戶端把它設為預設」。那個 0.9 是一個尚未存在的世界的參數。

儲存端的算術倒是很漂亮:節點省下約 90% 的鏈歷史儲存,而且未來的歷史增長速率也只承擔 10%。

更正二:eth_getLogs 不是被歷史過期弄壞的,它早就壞了

「EIP-4444 會讓 eth_getLogs 沒辦法用」這個說法有一半是對的,但它把因果講反了,而且會讓你把注意力放錯地方。

eth_getLogs 的加速結構是 header 裡的 logs_bloom,固定 2048 位元,每個 address 或 topic 設定其中 3 個位元。EIP-7745 的 Motivation 段落給了現況的數字:主網區塊平均產生超過 1000 個 address 與 topic。2048 位元的濾波器在 1000 個值、每個設 3 位的情況下早就飽和了,假陽性率高到「濾波器實質上已經沒用」。EIP 直接說,要把假陽性壓回可接受的水準,bloom 需要放大約十倍,那會讓 block header 膨脹到大約 3 KB。而即使放大了,搜尋仍然需要存取整條 header 鏈——只搜最近一年的歷史就要付出超過 6 GB 的資料存取,而且那還沒算上真正去取 log 的部分。

換句話說:今天你打 eth_getLogs 查一個長區間,實際上跑的不是 bloom filter,而是某個 RPC 服務商自己建的私有索引。而 EIP-7745 的 Motivation 對這件事下了一句很重的判斷:「使用者依賴受信任的伺服器與索引器,這某種程度上就違背了以太坊的全部意義。」所以 eth_getLogs 的信任問題是既存的,歷史過期只是把它從「可以裝作沒看到」變成「不能不處理」。

EIP-7745 的處理方式是把 logs_bloom 從 header schema 裡換掉,改成 log_index_root——一個新的 LogIndex 結構的根雜湊。這是共識層變更,要硬分叉。設計是把所有 log event、交易標記、區塊標記映射到一個全域線性索引空間,並對每一段固定長度的索引空間生成一張二維稀疏 bit map(filter map)。提議的常數是:

MAP_WIDTH        = 2^24 = 16,777,216
MAP_HEIGHT       = 2^16 = 65,536
VALUES_PER_MAP   = 2^16 = 65,536
MAPS_PER_EPOCH   = 2^10 = 1,024
MAX_EPOCH_HISTORY= 2^24
MAX_ROW_LENGTH   = [8, 168, 2728, 10920]
MAPPING_FREQUENCY= [2^10, 2^6, 2^2, 1]

假陽性率的估計式值得親手算一次,因為它把設計意圖攤得很開:

E[假陽性 / 每張 map]
  = VALUES_PER_MAP² / MAP_WIDTH / MAP_HEIGHT
    × (1 + VALUES_PER_MAP / MAX_ROW_LENGTH[0] / MAP_HEIGHT)
  = 65536² / 2^24 / 2^16 × (1 + 65536 / 8 / 65536)
  = 0.00390625 × 1.125
  = 0.0043945

EIP 寫的是「約 0.0044」。再往下推:平均一個區塊產生略多於 1000 個 map value,一張 map 裝 65,536 個,所以一張 map 大約覆蓋 65.5 個區塊;0.0044 個假陽性/map 換算成 大約每 14,000 個區塊出現一次假陽性,而整條鏈的歷史總共預期約 1,200 次。這比「濾波器實質沒用」好了不知道幾個數量級。

還有兩個數字很關鍵。第一,不打算搜尋或提供證明的驗證者可以只維護一份最小索引狀態,序列化後是 21,300,808 位元組,約 20.31 MiB——這是「在任何時點初始化這個結構所需的資料量」的估計。第二,filter map 的額外磁碟成本是實際 log 大小的 15% 至 20%。而最切題的一句在 Rationale 裡:這個 Merkle 樹結構「使得整個 epoch 連同對應的 Merkle 子樹可以輕易丟棄,讓 log 索引的歷史過期實作變得簡單」。EIP-7745 從一開始就是為了「會被丟掉的歷史」而設計的。

它的 Requires 是 EIP-7916(ProgressiveList SSZ 型別),狀態是 Draft。它有沒有進 Glamsterdam,我沒有查到權威的收斂說法,已標存疑

rollup 這一側:那個旗標叫什麼,以及誰真的在跑

昨天的實踐項目裡我叫你去 blobscan 看十八天前的 blob,並指出「瀏覽器還給得出資料,是因為瀏覽器自己歸檔了」。今天把這件事從觀察變成配置。

OP Stack。 op-node 需要一個 L1 beacon 端點才能取 blob。標準 beacon 節點在十八天後修剪 blob,所以官方文件明列了兩種你必須配歸檔器的情況:(a) 你從一個超過十八天的快照或創世開始同步一個新節點;(b) 你的節點離線超過十八天。旗標是:

--l1.beacon-fallbacks value      ($OP_NODE_L1_BEACON_FALLBACKS)

這個旗標改過名字。 舊名是 --l1.beacon-archiver,舊環境變數是 $OP_NODE_L1_BEACON_ARCHIVER,兩者現在都還是別名。你在網路上看到的教學九成寫的是舊名——會動,但要知道它已經被改成複數的 fallbacks,而那個複數是語義上的:它接受多個後備端點。三個選項:跑一個關掉 blob 修剪的 beacon 節點(Lighthouse 用 --prune-blobs=false)、跑一個專用的歸檔服務、或者用第三方。官方文件對第二個選項的評語是「比跑一個不修剪的完整 beacon 節點輕量」。

那個專用服務長什麼樣子。 base/blob-archiver 拆成兩個元件:一個 Archiver 追蹤信標鏈並把 blob 寫進儲存後端,一個 API 實作 blob sidecars API 讓客戶端來取。後端支援兩種:寫到磁碟目錄,或寫到 S3 桶(S3 後端也能用 Google Cloud Storage)。所以「Base 的歷史 blob 歸檔」在物理上就是一個 S3 桶加一個假裝自己是 beacon 節點的 HTTP 服務。 這件事沒有任何密碼學成分,但因為 blob 的 versioned_hash 在 L1 上是永久的,你取回來的每一個 blob 都能用 KZG 驗到底——又是一次 1-of-N。

Arbitrum。 形狀一樣但介面不同,設定走 JSON:parent-chain.blob-client.beacon-url,另有 secondary-beacon-urlauthorization。錯誤訊息長這樣:failed to get blobs: expected at least six blobs for slot [N] but only got 0——「expected at least six」這個 six 就是昨天講的 MAX_BLOBS_PER_TX = 6,批次提交者一筆交易塞滿六個 blob,節點回頭要,拿到零個。文件同時列出各共識客戶端要怎麼設定才願意提供歷史 blob:Prysm 7.1.0 以上要 --blob-retention-epochs --semi-supernode --enable-backfill,Lighthouse 用 --prune-blobs false--blob-prune-margin-epochs,Lodestar 用 --chain.archiveDataEpochs

EthStorage 的路線比較有野心。 OP Stack 的 BatchInbox 預設是一個 EOA 地址——刻意不是合約,為了省下 EVM 執行的 gas。EthStorage 的做法是把它換成一個合約,合約收到 blob 交易後呼叫 EthStorage 儲存合約的 put(key, blob),而 key 就是那個 blob 的 blobhash,合約用自己的 ETH 餘額支付預付儲存費。這是一個很乾淨的設計:歸檔的付費點被搬到批次提交的那一刻,而不是事後拜託誰去存。代價是批次提交者要改——它得先判斷 BatchInbox 是合約還是 EOA,得預估 gas 而不能依賴內在 gas,還得處理交易狀態驗證與錯誤處理。

文件裡還埋了一個非常具體的實作陷阱,值得抄下來:至少部分共識客戶端(如 Prysm)在被問到已過期的 blob 時,回的是 HTTP 200 加一個空陣列,而 OP Stack 的程式碼期待的是一個錯誤。 於是取回歷史 blob 這件事會在一個沒有人報錯的地方安靜地失敗。這種「成功地回報了空」的失敗模式,是所有降級路徑裡最難查的一類。

更正三:「重放交易重建狀態」不是對每一條 rollup 都成立

我昨天寫「要有人根據批次資料重算 L2 狀態」,語感上假設批次資料是交易,重建就是重放。這對 OP Stack 系成立,對 zkSync Era 系不成立,而差異的方向和直覺相反。

zkSync Era 是狀態差異型(state diff-based) rollup。它發布到 blob 或 calldata 的 pubdata 不是交易,而是狀態變更:如果帳戶 A 的儲存槽 S 變成了值 V,它發布的就是三元組 (A, S, V)。官方文件的原話是,觀察所有這些三元組的使用者就能還原 zkSync 的狀態。收費模型也是照著這個走的——你寫入一個儲存槽才產生 pubdata 費用,不是你發了幾筆交易。

這造出一組有趣的不對稱。對「當前狀態」來說,state diff 型反而更容易重建:你不需要一個 EVM、不需要重放任何東西,把所有 diff 依序套上去就是最終狀態。實際的工具是 eqlabs/zksync-state-reconstruct,它從 L1 上的 commit 區塊重建 zkSync 狀態,做法是過濾出所有送到 L1 zkSync 合約的 commitBatches 交易、只取那些已經被對應的 executeBatches 引用過的,然後依序套用。但對「歷史」來說,state diff 型是永久性的資訊損失:協定從來沒有發布過那些交易,所以就算你有全部的 blob,你也永遠拿不回「二〇二五年某天某個地址發了什麼交易」。那份資訊只存在於排序者的資料庫裡。

反過來,OP Stack 系發布壓縮後的交易批次,理論上你可以重放出每一個歷史狀態,代價是你得跑完整條鏈的推導與執行。

所以昨天那個「有人算得出證明」的前提,對不同的 rollup 要問不同的問題。 對交易型 rollup 問的是「還有人留著批次資料嗎」;對 state diff 型 rollup 問的是「還有人留著 diff 嗎」,而且要接受歷史交易在協定層面本來就不存在——這不是歸檔沒做好,是設計選擇。任何把兩者用同一張「DA:Ethereum(blobs)」表格並排的比較,都藏掉了這一格。

算一次歸檔的帳:每年 4.39 TiB,而整條 PoW 鏈只有 400 GB

把數字攤開,「靠生態歸檔」這句話的重量才出得來。

昨天算過十八天窗口在目標十四之下是 224 GiB。除以 18.2 天,得到每天 12.30 GiB。乘以三百六十五:

目標 14 個 blob:14 × 131,072 B × 7,200 slot/日 = 13,212,057,600 B/日
                = 12.30 GiB/日 = 4.39 TiB/年
上限 21 個 blob:                = 18.46 GiB/日 = 6.58 TiB/年

對照組:EIP-4444 要處理的整條 Merge 前 PoW 鏈的 body 與 receipt,是 300 至 500 GB。 也就是說,把 blob 永久歸檔的一年成本,比以太坊前七年全部歷史資料的總量還要大一個數量級。而如果 blob 容量繼續按 BPO 往上調,這個斜率會跟著上去。

實際的數字可以在 Blobscan 上對:可查到的統計是總計 19,328,970 個 blob、2.3 TiB(親手驗算 19,328,970 × 131,072 = 2.304 TiB,吻合)。這個快照的日期我無法確認,已標存疑,但量級是對的:Dencun 到現在兩年半,累積 2.3 TiB,而未來一年會再加 4.4 TiB——歸檔的負擔正在加速,而承擔它的是一群沒有協定義務、沒有協定收入的自願者。 Blobscan 的做法是往 IPFS 遷移並把主網 blob 永久 pin 住;昨天提到的 Blockscout、The Graph、EthStorage 各有各的路。這些都是真實的努力,但沒有一個是協定。

回頭看昨天那句「blob 比 calldata 便宜一個數量級,因為節點只承諾存十八天」——今天可以把它補完:便宜的那個數量級,正好等於歸檔負擔被外部化的那個數量級。 這不是批評,這是定價的算術。

時程:滾動窗口在 Hegota,而 Hegota 在 Glamsterdam 之後

最後把路線圖上的位置標清楚,並且明確標示哪些是二手來源。

Glamsterdam 在二〇二六年六月中進入最後階段的 devnet。它的範圍裡有 EIP-7928(區塊層級存取清單)、EIP-7886(延遲執行)、EIP-7732(ePBS)。核心開發者已經替 Glamsterdam 之後的升級定名為 Hegota,由執行層的 Bogota 與共識層的 Heze 組合而成;報導指出 FOCIL(EIP-7805)是 Hegota 的頭條項目,而狀態過期與歷史過期機制被列在 Hegota 的討論範圍裡。滾動歷史過期正在 Sepolia 上實測。

兩個必須標記的不確定性。第一,Glamsterdam 的主網時程,二手來源互相矛盾:有寫「二〇二六上半年」的、有寫「Q3 / 下半年」的、也有寫「九月到十二月」的。這一格昨天就標了存疑,今天仍然存疑。第二,Verkle 或 binary tree 是不是 Hegota 的頭條、能不能真的把節點儲存負擔砍掉約 90%,我看到的全是二手報導,沒有找到一級文件,已標存疑

但有一件事是確定的,而且它就是今天的收束:歷史區塊資料的滾動窗口、歷史狀態的可取回性、blob 的永久歸檔,這三件事目前沒有任何一件有已排定的協定級解答。 第一件卡在客戶端沒有一致的 P2P 方案上,第二件從來不在任何 EIP 的範圍裡,第三件連提案都沒有。昨天我說「協定不負責,生態負責」,今天可以說得更精確:協定負責到 146.77 天,而 146.77 天之後那一整段,是一個 1-of-N 的賭注,賭的是總有人願意繼續付儲存費。 好消息是這個賭注的驗證那一步是密碼學的——header 鏈還在,KZG 承諾還在,你永遠能檢查別人給你的東西是不是真的。壞消息是,能檢查不等於有人給。

🧠 記

  • EIP-4444 現行條文的 HISTORY_PRUNE_EPOCHS = 33024 epochs = 1,056,768 slots = 12,681,216 秒 = 146.77 天(約 4.82 個月),不是一年。 舊值 82,125 epochs × 384 秒 = 31,536,000 秒 = 精確 365 天,新值砍到 1/2.49。
  • 33,024 綁在共識層的 MIN_EPOCHS_FOR_BLOCK_REQUESTS:MIN_VALIDATOR_WITHDRAWABILITY_DELAY + MAX_SAFETY_DECAY × CHURN_LIMIT_QUOTIENT // 200 = 256 + 100 × 65,536 ÷ 200 = 256 + 32,768 = 33,024,來源是最大弱主觀性週期
  • 33,024 ÷ 4,096 = 8.0625,執行層歷史窗口是 blob 窗口的八倍多。
  • 三條互相獨立的軸:歷史區塊資料(EIP-4444,省 300–500 GB)、歷史狀態(archive node,EIP-4444 完全不管)、當前狀態膨脹(state expiry / Verkle)。基金會明講:光有歷史資料也不容易判定某個過去時點的餘額。
  • 日期線:曼谷 L1 Workshop(2024-11)決策 → 2025-05-01 可丟 Merge 前 body/receipt、Sepolia 同日 → 2025-07-08 全執行層客戶端支援。Nethermind 1.32.2 起預設開啟。
  • Merge 分界:最後一個 PoW 區塊 15537393(2022-09-15),第一個 PoS 區塊 15537394。Reth 旗標寫的就是 --prune.receipts.before 15537394;Sepolia 是 1450409。
  • EIP-4444 不丟 header——實作計畫的「What Remains Unchanged」第一條。所以任何來源取回的歷史都能對著本地 header 鏈驗證,這是1-of-N 存在假設,和 DAC 的 M-of-N 活性假設是兩種東西。
  • era1 = e2store 容器,單檔 8192 個區塊上限(來自 Accumulator 設計),Accumulator 是最多 8192 個 header-record 的 SSZ list 取 hash_tree_root;整條 PoW 鏈 15,537,393 ÷ 8,192 = 1,897 個檔案。更新的 eraE 格式涵蓋 Merge 前後。
  • EIP-7642(eth/69)是 Final:Status 拿掉 td、加上 earliestBlock/latestBlock/latestBlockHash,新增 BlockRangeUpdate (0x11)(最多每 32 區塊一次)。順手刪掉 receipt 的 Bloom——舊規格逼每次同步現場重生 530 GiB(2.3e9 筆 × 256 B;親手算 588.8 GB = 548.4 GiB)的 bloom,壓縮後仍 95 GiB,而收發雙方都不存它
  • EIP-7927(History Expiry Meta)與 EIP-7639(eth/70)都是 Stagnant。 實際落地的不是「禁止提供」的 eth/70,而是「宣告自己有什麼」的 eth/69——協定選了描述性機制而非規範性機制。
  • 滾動窗口第二階段的 Timeline 是 Not set,卡點是 post-merge 的 P2P 解法沒有客戶端共識。七個客戶端三條路線:Portal(Discv5/UDP)、EIP-7801(DevP2P/TCP)、era 檔與 torrent。窗口大小候選是 一百萬個區塊 = 138.89 天,和 146.77 天差不到 6%。
  • EIP-7801 的底數是 0.9,不是 0.5 或 0.75。 十位元 bitmask、每位 106,496 = 13 × 8,192 個區塊、循環 1,064,960 = 130 × 8,192 個區塊(= 147.91 天),要求每節點至少涵蓋 10% 歷史。P = 0.9^n:25 peer = 7.18%、32 peer = 3.43%、要進 10⁻³ 需要 66 個 peer。EIP 自陳這假設參與節點隨機分佈,且可能需要多數客戶端設為預設。
  • eth_getLogs 早就壞了,不是歷史過期弄壞的。 主網區塊平均 >1000 個 address+topic,2048 位元的 bloom 早已飽和;要回到可用需放大約十倍,header 會膨脹到約 3 KB;而且只搜最近一年就要存取 >6 GB
  • EIP-7745 把 header 的 logs_bloom 換成 log_index_root(共識變更,需硬分叉)。常數 MAP_WIDTH=2^24MAP_HEIGHT=2^16VALUES_PER_MAP=2^16MAPS_PER_EPOCH=2^10MAX_ROW_LENGTH=[8,168,2728,10920]。假陽性 = 65536²/2^24/2^16 × (1+65536/8/65536) = 0.0044/map,約每 14,000 區塊一次,全鏈預期約 1,200 次。最小索引狀態 21,300,808 B = 20.31 MiB,filter map 額外磁碟成本是 log 本身的 15–20%。Requires EIP-7916,狀態 Draft,是否進 Glamsterdam 已標存疑
  • op-node 的旗標現在叫 --l1.beacon-fallbacks(舊名 --l1.beacon-archiver 仍是別名)。base/blob-archiver = Archiver(追鏈寫入)+ API(假裝自己是 beacon)+ 磁碟或 S3 後端。Arbitrum 走 parent-chain.blob-client.beacon-url / secondary-beacon-url;錯誤訊息裡的 “expected at least six blobs” 那個 six 就是 MAX_BLOBS_PER_TX = 6
  • 共識客戶端提供歷史 blob 的旗標:Lighthouse --prune-blobs=false / --blob-prune-margin-epochs;Prysm 7.1.0+ --blob-retention-epochs --semi-supernode --enable-backfill;Lodestar --chain.archiveDataEpochs
  • 實作陷阱:Prysm 對已過期的 blob 回 HTTP 200 加空陣列,而 OP Stack 期待錯誤。 這是一個沒有人報錯的安靜失敗。
  • zkSync Era 是 state diff 型 rollup,發布的是 (A, S, V) 三元組而不是交易,收費按儲存槽寫入而不是交易數。後果是不對稱的:當前狀態更容易重建(套 diff 即可,不需要 EVM,工具是 eqlabs/zksync-state-reconstructcommitBatches 並取被 executeBatches 引用過的),但歷史交易在協定層面本來就不存在,永久損失。
  • 歸檔的帳:目標十四個 blob = 每天 12.30 GiB = 每年 4.39 TiB;上限二十一 = 每天 18.46 GiB = 每年 6.58 TiB。對照 EIP-4444 要處理的整條 PoW 鏈 body+receipt 是 300–500 GB——永久歸檔 blob 的一年成本,比以太坊前七年全部歷史的總量還大一個數量級。 Blobscan 可查到 19,328,970 個 blob / 2.3 TiB(驗算吻合;快照日期已標存疑)。
  • 路線圖:Glamsterdam 於 2026-06 進入最後 devnet(主網時程二手來源矛盾,已標存疑);之後的升級定名 Hegota(EL Bogota + CL Heze),狀態過期與歷史過期在其討論範圍內,滾動歷史過期正在 Sepolia 實測。Verkle 是否為頭條、是否砍 90% 儲存,只有二手來源,已標存疑

✍️ 實踐

今天做五件事,二十到四十分鐘,前四件不需要跑節點。

一、把三個窗口和三個底數各算一次(五分鐘)。

# 三個窗口
33024 * 32 * 12 / 86400        # 預期 146.773  ← EIP-4444 現行值
82125 * 384 / 86400            # 預期 365.0    ← 舊值,剛好一年
33024 / 4096                   # 預期 8.0625   ← 執行層是 blob 的幾倍
1_000_000 * 12 / 86400         # 預期 138.889  ← 候選窗口
1_064_960 * 12 / 86400         # 預期 147.911  ← EIP-7801 的 bitmask 循環
 
# 常數推導,確認 33024 不是憑空來的
256 + 100 * 65536 // 200       # 預期 33024
 
# 三個底數,達到 1e-3 各要幾次
from math import log
log(1e-3)/log(0.5)             # 一維 RS:約 9.97  → 10 次
log(1e-3)/log(0.75)            # 二維 RS:約 24.0  → 25 次
log(1e-3)/log(0.9)             # EIP-7801:約 65.6 → 66 個 peer

看到 146.773365.0 並排,以及 9.97 / 24.0 / 65.6 這三個數字,你就同時修好了昨天和今天的兩個誤解。

二、確認自己的節點到底有沒有丟歷史(五分鐘,有節點才做)。 對照上面那張客戶端表,查一下你(或你的服務商)跑的是哪個版本、有沒有帶那個旗標。特別注意 Nethermind 1.32.2 以上是預設開啟的——如果你跑的是 Nethermind 而從來沒設過任何相關旗標,你的節點很可能已經沒有 Merge 前的 body 與 receipt 了。驗證方法很直接:對一個 Merge 前的區塊打 eth_getBlockByNumber,帶 true 要完整交易列表,看它給不給。

三、算你關心的那條 rollup 的歸檔帳(十分鐘)。 到 blobscan 查那條鏈一天用掉幾個 blob,乘以 131,072 位元組,得到每天的位元組數;乘以三百六十五得到年增量。然後問一個具體的問題:這條鏈有沒有公開說明誰在歸檔超過十八天的 blob、用什麼後端、由誰付錢? 如果答案是「某個瀏覽器好像有」,那就是昨天講的 1-of-N 裡的那個 1,而且你不認識他。

四、辨認你關心的 rollup 是交易型還是 state diff 型(五分鐘)。 去它的官方文件找 pubdata 或 batch 的說明,看它發布的是交易還是狀態變更。判別的捷徑是看收費模型:按交易 calldata 大小收費的是交易型,按儲存槽寫入次數收費的就是 state diff 型。然後接受對應的結論——state diff 型的歷史交易不是「歸檔沒做好」,是協定從來沒發布過。

五、實際配一次 blob 歸檔後備(十五分鐘,跑 OP Stack 節點才做)。op-node 加上 --l1.beacon-fallbacks,指到一個歸檔端點。然後刻意驗證一次那個安靜失敗:對你的 beacon 端點打一個十八天前的 slot 的 blob sidecars 請求,看它回的是錯誤,還是 HTTP 200 加一個空陣列。如果是後者,你就親眼看到了那個沒有人會報錯的失敗模式。

自我檢查

  1. 我能說出 EIP-4444 現行條文的常數名、值、換算天數,以及那個值是從哪個共識層常數繼承來的?
  2. 我能解釋為什麼「EIP-4444 之後歷史就不可信了」是錯的?(關鍵詞:header 仍須提供、1-of-N 存在假設、era1 的 Accumulator)
  3. 我能說出滾動歷史過期目前卡在哪一個前置條件上,以及七個客戶端分裂成哪三條路線?
  4. 我能把昨天的碼率推導套到 EIP-7801 上,說出底數是多少、為什麼是那個數、以及要幾個 peer 才進 10⁻³?
  5. 我能說出:對一條 state diff 型 rollup,「重建狀態」和「取回歷史交易」哪一件更容易、哪一件是永久不可能?

🔗 延伸學習

💬 問 AI

我在查以太坊的歷史資料保存機制,以下是我今天整理的數字與主張。
請逐條檢查,對的說對,錯的指出錯在哪並給出正確值與出處連結。
查不到就直接說「查不到」,不要用推測填空,也不要幫我圓場。
今天是 2026-09-04。
 
【EIP-4444 參數】
1. EIP-4444 現行條文的 HISTORY_PRUNE_EPOCHS = 33024 epochs
   = 1,056,768 slots = 12,681,216 秒 = 146.77 天(約 4.82 個月),而不是「一年」。
   舊版本的值是 82,125 epochs(× 384 秒 = 精確 365 天)。請確認這個修改與生效版本。
2. 33024 綁定共識層的 MIN_EPOCHS_FOR_BLOCK_REQUESTS,推導式是
   MIN_VALIDATOR_WITHDRAWABILITY_DELAY + MAX_SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // 200
   = 256 + 100 * 65536 // 200 = 33024,來源是最大弱主觀性週期。請驗算並確認常數值。
3. EIP-4444 的狀態是 Draft;EIP-7927(History Expiry Meta)與 EIP-7639(eth/70)
   都是 Stagnant;EIP-7642(eth/69)是 Final。請逐一確認 2026-09-04 當下的狀態。
 
【已上線的部分】
4. 決策在 2024-11 曼谷 L1 Workshop;2025-05-01 起可丟 Merge 前 body/receipt;
   2025-07-08 以太坊基金會宣布全執行層客戶端支援 partial history expiry;
   官方省下的空間是 300–500 GB。
5. Merge 分界是最後一個 PoW 區塊 15537393(2022-09-15),第一個 PoS 區塊 15537394。
6. Nethermind 從 1.32.2 起「預設開啟」history expiry。這個說法還成立嗎?
   其他客戶端(Geth v1.16.0、Besu 25.7.0、Erigon v3.0.12、Reth v1.5.0)
   目前有沒有改成預設開啟?
7. EIP-4444 不丟 header:實作計畫的「What Remains Unchanged」第一條
   規定 Merge 前的 header 仍須經 DevP2P 提供。這個說法對嗎?
8. era1 單檔上限 8192 個區塊,來自 Accumulator(最多 8192 個 header-record 的
   SSZ list 取 hash_tree_root);整條 PoW 鏈是 1,897 個檔案。請驗算。
   另外 eraE 格式現在的成熟度與覆蓋範圍是什麼?
 
【滾動窗口】
9. 實作計畫 Phase 2(rolling window)的 Timeline 欄位是「Not set」,
   卡點是 post-merge 的 P2P 解法沒有客戶端共識。
   請告訴我:2026-09-04 當下滾動歷史過期有沒有排定的主網時程?
   Sepolia 上的實測進展到哪裡?
10. 候選窗口大小是「一百萬個區塊」(= 138.89 天),理由是讓 EL 與 CL 共用一個 db。
    這個提案還在桌上嗎?最後選了什麼?
11. 七個客戶端的方案分歧(Geth/Nimbus/EthereumJS → Portal;
    Erigon → EIP-7801 + torrent;Reth → era 檔 + torrent;
    Besu/Nethermind → 雙軌)。2026 年這張表變了嗎?
    Portal Network 的 History Network 現在的實際可用度如何?
    block range queries 有沒有做完?
 
【EIP-7801 機率】
12. 十位元 bitmask、每位 106,496 = 13 × 8,192 個區塊、循環 1,064,960 = 130 × 8,192
    個區塊(= 147.91 天),每節點至少涵蓋 10% 歷史。
13. P = 0.9^n:25 peer = 7.18%、32 peer = 3.43%、要進 1e-3 需 66 個 peer。請驗算。
14. 承上:這個 0.9 的底數和 DAS 的碼率底數(一維 1/2、二維 3/4)是不同範疇的東西
    ——前者是自願保管率、後者是密碼學冗餘。這個對照公平嗎?
 
【log 索引】
15. 主網區塊平均產生超過 1000 個 address + topic,2048 位元的 logs_bloom 已飽和;
    要回到可用需放大約十倍,header 會膨脹到約 3 KB;只搜最近一年要存取超過 6 GB。
    這幾個數字都出自 EIP-7745 的 Motivation 嗎?現在還準確嗎?
16. EIP-7745 假陽性率 = VALUES_PER_MAP^2 / MAP_WIDTH / MAP_HEIGHT
    × (1 + VALUES_PER_MAP / MAX_ROW_LENGTH[0] / MAP_HEIGHT)
    = 65536^2 / 2^24 / 2^16 × 1.125 = 0.0044/map,約每 14,000 區塊一次假陽性,
    全鏈預期約 1,200 次。請驗算。
17. EIP-7745 的最小索引狀態序列化是 21,300,808 位元組(20.31 MiB),
    filter map 額外磁碟成本是 log 本身的 15–20%。
18. EIP-7745 有沒有進 Glamsterdam?狀態是不是還是 Draft?(我標了存疑)
 
【rollup 側】
19. op-node 的旗標已從 --l1.beacon-archiver 改名為 --l1.beacon-fallbacks,
    舊名與 $OP_NODE_L1_BEACON_ARCHIVER 仍是別名。
20. base/blob-archiver 由 Archiver + API 兩個元件組成,後端支援磁碟與 S3
    (S3 後端也可用 GCS)。
21. Arbitrum 的錯誤訊息 "expected at least six blobs for slot N but only got 0"
    裡的 six,對應的是 MAX_BLOBS_PER_TX = 6。這個對應正確嗎?
22. Prysm 對已過期的 blob 回 HTTP 200 + 空陣列,而 OP Stack 的程式碼期待錯誤。
    這個問題修了嗎?
 
【state diff vs 交易】
23. zkSync Era 是 state diff 型 rollup,發布 (A, S, V) 三元組而不是交易,
    pubdata 按儲存槽寫入收費。後果是:當前狀態更容易重建(套 diff 即可,
    工具 eqlabs/zksync-state-reconstruct 掃 commitBatches 並取被 executeBatches
    引用過的),但歷史交易在協定層面本來就不存在,是永久資訊損失。
    這組推論準確嗎?有沒有反例(例如 zkSync 也發布交易的某種模式)?
24. 有哪些主要 rollup 是 state diff 型、哪些是交易型?請給一張表與出處。
 
【歸檔成本】
25. 目標 14 個 blob = 每天 12.30 GiB = 每年 4.39 TiB;上限 21 = 每年 6.58 TiB。
    對照 EIP-4444 要處理的整條 PoW 鏈 body+receipt 是 300–500 GB。請驗算。
26. Blobscan 上可查到的總計是 19,328,970 個 blob / 2.3 TiB
    (19,328,970 × 131,072 = 2.304 TiB)。請告訴我 2026-09-04 當下的實際數字。
27. 現在有哪些實體在做「永久 blob 歸檔」,各自的後端與資金來源是什麼?
    Blobscan 的 IPFS pin、Blockscout、The Graph、EthStorage、4everland
    ——哪幾家真的還在跑?
 
【路線圖】
28. Glamsterdam 的主網時程(我看到「2026 上半年」「Q3」「9-12 月」三種說法)。
29. Glamsterdam 之後的升級定名 Hegota(EL Bogota + CL Heze),
    FOCIL(EIP-7805)是頭條,狀態過期與歷史過期在討論範圍內。請確認並給一級來源。
30. 上面 30 條裡,哪一條是我最可能在半年內因為參數變動或治理決議而說錯的?為什麼?