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

[v0.9] Remove penalties on recovery and on missed PoSt (FIP-0002) #1181

Merged
merged 2 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 58 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,49 @@ 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.
anorth marked this conversation as resolved.
Show resolved Hide resolved
// It includes power that was previously declared, skipped, or detected faulty, whether or
// not it is also marked for recovery.
ongoingFaultyPower = previouslyFaultyPower
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucaniz @irenegia I looked over this again and it appears to be correct. previouslyFaultPower already accounts for failedRecoveryPower (we only handle failedRecoveryPower separately in the previous version because we used to charge this power SP). Another way to put it, the value of deadline.FaultyPower.QA at line 1683 after processing deadline end = previouslyFaultPower - newFaultyPower

}
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