儲迅出品:Filecoin 二測網絡的算力恢復技巧之各顯身手

前幾天發表了一篇有關 Filecoin 測試網第二階段的算力變化的技術分析:儲迅出品:Filecoin 二測網絡之算力過山車 ,引發了不少爭議。當然,這篇文章也帶來了很多正面的意義,大家都關注到算力的變化,各顯神通,用不同手段進行算力保持和恢復,目前的算力變化圖已經非常平穩。

爲了避開江湖紛爭,我們在這裏只談技術,切勿對號入座。同時,我們也認爲,在分佈式存儲的賽道,Filecoin 挖礦技術本身的優劣並不能帶來長期的競爭力,只有願意長期持續投入,才能打造一個偉大的企業。儲迅也積極與大家分享相關的黑科技以及原創的深度技術解讀,希望在這個領域,也能和行業內的廠商一起,加強中國在相關領域的核心競爭力。

江湖告急:掉算力,白了少年頭

Window PoSt 無法正常提交,相關證明週期的扇區全部被標記爲 fault,看到的算力斷崖式下跌。運維着急,程序員着急,老闆更着急。一個個電話打來,皆曰:代碼有 Bug。Bug 也許可以背鍋,但問題得解決,盯着排行榜的各大媒體可不管什麼 Bug 不 Bug 的。

怎麼辦?最快速的解決方案,就是宣稱官方的幾個節點,比如 t01001、t01002、t01003 是自己跑的。但是……這幾個節點的算力也掉到 0 了:

儲迅出品:Filecoin 二測網絡的算力恢復技巧之各顯身手

作爲唯一的具有真實的可驗證數據的幾個節點,它們已經完成了使命,退出江湖了。剩下的事情,就交給芸芸衆生。技術的事情得靠技術來解決,想點辦法,各顯神通吧。

乾坤大挪移:重穿馬甲,江湖再見

現實可行的最快速方案就是重跑一個新節點。繞開之前遇到的問題。比如:採用多 miner 方案,短時間內提交太多扇區觸發了某個漏洞……當然,能改改代碼繞開也不錯,只要不是智能合約部分的共識代碼,都可以拿來利用。比如,儲迅也針對某些情況,提了 Bug 和補丁,能夠應對部分情況:

Bug:https://github.com/filecoin-project/lotus/issues/1824

補丁:https://github.com/filecoin-project/lotus/pull/1825

如果是自行修改了和證明代碼相關的邏輯,處理起來就更麻煩點。因爲不具通用性,這裏就不多說。

九陰真經:漏洞獵手,下手狠成效快

如果已經積累了不少算力,即使受到懲罰,也仍保留了不少算力,怎麼辦?如果 ID 已經被人熟知,再更換,會引起輿論上的壓力,怎麼辦?莫着急,自有武林祕籍來幫忙。

現階段,Filecoin 官方團隊非常忙,忙於實現 Spec 上的相關功能,所以代碼可以利用的地方還是有不少的。

自行修改代碼的邏輯,比如,一般來說沒有用處,我自己說我的扇區都沒有問題,但別人不相信啊,除非我自己形成了 51% 的優勢算力。那怎麼辦呢?Filecoin 的 specs-actors 模塊,是目前實現智能合約的主要模塊,lotus 和 go-filecoin 都依賴於它。這裏面的代碼,會在每一個礦工的機器上面執行,會對加到鏈上的每一個消息進行解析。思路來了:

  1. 要避免被懲罰,就要繞開 Window PoSt 的檢查。註釋掉本地代碼對 Window PoSt 的調用,自己不發這個消息。

  2. 但是 Filecoin 有 cron 定時任務,會定時對礦工進行檢查(每個證明週期的最後一個高度,如果該高度是空塊就順延)。如果發現沒有做 Window PoSt,就法不容情了,咔擦一下,對應扇區的算力全掉。

  3. 能繞開 cron 定時任務嗎?顯然不能,這個是強制執行的。一步步,執行到最後,會調用 enrollCronEvent,添加下次證明週期的定時任務,一環扣一環,讓你無路可逃。

  4. 如果 cron 定時任務構造某些異常的條件,讓它中途退出,執行不到最後面的添加下次任務的步驟呢?放心,每個環節,寫代碼的時候也都想好了,想輕易構造這些條件,也是不容易的。

  5. 但是……凡事皆有但是。假設 cron 定時任務裏面,有一個未被處理的異常,讓程序崩潰了呢?

官方開發人員在跑 go-filecoin 版本的時候注意到了一個整數(big.Int 類型)沒有被賦初值的情況:

https://github.com/filecoin-project/specs-actors/issues/382

但變量不被賦值,在我學寫代碼的時候經常遇到,這又有什麼關係呢?不就是一個小錯誤嗎?

在 miner_actor.go 中,對礦工定期進行算力檢查的函數會調用 handleProvingPeriod(),該函數又會調用 terminateSectors(),其中有如下變量定義:

    var dealIDs []abi.DealIDvar allSectors []*SectorOnChainInfovar faultySectors []*SectorOnChainInfovar penalty abi.TokenAmount

