Skip to content
This repository has been archived by the owner on Jun 6, 2023. It is now read-only.

Commit

Permalink
FIP-0008: Remove penalties on recovery and on missed PoSt.
Browse files Browse the repository at this point in the history
  • Loading branch information
anorth committed Sep 24, 2020
1 parent 2cbc140 commit d006413
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 46 deletions.
82 changes: 56 additions & 26 deletions actors/builtin/miner/miner_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ type SubmitWindowedPoStParams struct {
func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) *abi.EmptyValue {
currEpoch := rt.CurrEpoch()
store := adt.AsStore(rt)
networkVersion := rt.NetworkVersion()
var st State

if params.Deadline >= WPoStPeriodDeadlines {
Expand Down Expand Up @@ -394,23 +395,37 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams)
// Penalize new skipped faults and retracted recoveries as undeclared faults.
// These pay a higher fee than faults declared before the deadline challenge window opened.
undeclaredPenaltyPower := postResult.PenaltyPower()
undeclaredPenaltyTarget := PledgePenaltyForUndeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, undeclaredPenaltyPower.QA,
rt.NetworkVersion(),
)
// Subtract the "ongoing" fault fee from the amount charged now, since it will be charged at
// the end-of-deadline cron.
undeclaredPenaltyTarget = big.Sub(undeclaredPenaltyTarget, PledgePenaltyForDeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, undeclaredPenaltyPower.QA,
))
undeclaredPenaltyTarget := big.Zero()
if networkVersion >= network.Version3 {
// From version 3, skipped faults and retracted recoveries pay nothing at Window PoSt,
// but will incur the "ongoing" fault fee at deadline end.
} else {
undeclaredPenaltyTarget = PledgePenaltyForUndeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, undeclaredPenaltyPower.QA,
networkVersion,
)
// Subtract the "ongoing" fault fee from the amount charged now, since it will be charged at
// the end-of-deadline cron.
undeclaredPenaltyTarget = big.Sub(undeclaredPenaltyTarget, PledgePenaltyForDeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, undeclaredPenaltyPower.QA,
networkVersion,
))
}

// Penalize recoveries as declared faults (a lower fee than the undeclared, above).
// It sounds odd, but because faults are penalized in arrears, at the _end_ of the faulty period, we must
// penalize recovered sectors here because they won't be penalized by the end-of-deadline cron for the
// immediately-prior faulty period.
declaredPenaltyTarget := PledgePenaltyForDeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, postResult.RecoveredPower.QA,
)
declaredPenaltyTarget := big.Zero()
if networkVersion >= network.Version3 {
// From version 3, recovered sectors pay no penalty.
// They won't pay anything at deadline end either, since they'll no longer be faulty.
} else {
declaredPenaltyTarget = PledgePenaltyForDeclaredFault(
rewardStats.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, postResult.RecoveredPower.QA,
networkVersion,
)
}

