lotus - 深入理解时空证明的 golang 实现部分(PoSt)
参考文章:https://www.chainnews.com/articles/836343087401.htm
lotus 的时空证明(PoSt)在两个地方会被调用该算法:Winning PoSt 和 Window PoSt。
Winning PoSt 是矿工在出块时对已经提交的扇区进行证明,证明扇区保存的数据依然存在。
Window PoSt 是矿工在对应的周期内对已经提交的扇区进行证明,证明扇区保存的数据依然存在。
// Balance of Miner Actor should be greater than or equal to
// the sum of PreCommitDeposits and LockedFunds.
// Excess balance as computed by st.GetAvailableBalance will be
// withdrawable or usable for pre-commit deposit or pledge lock-up.
type State struct {
// Information not related to sectors.
// TODO: this should be a cid of the miner Info struct so it's not re-written when other fields change.
Info MinerInfo
PreCommitDeposits abi.TokenAmount // Total funds locked as PreCommitDeposits
LockedFunds abi.TokenAmount // Total unvested funds locked as pledge collateral
VestingFunds cid.Cid // Array, AMT[ChainEpoch]TokenAmount
// Sectors that have been pre-committed but not yet proven.
PreCommittedSectors cid.Cid // Map, HAMT[SectorNumber]SectorPreCommitOnChainInfo
// Information for all proven and not-yet-expired sectors.
Sectors cid.Cid // Array, AMT[SectorNumber]SectorOnChainInfo (sparse)
// The first epoch in this miner's current proving period. This is the first epoch in which a PoSt for a
// partition at the miner's first deadline may arrive. Alternatively, it is after the last epoch at which
// a PoSt for the previous window is valid.
// Always greater than zero, his may be greater than the current epoch for genesis miners in the first
// WPoStProvingPeriod epochs of the chain; the epochs before the first proving period starts are exempt from Window
// PoSt requirements.
// Updated at the end of every period by a power actor cron event.
ProvingPeriodStart abi.ChainEpoch
// Sector numbers prove-committed since period start, to be added to Deadlines at next proving period boundary.
NewSectors *abi.BitField
// Sector numbers indexed by expiry epoch (which are on proving period boundaries).
// Invariant: Keys(Sectors) == union(SectorExpirations.Values())
SectorExpirations cid.Cid // Array, AMT[ChainEpoch]Bitfield
// The sector numbers due for PoSt at each deadline in the current proving period, frozen at period start.
// New sectors are added and expired ones removed at proving period boundary.
// Faults are not subtracted from this in state, but on the fly.
Deadlines cid.Cid
// All currently known faulty sectors, mutated eagerly.
// These sectors are exempt from inclusion in PoSt.
Faults *abi.BitField
// Faulty sector numbers indexed by the start epoch of the proving period in which detected.
// Used to track fault durations for eventual sector termination.
// At most 14 entries, b/c sectors faulty longer expire.
// Invariant: Faults == union(FaultEpochs.Values())
FaultEpochs cid.Cid // AMT[ChainEpoch]Bitfield
// Faulty sectors that will recover when next included in a valid PoSt.
// Invariant: Recoveries ⊆ Faults.
Recoveries *abi.BitField
// Records successful PoSt submission in the current proving period by partition number.
// The presence of a partition number indicates on-time PoSt received.
PostSubmissions *abi.BitField
// The index of the next deadline for which faults should been detected and processed (after it's closed).
// The proving period cron handler will always reset this to 0, for the subsequent period.
// Eager fault detection processing on fault/recovery declarations or PoSt may set a smaller number,
// indicating partial progress, from which subsequent processing should continue.
// In the range [0, WPoStProvingPeriodDeadlines).
NextDeadlineToProcessFaults uint64
}
矿工的状态中,储存了矿工信息,对应的抵押信息以及扇区的各种状态。
Info:矿工信息。
PreCommitDeposits:预承诺时抵押数量。
LockedFunds:锁定抵押数量。
VestingFunds:可兑换数量。
PreCommittedSectors:预承诺区块,但是还没有证明。
Sectors:已经证明的并且没有过期的扇区信息。
ProvingPeriodStart:每个证明周期是起始区块高度。
NewSectors:新质押的扇区信息。
SectorExpirations:过期扇区信息。
Deadlines:保存证明周期内所有需要提交证明的扇区。
Faults:所以已经失败的扇区信息。
FaultEpochs:当前证明周期失败的扇区信息。
Recoveries:失败扇区中恢复的扇区信息。
PostSubmissions:当前证明周期所有可以正确提交时空证明的扇区。
NextDeadlineToProcessFaults:对错误扇区进行恢复时对应的挑战区间。
// The period over which all a miner's active sectors will be challenged.
const WPoStProvingPeriod = abi.ChainEpoch(builtin.EpochsInDay) // 24 hours
// The duration of a deadline's challenge window, the period before a deadline when the challenge is available.
const WPoStChallengeWindow = abi.ChainEpoch(3600 / builtin.EpochDurationSeconds) // An hour (=24 per day)
// The number of non-overlapping PoSt deadlines in each proving period.
const WPoStPeriodDeadlines = uint64(WPoStProvingPeriod / WPoStChallengeWindow)
WPoStProvingPeriod:证明周期,每天需要证明一次,即 3456 个区块为一个周期。
WPoStChallengeWindow:挑战窗口,每144个区块为一个挑战周期。这里需要挑战的扇区个数由对应的扇区大小决定,如32GiB扇区大小对应每个挑战窗口需要证明2349个扇区。
WPoStPeriodDeadlines:挑战窗口数量,一个证明周期有24个挑战。
注意:这里的参数并不一定是上主网对应的配置。
// Deadline calculations with respect to a current epoch.
// "Deadline" refers to the window during which proofs may be submitted.
// Windows are non-overlapping ranges [Open, Close), but the challenge epoch for a window occurs before
// the window opens.
// The current epoch may not necessarily lie within the deadline or proving period represented here.
type DeadlineInfo struct {
CurrentEpoch abi.ChainEpoch // Epoch at which this info was calculated.
PeriodStart abi.ChainEpoch // First epoch of the proving period (<= CurrentEpoch).
Index uint64 // A deadline index, in [0..WPoStProvingPeriodDeadlines) unless period elapsed.
Open abi.ChainEpoch // First epoch from which a proof may be submitted, inclusive (>= CurrentEpoch).
Close abi.ChainEpoch // First epoch from which a proof may no longer be submitted, exclusive (>= Open).
Challenge abi.ChainEpoch // Epoch at which to sample the chain for challenge (< Open).
FaultCutoff abi.ChainEpoch // First epoch at which a fault declaration is rejected (< Open).
}
CurrentEpoch:当前区块高度。
PeriodStart:当前证明周期起始高度,和ProvingPeriodStart一样。
Index:挑战窗口下标。
Open:当前挑战窗口可以提交扇区证明的起始高度。
Close:当前挑战窗口可以提交扇区证明的截至高度。
Challenge:作为挑战随机数生成的参数。
FaultCutoff:只有在这个区块时间之前,才能申明错误区块。
// Calculates the deadline at some epoch for a proving period and returns the deadline-related calculations.
func ComputeProvingPeriodDeadline(periodStart, currEpoch abi.ChainEpoch) *DeadlineInfo {
periodProgress := currEpoch - periodStart
if periodProgress >= WPoStProvingPeriod {
// Proving period has completely elapsed.
return NewDeadlineInfo(periodStart, WPoStPeriodDeadlines, currEpoch)
}
deadlineIdx := uint64(periodProgress / WPoStChallengeWindow)
if periodProgress < 0 { // Period not yet started.
deadlineIdx = 0
}
return NewDeadlineInfo(periodStart, deadlineIdx, currEpoch)
}
ComputeProvingPeriodDeadline 函数在给定证明周期起始高度和当前区块高度的情况下计算对应的挑战窗口的 deadlineInfo 信息。
WindowPoSt 的状态变化逻辑包括两部分:每次证明周期起始时间的调整和需要证明的扇区的信息的更新。
offset, err := assignProvingPeriodOffset(rt.Message().Receiver(), currEpoch, rt.Syscalls().HashBlake2b)
periodStart := nextProvingPeriodStart(currEpoch, offset)
assignProvingPeriodOffset 函数随机生成挑战窗口数量之间的偏移值。
nextProvingPeriodStart 函数根据当前高度和偏移值找出下一个证明的起始区块高度。
// Invoked at the end of each proving period, at the end of the epoch before the next one starts.
func handleProvingPeriod(rt Runtime) {
...
}
在确定了下一个证明的起始时间后,miner 智能合约都会在当前证明周期结束后会检查当前证明的状态并更新对应的下一个证明周期起始时间。
NewSectors是添加的新质押的扇区信息的集合,这里有三个函数对应相应的操作。
func (a Actor) ProveCommitSector(rt Runtime, params *ProveCommitSectorParams) *adt.EmptyValue {
...
}
ProveCommitSector 函数会在提交复制证明时同时将该扇区添加到 NewSectors 结构中。
func (a Actor) TerminateSectors(rt Runtime, params *TerminateSectorsParams) *adt.EmptyValue {
...
}
TerminateSectors 会从 NewSectors 结构中删除扇区信息。
// Invoked at the end of each proving period, at the end of the epoch before the next one starts.
func handleProvingPeriod(rt Runtime) {
...
err = AssignNewSectors(deadlines, st.Info.WindowPoStPartitionSectors, newSectors, assignmentSeed)
...
}
handleProvingPeriod 函数会周期性的将 NewSectors 中的扇区分配到不同的挑战窗口(deadline)中。
AssignNewSectors 函数将 WindowPoStPartitionSectors 个扇区分配到不同的挑战窗口(deadline)中。
这里的 WindowPoStPartitionSectors 根据 WindowPoStPartitionSectors 函数获取,即 32GiB 大小的扇区,没有挑战窗口需要挑战 2349 个扇区。
// Returns the partition size, in sectors, associated with a proof type.
// The partition size is the number of sectors proven in a single PoSt proof.
func (p RegisteredProof) WindowPoStPartitionSectors() (uint64, error) {
// Resolve to seal proof and then compute size from that.
sp, err := p.RegisteredSealProof()
if err != nil {
return 0, err
}
// These numbers must match those used by the proofs library.
// See https://github.com/filecoin-project/rust-fil-proofs/blob/master/filecoin-proofs/src/constants.rs#L85
switch sp {
case RegisteredProof_StackedDRG64GiBSeal:
return 2300, nil
case RegisteredProof_StackedDRG32GiBSeal:
return 2349, nil
case RegisteredProof_StackedDRG2KiBSeal:
return 2, nil
case RegisteredProof_StackedDRG8MiBSeal:
return 2, nil
case RegisteredProof_StackedDRG512MiBSeal:
return 2, nil
default:
return 0, errors.Errorf("unsupported proof type: %v", p)
}
}
WindowPoSt 证明的调度主要在 github.com\filecoin-project\lotus\storage\wdpost_sched.go 和 github.com\filecoin-project\lotus\storage\wdpost_run.go 文件中。

type WindowPoStScheduler struct {
api storageMinerApi
prover storage.Prover
faultTracker sectorstorage.FaultTracker
proofType abi.RegisteredProof
partitionSectors uint64
actor address.Address
worker address.Address
cur *types.TipSet
// if a post is in progress, this indicates for which ElectionPeriodStart
activeDeadline *miner.DeadlineInfo
abort context.CancelFunc
//failed abi.ChainEpoch // eps
//failLk sync.Mutex
}
WindowPoStScheduler 会监听链上的状态,在接受新的高度时都会尝试去调用 doPost 函数,doPost 函数包括两部分,一个是 runPost 函数,主要是计算需要的时空证明信息,一个是 submitPost 函数,就是将生成的证明提交到链上。
链上会记录在一个证明周期内所有提交的证明信息,在每个证明周期结束前 handleProvingPeriod 函数会统一检查所有证明情况。