看樣子也沒有啥問題。提醒一下,重點留意變量 penalty。它在下面的代碼中被賦值了:

       *
    rt.State().Transaction(&st;, func() interface{} {  ...  if terminationType != power.SectorTerminationExpired {    penalty, err = unlockPenalty(&st;, store, rt.CurrEpoch(), allSectors, pledgePenaltyForSectorTermination)  }  return nil})

最後在這個地方使用該變量:

    burnFundsAndNotifyPledgeChange(rt, penalty)

這些都是和懲罰相關的代碼,看得我心驚肉跳。它們又有什麼問題呢?大家注意到沒有,給 penalty 賦值的時候,有一個條件,就是 terminationType != power.SectorTerminationExpired。如果構造出一些扇區,讓它們滿足這個條件,不就成功得讓 penalty 不被賦值但卻被使用,於是引起異常,這樣就執行不了後面的掛載下一次定時任務的代碼,逃過算力懲罰。

最終在 lotus 庫的 chain/vm/runtime.go 裏面,會捕獲異常進行處理:

    func (rs *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) {    defer func() {        if r := recover(); r != nil {            if ar, ok := r.(aerrors.ActorError); ok {                log.Errorf(\"VM.Call failure: %+v\", ar)                aerr = ar                return            }            log.Errorf(\"spec actors failure: %s\", r)            aerr = aerrors.Newf(1, \"spec actors failure: %s\", r)        }    }()    ...}

defer 函數裏面會打印出相關的錯誤,然後繼續執行下一次任務的調用。當前的 cron 定時任務沒有完成,如何處理?都崩潰了,還能幹嗎?一切隨風,相忘於江湖。

至於怎麼構造有問題的扇區,就相對比較容易了。從條件 terminationType != power.SectorTerminationExpired 得知,需要有“過期”的扇區,但扇區參數中的 Expiration 是礦工自己傳到鏈的,隨便他怎麼設置,只要躲過下面這個檢查即可:

       *
    // Check expiry is exactly *the epoch before* the start of a proving period.periodOffset := st.ProvingPeriodStart % WPoStProvingPeriodexpiryOffset := (params.Expiration + 1) % WPoStProvingPeriodif expiryOffset != periodOffset {    rt.Abortf(exitcode.ErrIllegalArgument, \"invalid expiration %d, must be immediately before proving period boundary %d mod %d\",        params.Expiration, periodOffset, WPoStProvingPeriod)}

到執行 cron 定時任務的高度,發現當前礦工剛好有“過期”的扇區,就會觸發直接使用未初始化的變量的 Bug,於是導致定時任務終止。

這個漏洞利用得比較巧妙,下一個證明週期,就可以避免算力丟失的情況。因爲它是 specs-actors 裏的代碼,所以能躲過每個礦工的驗證。

隨着代碼的完善,以及社區的積極貢獻,但我們相信這樣的漏洞會越來越少。如果協議實驗室能學習微軟、谷歌、蘋果等制定漏洞懸賞計劃,我想 Filecoin 會越來越安全。至於找到 0day 漏洞的獎勵?也許給 FIL 就可以了,羊毛出在羊身上,獎勵出在公鏈上。

九陽真經:Window PoSt 和算力恢復

自然,最佳方式就是讓 Window PoSt 能提交成功,且可以把以前出錯的扇區恢復。Filecoin 實現了相關的錯誤恢復機制。只要出錯的扇區不要經歷太久的時間,對它提交 DeclareFaultsRecovered 的消息,再經過一次 Window PoSt,算力是能夠被恢復的。

這張圖是我們跑的小規模集羣的算力。具體內容請參考 t01024 節點——距離程序員最近的 Filecoin 礦場。這幾天反覆調試,提交和修復一些 Bug 並做相關驗證,算力掉得有點慘不忍睹,超過 80% 的扇區出錯,僅僅只有可憐的 3TiB 算力:

儲迅出品:Filecoin 二測網絡的算力恢復技巧之各顯身手

但通過修復後的代碼,來了一次 Window PoSt 提交去,全部算力恢復:

儲迅出品:Filecoin 二測網絡的算力恢復技巧之各顯身手

證明這塊的框架,儲迅改動較大(針對集羣做了優化),無法簡單描述。但我們也提交了一些基本的改動,主要是修復一些常見的錯誤,比如超過 2349 個扇區,Window PoSt 不能正常提交的問題:

儲迅出品:Filecoin 二測網絡的算力恢復技巧之各顯身手

整個恢復過程中用到的一些算法和技術細節,我們後面會逐漸公佈。

**
**

對於 Filecoin 運維,我們希望做到:儘量避免錯誤,如果真的遇到了問題也盡全力恢復。此過程其實是沒有什麼魔法的。所謂的黑科技,一切都是來源於對每一行代碼的深入解讀和永遠不停止的工程實踐。

本文所接的代碼,基於:

https://github.com/filecoin-project/specs-actors 的 v0.5.3 Tag,Commit ID 爲 eac38c1d153cb985fa147c97113b28718361c90b。

https://github.com/filecoin-project/lotus 的 master 分支,Commit ID 爲 0d3f602d58ebb05943943a85d858d45566433964。