// Note: We could delay this charge until end of deadline, but that would require more accounting state.
totalPenaltyTarget := big.Add(undeclaredPenaltyTarget, declaredPenaltyTarget)
Expand Down Expand Up @@ -1570,6 +1585,7 @@ func processEarlyTerminations(rt Runtime) (more bool) {
func handleProvingDeadline(rt Runtime) {
currEpoch := rt.CurrEpoch()
store := adt.AsStore(rt)
networkVersion := rt.NetworkVersion()

epochReward := requestCurrentEpochBlockReward(rt)
pwrTotal := requestCurrentTotalPower(rt)
Expand Down Expand Up @@ -1630,33 +1646,47 @@ func handleProvingDeadline(rt Runtime) {
quant := QuantSpecForDeadline(dlInfo)
unlockedBalance := st.GetUnlockedBalance(rt.CurrentBalance())

// Remember power that was faulty before processing any missed PoSts.
previouslyFaultyPower := deadline.FaultyPower.QA

{
// Detect and penalize missing proofs.
faultExpiration := dlInfo.Last() + FaultMaxAge
penalizePowerTotal := big.Zero()

newFaultyPower, failedRecoveryPower, err := deadline.ProcessDeadlineEnd(store, quant, faultExpiration)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to process end of deadline %d", dlInfo.Index)

powerDelta = powerDelta.Sub(newFaultyPower)
penalizePowerTotal = big.Sum(penalizePowerTotal, newFaultyPower.QA, failedRecoveryPower.QA)

// Unlock sector penalty for all undeclared faults.
penaltyTarget := PledgePenaltyForUndeclaredFault(epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed,
penalizePowerTotal, rt.NetworkVersion())
// Subtract the "ongoing" fault fee from the amount charged now, since it will be added on just below.
penaltyTarget = big.Sub(penaltyTarget, PledgePenaltyForDeclaredFault(epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, penalizePowerTotal))
penaltyFromVesting, penaltyFromBalance, err := st.PenalizeFundsInPriorityOrder(store, currEpoch, penaltyTarget, unlockedBalance)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to unlock penalty")
unlockedBalance = big.Sub(unlockedBalance, penaltyFromBalance)
penaltyTotal = big.Sum(penaltyTotal, penaltyFromVesting, penaltyFromBalance)
pledgeDelta = big.Sub(pledgeDelta, penaltyFromVesting)

if networkVersion >= network.Version3 {
// From network version 3, faults detected from a missed PoSt pay nothing.
// Failed recoveries pay nothing here, but will pay the ongoing fault fee in the subsequent block.
} else {
penalizePowerTotal := big.Add(newFaultyPower.QA, failedRecoveryPower.QA)

// Unlock sector penalty for all undeclared faults.
penaltyTarget := PledgePenaltyForUndeclaredFault(epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed,
penalizePowerTotal, rt.NetworkVersion())
// Subtract the "ongoing" fault fee from the amount charged now, since it will be added on just below.
penaltyTarget = big.Sub(penaltyTarget, PledgePenaltyForDeclaredFault(epochReward.ThisEpochRewardSmoothed,
pwrTotal.QualityAdjPowerSmoothed, penalizePowerTotal, networkVersion))
penaltyFromVesting, penaltyFromBalance, err := st.PenalizeFundsInPriorityOrder(store, currEpoch, penaltyTarget, unlockedBalance)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to unlock penalty")
unlockedBalance = big.Sub(unlockedBalance, penaltyFromBalance)
penaltyTotal = big.Sum(penaltyTotal, penaltyFromVesting, penaltyFromBalance)
pledgeDelta = big.Sub(pledgeDelta, penaltyFromVesting)
}
}
{
// Record faulty power for penalisation of ongoing faults, before popping expirations.
// This includes any power that was just faulted from missing a PoSt.
penaltyTarget := PledgePenaltyForDeclaredFault(epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, deadline.FaultyPower.QA)
ongoingFaultyPower := deadline.FaultyPower.QA
if networkVersion >= network.Version3 {
// From network version 3, this *excludes* any power that was just faulted from missing a PoSt.
ongoingFaultyPower = previouslyFaultyPower
}
penaltyTarget := PledgePenaltyForDeclaredFault(epochReward.ThisEpochRewardSmoothed,
pwrTotal.QualityAdjPowerSmoothed, ongoingFaultyPower, networkVersion)
penaltyFromVesting, penaltyFromBalance, err := st.PenalizeFundsInPriorityOrder(store, currEpoch, penaltyTarget, unlockedBalance)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to unlock penalty")
unlockedBalance = big.Sub(unlockedBalance, penaltyFromBalance) //nolint:ineffassign
Expand Down
10 changes: 5 additions & 5 deletions actors/builtin/miner/miner_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func TestFaultFeeInvariants(t *testing.T) {
t.Run("Undeclared faults are more expensive than declared faults", func(t *testing.T) {
faultySectorPower := abi.NewStoragePower(1 << 50)

ff := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorPower)
ff := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorPower, nv)
sp := PledgePenaltyForUndeclaredFault(rewardEstimate, powerEstimate, faultySectorPower, nv)
assert.True(t, sp.GreaterThan(ff))
})
Expand Down Expand Up @@ -163,11 +163,11 @@ func TestFaultFeeInvariants(t *testing.T) {
totalFaultPower := big.Add(big.Add(faultySectorAPower, faultySectorBPower), faultySectorCPower)

// Declared faults
ffA := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorAPower)
ffB := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorBPower)
ffC := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorCPower)
ffA := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorAPower, nv)
ffB := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorBPower, nv)
ffC := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, faultySectorCPower, nv)

ffAll := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, totalFaultPower)
ffAll := PledgePenaltyForDeclaredFault(rewardEstimate, powerEstimate, totalFaultPower, nv)

// Because we can introduce rounding error between 1 and zero for every penalty calculation
// we can at best expect n calculations of 1 power to be within n of 1 calculation of n powers.
Expand Down
23 changes: 12 additions & 11 deletions actors/builtin/miner/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ func TestWindowPost(t *testing.T) {
// Now submit PoSt
// Power should return for recovered sector.
// Recovery should be charged ongoing fee.
recoveryFee := actor.declaredFaultPenalty(infos)
recoveryFee := actor.declaredFaultPenalty(infos, rt.NetworkVersion())
cfg := &poStConfig{
expectedRawPowerDelta: pwr.Raw,
expectedQAPowerDelta: pwr.QA,
Expand Down Expand Up @@ -981,7 +981,7 @@ func TestWindowPost(t *testing.T) {
// Fee for skipped fault is undeclared fault fee, but it is split into the ongoing fault fee
// which is charged at next cron and the rest which is charged during submit PoSt.
undeclaredFee := actor.undeclaredFaultPenalty(infos[:1], rt.NetworkVersion())
declaredFee := actor.declaredFaultPenalty(infos[:1])
declaredFee := actor.declaredFaultPenalty(infos[:1], rt.NetworkVersion())
faultFee := big.Sub(undeclaredFee, declaredFee)

pwr := miner.PowerForSectors(actor.sectorSize, infos[:1])
Expand All @@ -1005,7 +1005,7 @@ func TestWindowPost(t *testing.T) {

// skip second fault
undeclaredFee = actor.undeclaredFaultPenalty(infos[1:], rt.NetworkVersion())
declaredFee = actor.declaredFaultPenalty(infos[1:])
declaredFee = actor.declaredFaultPenalty(infos[1:], rt.NetworkVersion())
faultFee = big.Sub(undeclaredFee, declaredFee)
pwr = miner.PowerForSectors(actor.sectorSize, infos[1:])

Expand All @@ -1020,7 +1020,7 @@ func TestWindowPost(t *testing.T) {
actor.submitWindowPoSt(rt, dlinfo, partitions, infos, cfg)

// expect ongoing fault from both sectors
advanceDeadline(rt, actor, &cronConfig{ongoingFaultsPenalty: actor.declaredFaultPenalty(infos)})
advanceDeadline(rt, actor, &cronConfig{ongoingFaultsPenalty: actor.declaredFaultPenalty(infos, rt.NetworkVersion())})
})

t.Run("skipped all sectors in a deadline may be skipped", func(t *testing.T) {
Expand Down Expand Up @@ -1051,7 +1051,7 @@ func TestWindowPost(t *testing.T) {
// Fee for skipped fault is undeclared fault fee, but it is split into the ongoing fault fee
// which is charged at next cron and the rest which is charged during submit PoSt.
undeclaredFee := actor.undeclaredFaultPenalty(infos, rt.NetworkVersion())
declaredFee := actor.declaredFaultPenalty(infos)
declaredFee := actor.declaredFaultPenalty(infos, rt.NetworkVersion())
faultFee := big.Sub(undeclaredFee, declaredFee)

pwr := miner.PowerForSectors(actor.sectorSize, infos)
Expand Down Expand Up @@ -1105,7 +1105,7 @@ func TestWindowPost(t *testing.T) {
// Now submit PoSt and skip recovered sector
// No power should be returned
// Retracted recovery will be charged difference between undeclared and ongoing fault fees
ongoingFee := actor.declaredFaultPenalty(infos)
ongoingFee := actor.declaredFaultPenalty(infos, rt.NetworkVersion())
recoveryFee := big.Sub(actor.undeclaredFaultPenalty(infos, rt.NetworkVersion()), ongoingFee)
cfg := &poStConfig{
expectedRawPowerDelta: big.Zero(),
Expand Down Expand Up @@ -1313,11 +1313,12 @@ func TestDeadlineCron(t *testing.T) {
retractedPwr := miner.PowerForSectors(actor.sectorSize, allSectors[1:])
retractedPenalty := miner.PledgePenaltyForUndeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, retractedPwr.QA, rt.NetworkVersion())
// subtract ongoing penalty, because it's charged below (this prevents round-off mismatches)
retractedPenalty = big.Sub(retractedPenalty, miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, retractedPwr.QA))
retractedPenalty = big.Sub(retractedPenalty,
miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, retractedPwr.QA, rt.NetworkVersion()))

// Un-recovered faults are charged as ongoing faults
ongoingPwr := miner.PowerForSectors(actor.sectorSize, allSectors)
ongoingPenalty := miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, ongoingPwr.QA)
ongoingPenalty := miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, ongoingPwr.QA, rt.NetworkVersion())

advanceDeadline(rt, actor, &cronConfig{
detectedFaultsPenalty: retractedPenalty,
Expand Down Expand Up @@ -1413,7 +1414,7 @@ func TestDeclareFaults(t *testing.T) {

// faults are charged at ongoing rate and no additional power is removed
ongoingPwr := miner.PowerForSectors(actor.sectorSize, allSectors)
ongoingPenalty := miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, ongoingPwr.QA)
ongoingPenalty := miner.PledgePenaltyForDeclaredFault(actor.epochRewardSmooth, actor.epochQAPowerSmooth, ongoingPwr.QA, rt.NetworkVersion())

advanceDeadline(rt, actor, &cronConfig{
ongoingFaultsPenalty: ongoingPenalty,
Expand Down Expand Up @@ -3193,9 +3194,9 @@ func (h *actorHarness) withdrawFunds(rt *mock.Runtime, amount abi.TokenAmount) {
rt.Verify()
}

func (h *actorHarness) declaredFaultPenalty(sectors []*miner.SectorOnChainInfo) abi.TokenAmount {
func (h *actorHarness) declaredFaultPenalty(sectors []*miner.SectorOnChainInfo, nv network.Version) abi.TokenAmount {
_, qa := powerForSectors(h.sectorSize, sectors)
return miner.PledgePenaltyForDeclaredFault(h.epochRewardSmooth, h.epochQAPowerSmooth, qa)
return miner.PledgePenaltyForDeclaredFault(h.epochRewardSmooth, h.epochQAPowerSmooth, qa, nv)
}

func (h *actorHarness) undeclaredFaultPenalty(sectors []*miner.SectorOnChainInfo, nv network.Version) abi.TokenAmount {
Expand Down
15 changes: 11 additions & 4 deletions actors/builtin/miner/monies.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ var SpaceRaceInitialPledgeMaxPerByte = big.Div(big.NewInt(1e18), big.NewInt(32 <

// FF = BR(t, DeclaredFaultProjectionPeriod)
// projection period of 2.14 days: 2880 * 2.14 = 6163.2. Rounded to nearest epoch 6163
var DeclaredFaultFactorNum = 214
var DeclaredFaultFactorNumV0 = 214
var DeclaredFaultFactorNumV3 = 351
var DeclaredFaultFactorDenom = 100
var DeclaredFaultProjectionPeriod = abi.ChainEpoch((builtin.EpochsInDay * DeclaredFaultFactorNum) / DeclaredFaultFactorDenom)
var DeclaredFaultProjectionPeriodV0 = abi.ChainEpoch((builtin.EpochsInDay * DeclaredFaultFactorNumV0) / DeclaredFaultFactorDenom)
var DeclaredFaultProjectionPeriodV3 = abi.ChainEpoch((builtin.EpochsInDay * DeclaredFaultFactorNumV3) / DeclaredFaultFactorDenom)

// SP = BR(t, UndeclaredFaultProjectionPeriod)
var UndeclaredFaultFactorNumV0 = 50
Expand Down Expand Up @@ -60,8 +62,13 @@ func ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate *smoothing.Fi
// This is the FF(t) penalty for a sector expected to be in the fault state either because the fault was declared or because
// it has been previously detected by the network.
// FF(t) = DeclaredFaultFactor * BR(t)
func PledgePenaltyForDeclaredFault(rewardEstimate, networkQAPowerEstimate *smoothing.FilterEstimate, qaSectorPower abi.StoragePower) abi.TokenAmount {
return ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate, qaSectorPower, DeclaredFaultProjectionPeriod)
func PledgePenaltyForDeclaredFault(rewardEstimate, networkQAPowerEstimate *smoothing.FilterEstimate, qaSectorPower abi.StoragePower,
networkVersion network.Version) abi.TokenAmount {
projectionPeriod := DeclaredFaultProjectionPeriodV0
if networkVersion >= network.Version3 {
projectionPeriod = DeclaredFaultProjectionPeriodV3
}
return ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate, qaSectorPower, projectionPeriod)
}

// This is the SP(t) penalty for a newly faulty sector that has not been declared.
Expand Down

0 comments on commit d006413

Please sign in to comment.