From f8335f3ff4a5ef3fe6c2d25c22c9a3e3c44979b5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 16:22:07 -0800 Subject: [PATCH 01/61] update state --- actors/builtin/miner/cbor_gen.go | 181 ++++++++++++++++++++++--- actors/builtin/miner/deadline_state.go | 48 +++++-- gen/gen.go | 1 + 3 files changed, 204 insertions(+), 26 deletions(-) diff --git a/actors/builtin/miner/cbor_gen.go b/actors/builtin/miner/cbor_gen.go index bbe8b11cb..d821c60da 100644 --- a/actors/builtin/miner/cbor_gen.go +++ b/actors/builtin/miner/cbor_gen.go @@ -8,6 +8,7 @@ import ( address "github.com/filecoin-project/go-address" abi "github.com/filecoin-project/go-state-types/abi" + proof "github.com/filecoin-project/specs-actors/actors/runtime/proof" cid "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" @@ -753,7 +754,7 @@ func (t *Deadlines) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufDeadline = []byte{135} +var lengthBufDeadline = []byte{138} func (t *Deadline) MarshalCBOR(w io.Writer) error { if t == nil { @@ -778,11 +779,6 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.ExpirationsEpochs: %w", err) } - // t.PostSubmissions (bitfield.BitField) (struct) - if err := t.PostSubmissions.MarshalCBOR(w); err != nil { - return err - } - // t.EarlyTerminations (bitfield.BitField) (struct) if err := t.EarlyTerminations.MarshalCBOR(w); err != nil { return err @@ -804,6 +800,30 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { if err := t.FaultyPower.MarshalCBOR(w); err != nil { return err } + + // t.PostSubmissions (bitfield.BitField) (struct) + if err := t.PostSubmissions.MarshalCBOR(w); err != nil { + return err + } + + // t.Proofs (cid.Cid) (struct) + + if err := cbg.WriteCidBuf(scratch, w, t.Proofs); err != nil { + return xerrors.Errorf("failed to write cid field t.Proofs: %w", err) + } + + // t.PartitionsSnapshot (cid.Cid) (struct) + + if err := cbg.WriteCidBuf(scratch, w, t.PartitionsSnapshot); err != nil { + return xerrors.Errorf("failed to write cid field t.PartitionsSnapshot: %w", err) + } + + // t.ProofsSnapshot (cid.Cid) (struct) + + if err := cbg.WriteCidBuf(scratch, w, t.ProofsSnapshot); err != nil { + return xerrors.Errorf("failed to write cid field t.ProofsSnapshot: %w", err) + } + return nil } @@ -821,7 +841,7 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input should be of type array") } - if extra != 7 { + if extra != 10 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -848,15 +868,6 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.ExpirationsEpochs = c - } - // t.PostSubmissions (bitfield.BitField) (struct) - - { - - if err := t.PostSubmissions.UnmarshalCBOR(br); err != nil { - return xerrors.Errorf("unmarshaling t.PostSubmissions: %w", err) - } - } // t.EarlyTerminations (bitfield.BitField) (struct) @@ -903,6 +914,51 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { return xerrors.Errorf("unmarshaling t.FaultyPower: %w", err) } + } + // t.PostSubmissions (bitfield.BitField) (struct) + + { + + if err := t.PostSubmissions.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.PostSubmissions: %w", err) + } + + } + // t.Proofs (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Proofs: %w", err) + } + + t.Proofs = c + + } + // t.PartitionsSnapshot (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.PartitionsSnapshot: %w", err) + } + + t.PartitionsSnapshot = c + + } + // t.ProofsSnapshot (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.ProofsSnapshot: %w", err) + } + + t.ProofsSnapshot = c + } return nil } @@ -2310,3 +2366,96 @@ func (t *VestingFund) UnmarshalCBOR(r io.Reader) error { } return nil } + +var lengthBufWindowedPoSt = []byte{130} + +func (t *WindowedPoSt) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufWindowedPoSt); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Partitions (bitfield.BitField) (struct) + if err := t.Partitions.MarshalCBOR(w); err != nil { + return err + } + + // t.Proofs ([]proof.PoStProof) (slice) + if len(t.Proofs) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Proofs was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Proofs))); err != nil { + return err + } + for _, v := range t.Proofs { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + return nil +} + +func (t *WindowedPoSt) UnmarshalCBOR(r io.Reader) error { + *t = WindowedPoSt{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Partitions (bitfield.BitField) (struct) + + { + + if err := t.Partitions.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.Partitions: %w", err) + } + + } + // t.Proofs ([]proof.PoStProof) (slice) + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Proofs: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Proofs = make([]proof.PoStProof, extra) + } + + for i := 0; i < int(extra); i++ { + + var v proof.PoStProof + if err := v.UnmarshalCBOR(br); err != nil { + return err + } + + t.Proofs[i] = v + } + + return nil +} diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 4d480a91e..e536c389f 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -12,6 +12,7 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" + "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" "github.com/filecoin-project/specs-actors/v3/actors/util/adt" ) @@ -40,9 +41,6 @@ type Deadline struct { // recovered, and this queue will not be updated at that time. ExpirationsEpochs cid.Cid // AMT[ChainEpoch]BitField - // Partitions numbers with PoSt submissions since the proving period started. - PostSubmissions bitfield.BitField - // Partitions with sectors that terminated early. EarlyTerminations bitfield.BitField @@ -54,10 +52,32 @@ type Deadline struct { // Memoized sum of faulty power in partitions. FaultyPower PowerPair + + // Bitfield of partitions that have been posted. + PostSubmissions bitfield.BitField + + // AMT of WindowPoSt proofs. + // TODO: this AMT could be inefficient. The proof will be ~200 bytes so + // we could end up writing ~1600 bytes to update this AMT. + // We could also mis-estimate gas, so we need to be very careful here. + Proofs cid.Cid // AMT[]WindowedPoSt + + // Snapshot of partition state at the end of the previous challenge + // window for this deadline. + PartitionsSnapshot cid.Cid + // Snapshot of the proofs submitted by the end of the previous challenge + // window for this deadline. + ProofsSnapshot cid.Cid +} + +type WindowedPoSt struct { + Partitions bitfield.BitField + Proofs []proof.PoStProof } const DeadlinePartitionsAmtBitwidth = 3 const DeadlineExpirationAmtBitwidth = 5 +const DeadlineProofsAmtBitwidth = 5 // // Deadlines (plural) @@ -129,14 +149,22 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { return nil, xerrors.Errorf("failed to construct empty deadline expiration array: %w", err) } + emptyProofsArrayCid, err := adt.StoreEmptyArray(store, DeadlineProofsAmtBitwidth) + if err != nil { + return nil, xerrors.Errorf("failed to construct empty proofs array: %w", err) + } + return &Deadline{ - Partitions: emptyPartitionsArrayCid, - ExpirationsEpochs: emptyDeadlineExpirationArrayCid, - PostSubmissions: bitfield.New(), - EarlyTerminations: bitfield.New(), - LiveSectors: 0, - TotalSectors: 0, - FaultyPower: NewPowerPairZero(), + Partitions: emptyPartitionsArrayCid, + ExpirationsEpochs: emptyDeadlineExpirationArrayCid, + EarlyTerminations: bitfield.New(), + LiveSectors: 0, + TotalSectors: 0, + FaultyPower: NewPowerPairZero(), + PostSubmissions: bitfield.New(), + Proofs: emptyProofsArrayCid, + PartitionsSnapshot: emptyPartitionsArrayCid, + ProofsSnapshot: emptyProofsArrayCid, }, nil } diff --git a/gen/gen.go b/gen/gen.go index 79dc5401a..a415b402f 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -179,6 +179,7 @@ func main() { miner.WorkerKeyChange{}, miner.VestingFunds{}, miner.VestingFund{}, + miner.WindowedPoSt{}, // method params and returns // miner.ConstructorParams{}, // in power actor //miner.SubmitWindowedPoStParams{}, // Aliased from v0 From 6fd790428503930535fbf86c13a8fb84b65316ee Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 16:22:37 -0800 Subject: [PATCH 02/61] update window post submission to record but not verify --- actors/builtin/miner/deadline_state.go | 5 ++-- actors/builtin/miner/miner_actor.go | 40 ++++++++++---------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index e536c389f..f7d40cf45 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -898,6 +898,7 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx return powerDelta, penalizedPower, nil } +// TODO: We're only using PowerDelta at this point. We should simplify. type PoStResult struct { // Power activated or deactivated (positive or negative). PowerDelta PowerPair @@ -921,9 +922,7 @@ func (p *PoStResult) PenaltyPower() PowerPair { // changes to power (newly faulty power, power that should have been proven // recovered but wasn't, and newly recovered power). // -// NOTE: This function does not actually _verify_ any proofs. The returned -// Sectors and IgnoredSectors must subsequently be validated against the PoSt -// submitted by the miner. +// NOTE: This function does not actually _verify_ any proofs. func (dl *Deadline) RecordProvenSectors( store adt.Store, sectors Sectors, ssize abi.SectorSize, quant QuantSpec, faultExpiration abi.ChainEpoch, diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index b59dd6255..9d6af6b16 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -427,11 +427,12 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) // Record proven sectors/partitions, returning updates to power and the final set of sectors // proven/skipped. // - // NOTE: This function does not actually check the proofs but does assume that they'll be - // successfully validated. The actual proof verification is done below in verifyWindowedPost. + // NOTE: This function does not actually check the proofs but does assume that they're correct. Instead, + // it snapshots the deadline's state and the submitted proofs at the end of the challenge window and + // allows third-parties to challenge these proofs. // - // If proof verification fails, the this deadline MUST NOT be saved and this function should - // be aborted. + // While we could perform _all_ operations at the end of challenge window, we do as we can here to avoid + // overloading cron. faultExpiration := currDeadline.Last() + FaultMaxAge postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to process post submission for deadline %d", params.Deadline) @@ -439,27 +440,16 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) // Skipped sectors (including retracted recoveries) pay nothing at Window PoSt, // but will incur the "ongoing" fault fee at deadline end. - // Validate proofs - - // Load sector infos for proof, substituting a known-good sector for known-faulty sectors. - // Note: this is slightly sub-optimal, loading info for the recovering sectors again after they were already - // loaded above. - sectorInfos, err := sectors.LoadForProof(postResult.Sectors, postResult.IgnoredSectors) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proven sector info") - - if len(sectorInfos) == 0 { - // Abort verification if all sectors are (now) faults. There's nothing to prove. - // It's not rational for a miner to submit a Window PoSt marking *all* non-faulty sectors as skipped, - // since that will just cause them to pay a penalty at deadline end that would otherwise be zero - // if they had *not* declared them. - rt.Abortf(exitcode.ErrIllegalArgument, "cannot prove partitions with no active sectors") - } - - // Verify the proof. - // A failed verification doesn't immediately cause a penalty; the miner can try again. - // - // This function aborts on failure. - verifyWindowedPost(rt, currDeadline.Challenge, sectorInfos, params.Proofs) + // Store proofs in case they need to be challenged later. + proofs, err := adt.AsArray(store, deadline.Proofs) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") + err = proofs.AppendContinuous(&WindowedPoSt{ + Partitions: partitionIndexes, + Proofs: params.Proofs, + }) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to append new proofs") + deadline.Proofs, err = proofs.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed store proofs") err = deadlines.UpdateDeadline(store, params.Deadline, deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) From 6a7516b72b14e81a7b21679b9b410fca8b1f80ba Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 16:29:58 -0800 Subject: [PATCH 03/61] handle proofs update at the end of the deadline --- actors/builtin/miner/deadline_state.go | 8 +++++++- actors/builtin/miner/miner_state.go | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index f7d40cf45..a9c5976c5 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -893,8 +893,14 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx return powerDelta, penalizedPower, xc.ErrIllegalState.Wrapf("failed to update deadline expiration queue: %w", err) } - // Reset PoSt submissions. + // Reset PoSt submissions, snapshot proofs. dl.PostSubmissions = bitfield.New() + dl.PartitionsSnapshot = dl.Partitions + dl.ProofsSnapshot = dl.Proofs + dl.Proofs, err = adt.MakeEmptyArray(store).Root() + if err != nil { + return powerDelta, penalizedPower, xc.ErrIllegalState.Wrapf("failed to clear pending proofs array", err) + } return powerDelta, penalizedPower, nil } diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index 30e9b8542..c3dae5fdd 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1128,6 +1128,9 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad // No live sectors in this deadline, nothing to do. if deadline.LiveSectors == 0 { + // TODO: do we still need to clear the post submissions here? I + // think we're technically fine, but this is a strange + // edge-case. return &AdvanceDeadlineResult{ pledgeDelta, powerDelta, From 715bc7296b191e71ac8e707bb377820c6a33e4e0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 18:22:34 -0800 Subject: [PATCH 04/61] initial challenge windowed post implementation --- actors/builtin/methods.go | 3 +- actors/builtin/miner/miner_actor.go | 99 ++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/actors/builtin/methods.go b/actors/builtin/methods.go index 92ace77c7..795f73d80 100644 --- a/actors/builtin/methods.go +++ b/actors/builtin/methods.go @@ -99,7 +99,8 @@ var MethodsMiner = struct { ConfirmUpdateWorkerKey abi.MethodNum RepayDebt abi.MethodNum ChangeOwnerAddress abi.MethodNum -}{MethodConstructor, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23} + ChallengeWindowedPoSt abi.MethodNum +}{MethodConstructor, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24} var MethodsVerifiedRegistry = struct { Constructor abi.MethodNum diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 9d6af6b16..19f2b4359 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -72,6 +72,7 @@ func (a Actor) Exports() []interface{} { 21: a.ConfirmUpdateWorkerKey, 22: a.RepayDebt, 23: a.ChangeOwnerAddress, + 24: a.ChallengeWindowedPoSt, } } @@ -471,6 +472,96 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) return nil } +type ChallengeWindowedPoStParams struct { + Deadline uint64 + ProofIndex uint64 // only one is allowed at a time to avoid loading too many sector infos. +} + +func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStParams) *abi.EmptyValue { + rt.ValidateImmediateCallerAcceptAny() + + if params.Deadline >= WPoStPeriodDeadlines { + rt.Abortf(exitcode.ErrIllegalArgument, "invalid deadline %d of %d", params.Deadline, WPoStPeriodDeadlines) + } + + powerDelta := NewPowerPairZero() + var st State + rt.StateTransaction(&st, func() { + + if st.CurrentDeadline == params.Deadline { + // TODO: blank out the previous deadline as well? + rt.Abortf(exitcode.ErrForbidden, "cannot challenge a window post for an open deadline") + } + + store := adt.AsStore(rt) + deadlines, err := st.LoadDeadlines(store) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadlines") + + dl, err := deadlines.LoadDeadline(store, params.Deadline) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") + + proofs, err := adt.AsArray(store, dl.ProofsSnapshot) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") + + partitions, err := adt.AsArray(store, dl.PartitionsSnapshot) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") + + var post WindowedPoSt + found, err := proofs.Get(params.ProofIndex, &post) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof") + if !found { + rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.ProofIndex) + } + + var allSectors, allIgnored []bitfield.BitField + err = post.Partitions.ForEach(func(partIdx uint64) error { + var partition Partition + if found, err := partitions.Get(partIdx, &partition); err != nil { + return err + } else if !found { + return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.ProofIndex) + } + allSectors = append(allSectors, partition.Sectors) + allIgnored = append(allIgnored, partition.Faults) + allIgnored = append(allIgnored, partition.Terminated) + + // NOTE: This also includes power that was + // activated at the end of the last challenge + // window, and power from sectors that have since + // expired. That means we may end up over-penalizing, + // but that's fine. The miner submitted a bad proof. + powerDelta = powerDelta.Sub(partition.ActivePower()) + return nil + }) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") + + allSectorsNos, err := bitfield.MultiMerge(allSectors...) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge sector bitfields") + allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") + + // FIXME: handle compaction + sectors, err := LoadSectors(store, st.Sectors) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") + sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") + + // Find the proving period start for the deadline in question. + ppStart := st.ProvingPeriodStart + if st.CurrentDeadline < params.Deadline { + ppStart -= WPoStProvingPeriod + } + + challengeEpoch := ppStart + WPoStChallengeWindow*abi.ChainEpoch(params.Deadline) - WPoStChallengeLookback + + challengeWindowedPost(rt, challengeEpoch, sectorInfos, post.Proofs) + + // TODO: mark sectors as bad. + }) + + // TODO: Pay submitter some fee. +} + /////////////////////// // Sector Commitment // /////////////////////// @@ -2074,7 +2165,7 @@ func havePendingEarlyTerminations(rt Runtime, st *State) bool { return !noEarlyTerminations } -func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof) { +func challengeWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof) { minerActorID, err := addr.IDFromAddress(rt.Receiver()) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "runtime provided bad receiver address %v", rt.Receiver()) @@ -2103,8 +2194,10 @@ func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*Se } // Verify the PoSt Proof - if err = rt.VerifyPoSt(pvInfo); err != nil { - rt.Abortf(exitcode.ErrIllegalArgument, "invalid PoSt %+v: %s", pvInfo, err) + if err = rt.VerifyPoSt(pvInfo); err == nil { + rt.Abortf(exitcode.ErrIllegalArgument, "valid PoSt %+v", pvInfo) + } else { + rt.Log(rtt.WARN, "PoSt successfully challenged: %s", err) } } From de07ccb46f6444648dcf8641727e934c71d4f936 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 18:29:58 -0800 Subject: [PATCH 05/61] handle compaction --- actors/builtin/miner/deadline_state.go | 15 +++++++++------ actors/builtin/miner/miner_actor.go | 4 ++-- actors/builtin/miner/miner_state.go | 9 +++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index a9c5976c5..0ebfa8ffc 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -68,6 +68,9 @@ type Deadline struct { // Snapshot of the proofs submitted by the end of the previous challenge // window for this deadline. ProofsSnapshot cid.Cid + // Snapshot of the miner's sectors at the end of the previous challenge + // window for this deadline. + SectorsSnapshot cid.Cid } type WindowedPoSt struct { @@ -154,6 +157,11 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { return nil, xerrors.Errorf("failed to construct empty proofs array: %w", err) } + emptySectorsArrayCid, err := adt.StoreEmptyArray(store, SectorsAmtBitwidth) + if err != nil { + return nil, xerrors.Errorf("failed to construct empty sectors array: %w", err) + } + return &Deadline{ Partitions: emptyPartitionsArrayCid, ExpirationsEpochs: emptyDeadlineExpirationArrayCid, @@ -164,6 +172,7 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { PostSubmissions: bitfield.New(), Proofs: emptyProofsArrayCid, PartitionsSnapshot: emptyPartitionsArrayCid, + SectorsSnapshot: emptySectorsArrayCid, ProofsSnapshot: emptyProofsArrayCid, }, nil } @@ -895,12 +904,6 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx // Reset PoSt submissions, snapshot proofs. dl.PostSubmissions = bitfield.New() - dl.PartitionsSnapshot = dl.Partitions - dl.ProofsSnapshot = dl.Proofs - dl.Proofs, err = adt.MakeEmptyArray(store).Root() - if err != nil { - return powerDelta, penalizedPower, xc.ErrIllegalState.Wrapf("failed to clear pending proofs array", err) - } return powerDelta, penalizedPower, nil } diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 19f2b4359..bae5ada67 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -540,8 +540,8 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") - // FIXME: handle compaction - sectors, err := LoadSectors(store, st.Sectors) + // Load sectors as seen at the end of the last proving period. + sectors, err := LoadSectors(store, dl.SectorsSnapshot) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index c3dae5fdd..545a92c1b 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1151,6 +1151,15 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad return nil, xerrors.Errorf("failed to process end of deadline %d: %w", dlInfo.Index, err) } + // Setup snapshots for challenges. + deadline.PartitionsSnapshot = deadline.Partitions + deadline.ProofsSnapshot = deadline.Proofs + deadline.Proofs, err = adt.MakeEmptyArray(store).Root() + if err != nil { + return nil, xerrors.Errorf("failed to clear pending proofs array", err) + } + deadline.SectorsSnapshot = st.Sectors + // Capture deadline's faulty power after new faults have been detected, but before it is // dropped along with faulty sectors expiring this round. totalFaultyPower = deadline.FaultyPower From 1d3801f86c3cb7a088223505399200583255723a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 8 Dec 2020 18:30:43 -0800 Subject: [PATCH 06/61] fix compile --- actors/builtin/miner/miner_actor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index bae5ada67..627bddec2 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -560,6 +560,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa }) // TODO: Pay submitter some fee. + return nil } /////////////////////// From 176f7524eb306bfce6bc17d69913aa64c243965b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 10:50:12 -0800 Subject: [PATCH 07/61] flesh out ChallengeWindowedPoSt --- actors/builtin/miner/cbor_gen.go | 99 ++++++++++++++- actors/builtin/miner/deadlines.go | 11 ++ actors/builtin/miner/miner_actor.go | 183 ++++++++++++++++++++-------- gen/gen.go | 1 + 4 files changed, 238 insertions(+), 56 deletions(-) diff --git a/actors/builtin/miner/cbor_gen.go b/actors/builtin/miner/cbor_gen.go index d821c60da..9166f64d1 100644 --- a/actors/builtin/miner/cbor_gen.go +++ b/actors/builtin/miner/cbor_gen.go @@ -754,7 +754,7 @@ func (t *Deadlines) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufDeadline = []byte{138} +var lengthBufDeadline = []byte{139} func (t *Deadline) MarshalCBOR(w io.Writer) error { if t == nil { @@ -824,6 +824,12 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.ProofsSnapshot: %w", err) } + // t.SectorsSnapshot (cid.Cid) (struct) + + if err := cbg.WriteCidBuf(scratch, w, t.SectorsSnapshot); err != nil { + return xerrors.Errorf("failed to write cid field t.SectorsSnapshot: %w", err) + } + return nil } @@ -841,7 +847,7 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input should be of type array") } - if extra != 10 { + if extra != 11 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -959,6 +965,18 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.ProofsSnapshot = c + } + // t.SectorsSnapshot (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(br) + if err != nil { + return xerrors.Errorf("failed to read cid field t.SectorsSnapshot: %w", err) + } + + t.SectorsSnapshot = c + } return nil } @@ -2459,3 +2477,80 @@ func (t *WindowedPoSt) UnmarshalCBOR(r io.Reader) error { return nil } + +var lengthBufChallengeWindowedPoStParams = []byte{130} + +func (t *ChallengeWindowedPoStParams) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + if _, err := w.Write(lengthBufChallengeWindowedPoStParams); err != nil { + return err + } + + scratch := make([]byte, 9) + + // t.Deadline (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Deadline)); err != nil { + return err + } + + // t.ProofIndex (uint64) (uint64) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.ProofIndex)); err != nil { + return err + } + + return nil +} + +func (t *ChallengeWindowedPoStParams) UnmarshalCBOR(r io.Reader) error { + *t = ChallengeWindowedPoStParams{} + + br := cbg.GetPeeker(r) + scratch := make([]byte, 8) + + maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Deadline (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Deadline = uint64(extra) + + } + // t.ProofIndex (uint64) (uint64) + + { + + maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ProofIndex = uint64(extra) + + } + return nil +} diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index bef627966..6feee535d 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -66,3 +66,14 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE // that deadline opens. return currentEpoch < dlInfo.Open-WPoStChallengeWindow } + +func deadlineCanCompact(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { + dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() + + // Make sure the current epoch is at least finality after the last + // window post to give the network time to challenge the sectors, and at + // least 1 window before the next window opens to avoid compacting an + // immutable deadline. + return currentEpoch > (dlInfo.Close-WPoStProvingPeriod)+ChainFinality && + currentEpoch < dlInfo.Open-WPoStChallengeWindow +} diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 627bddec2..d98b298d6 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -484,81 +484,156 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa rt.Abortf(exitcode.ErrIllegalArgument, "invalid deadline %d of %d", params.Deadline, WPoStPeriodDeadlines) } + currEpoch := rt.CurrEpoch() + + // TODO: maybe stash this in the deadline for better accuracy? + epochReward := requestCurrentEpochBlockReward(rt) + pwrTotal := requestCurrentTotalPower(rt) + + toBurn := abi.NewTokenAmount(0) + pledgeDelta := abi.NewTokenAmount(0) powerDelta := NewPowerPairZero() var st State rt.StateTransaction(&st, func() { - if st.CurrentDeadline == params.Deadline { // TODO: blank out the previous deadline as well? rt.Abortf(exitcode.ErrForbidden, "cannot challenge a window post for an open deadline") } + info := getMinerInfo(rt, &st) + faultyPower := NewPowerPairZero() store := adt.AsStore(rt) - deadlines, err := st.LoadDeadlines(store) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadlines") - dl, err := deadlines.LoadDeadline(store, params.Deadline) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") + // Check proof + // TODO: move into deadline state function. + { + // Load the target state. + deadlines, err := st.LoadDeadlines(store) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadlines") - proofs, err := adt.AsArray(store, dl.ProofsSnapshot) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") + dl, err := deadlines.LoadDeadline(store, params.Deadline) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") - partitions, err := adt.AsArray(store, dl.PartitionsSnapshot) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") + proofs, err := adt.AsArray(store, dl.ProofsSnapshot) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - var post WindowedPoSt - found, err := proofs.Get(params.ProofIndex, &post) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof") - if !found { - rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.ProofIndex) - } + partitionsSnapshot, err := adt.AsArray(store, dl.PartitionsSnapshot) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") - var allSectors, allIgnored []bitfield.BitField - err = post.Partitions.ForEach(func(partIdx uint64) error { - var partition Partition - if found, err := partitions.Get(partIdx, &partition); err != nil { - return err - } else if !found { - return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.ProofIndex) + // Load the target proof. + var post WindowedPoSt + found, err := proofs.Get(params.ProofIndex, &post) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof") + if !found { + rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.ProofIndex) } - allSectors = append(allSectors, partition.Sectors) - allIgnored = append(allIgnored, partition.Faults) - allIgnored = append(allIgnored, partition.Terminated) - - // NOTE: This also includes power that was - // activated at the end of the last challenge - // window, and power from sectors that have since - // expired. That means we may end up over-penalizing, - // but that's fine. The miner submitted a bad proof. - powerDelta = powerDelta.Sub(partition.ActivePower()) - return nil - }) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") - allSectorsNos, err := bitfield.MultiMerge(allSectors...) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge sector bitfields") - allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") + var allSectors, allIgnored []bitfield.BitField + faults := make(PartitionSectorMap) + err = post.Partitions.ForEach(func(partIdx uint64) error { + var partition Partition + if found, err := partitionsSnapshot.Get(partIdx, &partition); err != nil { + return err + } else if !found { + return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.ProofIndex) + } - // Load sectors as seen at the end of the last proving period. - sectors, err := LoadSectors(store, dl.SectorsSnapshot) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") - sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") + // Record sectors for proof verification + allSectors = append(allSectors, partition.Sectors) + allIgnored = append(allIgnored, partition.Faults) + allIgnored = append(allIgnored, partition.Terminated) - // Find the proving period start for the deadline in question. - ppStart := st.ProvingPeriodStart - if st.CurrentDeadline < params.Deadline { - ppStart -= WPoStProvingPeriod - } + // Record active sectors for marking faults. + active, err := partition.ActiveSectors() + if err != nil { + return err + } + err = faults.Add(partIdx, active) + if err != nil { + return err + } + + // Record faulty power for penalties. + // + // NOTE: This also includes power that was + // activated at the end of the last challenge + // window, and power from sectors that have since + // expired. That means we may end up over-penalizing, + // but that's fine. The miner submitted a bad proof. + faultyPower = faultyPower.Add(partition.ActivePower()) + return nil + }) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") + + allSectorsNos, err := bitfield.MultiMerge(allSectors...) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge sector bitfields") + + allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") + + // Load sectors as seen at the end of the last proving period. + sectors, err := LoadSectors(store, dl.SectorsSnapshot) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") + sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") + + // Find the proving period start for the deadline in question. + ppStart := st.ProvingPeriodStart + if st.CurrentDeadline < params.Deadline { + ppStart -= WPoStProvingPeriod + } + + targetDeadline := NewDeadlineInfo(ppStart, params.Deadline, currEpoch) - challengeEpoch := ppStart + WPoStChallengeWindow*abi.ChainEpoch(params.Deadline) - WPoStChallengeLookback + // Fails if validation succeeds. + challengeWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs) - challengeWindowedPost(rt, challengeEpoch, sectorInfos, post.Proofs) + // Ok, so, here's the tricky part. Now we need to mark these all as _bad_. However, compaction could have + // happened, so the sectors could have moved. + // + // We've addressed this in two ways: + // + // 1. Compaction cannot be done for Finality epochs after a challenge window. + // 2. Successful challenges will always penalize, even if we can't find the target sectors and mark them faulty. + // You can run but you can't hide. - // TODO: mark sectors as bad. + faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge + powerDelta, err = dl.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) + + // Clear proof snapshot so the miner can't be charged multiple times. + dl.ProofsSnapshot, err = adt.MakeEmptyArray(store).Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to clear proofs") + + err = deadlines.UpdateDeadline(store, params.Deadline, dl) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) + err = st.SaveDeadlines(store, deadlines) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to save deadlines") + } + + // Penalties. + { + penaltyTarget := PledgePenaltyForContinuedFault( + epochReward.ThisEpochRewardSmoothed, + pwrTotal.QualityAdjPowerSmoothed, + faultyPower.QA, + ) + + err := st.ApplyPenalty(penaltyTarget) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to apply penalty") + penaltyFromVesting, penaltyFromBalance, err := st.RepayPartialDebtInPriorityOrder(store, currEpoch, rt.CurrentBalance()) + toBurn = big.Add(penaltyFromVesting, penaltyFromBalance) + pledgeDelta = penaltyFromVesting.Neg() + } }) + requestUpdatePower(rt, powerDelta) + notifyPledgeChanged(rt, pledgeDelta) + burnFunds(rt, toBurn) + rt.StateReadonly(&st) + + err := st.CheckBalanceInvariants(rt.CurrentBalance()) + builtin.RequireNoErr(rt, err, ErrBalanceInvariantBroken, "balance invariants broken") + // TODO: Pay submitter some fee. return nil } @@ -1512,9 +1587,9 @@ func (a Actor) CompactPartitions(rt Runtime, params *CompactPartitionsParams) *a info := getMinerInfo(rt, &st) rt.ValidateImmediateCallerIs(append(info.ControlAddresses, info.Owner, info.Worker)...) - if !deadlineIsMutable(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { + if !deadlineCanCompact(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { rt.Abortf(exitcode.ErrForbidden, - "cannot compact deadline %d during its challenge window or the prior challenge window", params.Deadline) + "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, ChainFinality) } submissionPartitionLimit := loadPartitionsSectorsMax(info.WindowPoStPartitionSectors) diff --git a/gen/gen.go b/gen/gen.go index a415b402f..2d18be41c 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -180,6 +180,7 @@ func main() { miner.VestingFunds{}, miner.VestingFund{}, miner.WindowedPoSt{}, + miner.ChallengeWindowedPoStParams{}, // method params and returns // miner.ConstructorParams{}, // in power actor //miner.SubmitWindowedPoStParams{}, // Aliased from v0 From a98d741208fd765b1d96db09d967d30264007b31 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 11:30:41 -0800 Subject: [PATCH 08/61] only clear out checked proof --- actors/builtin/miner/miner_actor.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index d98b298d6..99259fa38 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -597,12 +597,16 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // 2. Successful challenges will always penalize, even if we can't find the target sectors and mark them faulty. // You can run but you can't hide. + // TODO: this is using the sector _snapshot_ is that + // going to be an issue? faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge powerDelta, err = dl.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) - // Clear proof snapshot so the miner can't be charged multiple times. - dl.ProofsSnapshot, err = adt.MakeEmptyArray(store).Root() - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to clear proofs") + // Delete challenged proof so it can't be charged multiple times. + err = proofs.Delete(params.ProofIndex) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete challenged proof") + dl.ProofsSnapshot, err = proofs.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update proofs") err = deadlines.UpdateDeadline(store, params.Deadline, dl) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) From 19ae9f675d9f8d278f9b06e309653588be71589c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 11:31:05 -0800 Subject: [PATCH 09/61] start fixing tests --- actors/builtin/miner/miner_test.go | 147 ++++++++++++++++++----------- 1 file changed, 90 insertions(+), 57 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 9977e1649..0f8eb919e 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -5079,78 +5079,111 @@ type poStConfig struct { verificationError error } -func (h *actorHarness) submitWindowPoSt(rt *mock.Runtime, deadline *dline.Info, partitions []miner.PoStPartition, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { +func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { rt.SetCaller(h.worker, builtin.AccountActorCodeID) - commitRand := abi.Randomness("chaincommitment") - rt.ExpectGetRandomnessTickets(crypto.DomainSeparationTag_PoStChainCommit, deadline.Challenge, nil, commitRand) + rt.ExpectValidateCallerAny() - rt.ExpectValidateCallerAddr(append(h.controlAddrs, h.owner, h.worker)...) + // TODO: adapt this to verify post on challenge, instead of on submit. + /* + proofs := makePoStProofs(h.postProofType) + challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) - proofs := makePoStProofs(h.postProofType) - challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) - - // only sectors that are not skipped and not existing non-recovered faults will be verified - allIgnored := bf() - dln := h.getDeadline(rt, deadline.Index) - for _, p := range partitions { - partition := h.getPartition(rt, dln, p.Index) - expectedFaults, err := bitfield.SubtractBitField(partition.Faults, partition.Recoveries) - require.NoError(h.t, err) - allIgnored, err = bitfield.MultiMerge(allIgnored, expectedFaults, p.Skipped) - require.NoError(h.t, err) - } + // only sectors that are not skipped and not existing non-recovered faults will be verified + allIgnored := bf() + dln := h.getDeadline(rt, deadline.Index) - // find the first non-faulty, non-skipped sector in poSt to replace all faulty sectors. - var goodInfo *miner.SectorOnChainInfo - for _, ci := range infos { - contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) - require.NoError(h.t, err) - if !contains { - goodInfo = ci - break + for _, p := range partitions { + partition := h.getPartition(rt, dln, p.Index) + expectedFaults, err := bitfield.SubtractBitField(partition.Faults, partition.Recoveries) + require.NoError(h.t, err) + allIgnored, err = bitfield.MultiMerge(allIgnored, expectedFaults, p.Skipped) + require.NoError(h.t, err) } - } - // goodInfo == nil indicates all the sectors have been skipped and should PoSt verification should not occur - if goodInfo != nil { - var buf bytes.Buffer - receiver := rt.Receiver() - err := receiver.MarshalCBOR(&buf) - require.NoError(h.t, err) + // find the first non-faulty, non-skipped sector in poSt to replace all faulty sectors. + var goodInfo *miner.SectorOnChainInfo + for _, ci := range infos { + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if !contains { + goodInfo = ci + break + } + } - rt.ExpectGetRandomnessBeacon(crypto.DomainSeparationTag_WindowedPoStChallengeSeed, deadline.Challenge, buf.Bytes(), abi.Randomness(challengeRand)) + // goodInfo == nil indicates all the sectors have been skipped and should PoSt verification should not occur + if goodInfo != nil { + var buf bytes.Buffer + receiver := rt.Receiver() + err := receiver.MarshalCBOR(&buf) + require.NoError(h.t, err) - actorId, err := addr.IDFromAddress(h.receiver) - require.NoError(h.t, err) + rt.ExpectGetRandomnessBeacon(crypto.DomainSeparationTag_WindowedPoStChallengeSeed, deadline.Challenge, buf.Bytes(), abi.Randomness(challengeRand)) - // if not all sectors are skipped - proofInfos := make([]proof.SectorInfo, len(infos)) - for i, ci := range infos { - si := ci - contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + actorId, err := addr.IDFromAddress(h.receiver) require.NoError(h.t, err) - if contains { - si = goodInfo - } - proofInfos[i] = proof.SectorInfo{ - SealProof: si.SealProof, - SectorNumber: si.SectorNumber, - SealedCID: si.SealedCID, + + // if not all sectors are skipped + proofInfos := make([]proof.SectorInfo, len(infos)) + for i, ci := range infos { + si := ci + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if contains { + si = goodInfo + } + proofInfos[i] = proof.SectorInfo{ + SealProof: si.SealProof, + SectorNumber: si.SectorNumber, + SealedCID: si.SealedCID, + } } - } - vi := proof.WindowPoStVerifyInfo{ - Randomness: abi.PoStRandomness(challengeRand), - Proofs: proofs, - ChallengedSectors: proofInfos, - Prover: abi.ActorID(actorId), + vi := proof.WindowPoStVerifyInfo{ + Randomness: abi.PoStRandomness(challengeRand), + Proofs: proofs, + ChallengedSectors: proofInfos, + Prover: abi.ActorID(actorId), + } + var verifResult error + if poStCfg != nil { + verifResult = poStCfg.verificationError + } + rt.ExpectVerifyPoSt(vi, verifResult) } - var verifResult error if poStCfg != nil { - verifResult = poStCfg.verificationError + // expect power update + if !poStCfg.expectedPowerDelta.IsZero() { + claim := &power.UpdateClaimedPowerParams{ + RawByteDelta: poStCfg.expectedPowerDelta.Raw, + QualityAdjustedDelta: poStCfg.expectedPowerDelta.QA, + } + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdateClaimedPower, claim, abi.NewTokenAmount(0), + nil, exitcode.Ok) + } } - rt.ExpectVerifyPoSt(vi, verifResult) - } + + params := miner.SubmitWindowedPoStParams{ + Deadline: deadline.Index, + Partitions: partitions, + Proofs: proofs, + ChainCommitEpoch: deadline.Challenge, + ChainCommitRand: commitRand, + } + + rt.Call(h.a.SubmitWindowedPoSt, ¶ms) + rt.Verify() + */ +} + +func (h *actorHarness) submitWindowPoSt(rt *mock.Runtime, deadline *dline.Info, partitions []miner.PoStPartition, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { + rt.SetCaller(h.worker, builtin.AccountActorCodeID) + commitRand := abi.Randomness("chaincommitment") + rt.ExpectGetRandomnessTickets(crypto.DomainSeparationTag_PoStChainCommit, deadline.Challenge, nil, commitRand) + + rt.ExpectValidateCallerAddr(append(h.controlAddrs, h.owner, h.worker)...) + + proofs := makePoStProofs(h.postProofType) if poStCfg != nil { // expect power update if !poStCfg.expectedPowerDelta.IsZero() { From b087f5f63cf9c1099faf1ea24bbf31c839788417 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 15:02:24 -0800 Subject: [PATCH 10/61] get tests passing --- actors/builtin/miner/deadlines.go | 2 +- actors/builtin/miner/miner_actor.go | 19 +++++++++++++++++-- actors/builtin/miner/miner_test.go | 17 ++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index 6feee535d..ebf148e05 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -74,6 +74,6 @@ func deadlineCanCompact(provingPeriodStart abi.ChainEpoch, dlIdx uint64, current // window post to give the network time to challenge the sectors, and at // least 1 window before the next window opens to avoid compacting an // immutable deadline. - return currentEpoch > (dlInfo.Close-WPoStProvingPeriod)+ChainFinality && + return currentEpoch >= (dlInfo.Close-WPoStProvingPeriod)+ChainFinality && currentEpoch < dlInfo.Open-WPoStChallengeWindow } diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 99259fa38..f75ffc316 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -417,6 +417,8 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) deadline, err := deadlines.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline %d", params.Deadline) + // TODO: we now check this twice: once here and once in the when + // we record the proofs. We can probably just do it once there. alreadyProven, err := bitfield.IntersectBitField(deadline.PostSubmissions, partitionIndexes) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check proven partitions") empty, err := alreadyProven.IsEmpty() @@ -438,8 +440,21 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to process post submission for deadline %d", params.Deadline) - // Skipped sectors (including retracted recoveries) pay nothing at Window PoSt, - // but will incur the "ongoing" fault fee at deadline end. + // Make sure we actually proved something. + + // TODO: refactor RecordProvenSectors to return what we actually need. + provenSectors, err := bitfield.SubtractBitField(postResult.Sectors, postResult.IgnoredSectors) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to determine proven sectors for deadline %d", params.Deadline) + + noSectors, err := provenSectors.IsEmpty() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to determine if any sectors were proven", params.Deadline) + if noSectors { + // Abort verification if all sectors are (now) faults. There's nothing to prove. + // It's not rational for a miner to submit a Window PoSt marking *all* non-faulty sectors as skipped, + // since that will just cause them to pay a penalty at deadline end that would otherwise be zero + // if they had *not* declared them. + rt.Abortf(exitcode.ErrIllegalArgument, "cannot prove partitions with no active sectors") + } // Store proofs in case they need to be challenged later. proofs, err := adt.AsArray(store, deadline.Proofs) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 0f8eb919e..cd1ad6aac 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -3395,6 +3395,9 @@ func TestCompactPartitions(t *testing.T) { sectors := bitfield.NewFromSet([]uint64{uint64(sector1)}) actor.terminateSectors(rt, sectors, expectedFee) + // Wait Finality epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.ChainFinality) + // compacting partition will remove sector1 but retain sector 2, 3 and 4. partId := uint64(0) deadlineId := uint64(0) @@ -3422,6 +3425,9 @@ func TestCompactPartitions(t *testing.T) { // fault sector1 actor.declareFaults(rt, info[0]) + // Wait Finality epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.ChainFinality) + partId := uint64(0) deadlineId := uint64(0) rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "failed to remove partitions from deadline 0: while removing partitions: cannot remove partition 0: has faults", func() { @@ -3435,10 +3441,19 @@ func TestCompactPartitions(t *testing.T) { rt := builder.Build(t) actor.constructAndVerify(rt) - rt.SetEpoch(200) + // Wait until deadline 0 (the one to which we'll assign the + // sector) has elapsed. That'll let us commit, prove, then wait + // finality epochs. + st := getState(rt) + deadlineEpoch := miner.NewDeadlineInfo(st.ProvingPeriodStart, 0, rt.Epoch()).NextNotElapsed().NextOpen() + rt.SetEpoch(deadlineEpoch) + // create 2 sectors in partition 0 actor.commitAndProveSectors(rt, 2, defaultSectorExpiration, [][]abi.DealID{{10}, {20}}) + // Wait Finality epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, deadlineEpoch+miner.ChainFinality) + partId := uint64(0) deadlineId := uint64(0) rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "failed to remove partitions from deadline 0: while removing partitions: cannot remove partition 0: has unproven sectors", func() { From a6f68f28d714f44570efc30e439b91adcc7e1ad7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 15:44:20 -0800 Subject: [PATCH 11/61] fix lint errors --- actors/builtin/miner/miner_actor.go | 2 ++ actors/builtin/miner/miner_state.go | 2 +- actors/builtin/miner/miner_test.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index f75ffc316..5f0a3cecc 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -616,6 +616,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // going to be an issue? faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge powerDelta, err = dl.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") // Delete challenged proof so it can't be charged multiple times. err = proofs.Delete(params.ProofIndex) @@ -640,6 +641,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa err := st.ApplyPenalty(penaltyTarget) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to apply penalty") penaltyFromVesting, penaltyFromBalance, err := st.RepayPartialDebtInPriorityOrder(store, currEpoch, rt.CurrentBalance()) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to pay debt") toBurn = big.Add(penaltyFromVesting, penaltyFromBalance) pledgeDelta = penaltyFromVesting.Neg() } diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index 545a92c1b..3c4e53d16 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1156,7 +1156,7 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad deadline.ProofsSnapshot = deadline.Proofs deadline.Proofs, err = adt.MakeEmptyArray(store).Root() if err != nil { - return nil, xerrors.Errorf("failed to clear pending proofs array", err) + return nil, xerrors.Errorf("failed to clear pending proofs array: %w", err) } deadline.SectorsSnapshot = st.Sectors diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index cd1ad6aac..15f4fa8a0 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -5091,7 +5091,7 @@ func (h *actorHarness) advancePastDeadlineEndWithCron(rt *mock.Runtime) { type poStConfig struct { expectedPowerDelta miner.PowerPair - verificationError error + //verificationError error } func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { From 0e7e691ebf123b461c9e965e69695fee4d4ebcfd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 10 Dec 2020 16:30:05 -0800 Subject: [PATCH 12/61] use correct sectors array for marking faults --- actors/builtin/miner/miner_actor.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 5f0a3cecc..407b3239b 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -587,9 +587,9 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") // Load sectors as seen at the end of the last proving period. - sectors, err := LoadSectors(store, dl.SectorsSnapshot) + sectorsSnapshot, err := LoadSectors(store, dl.SectorsSnapshot) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") - sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) + sectorInfos, err := sectorsSnapshot.LoadForProof(allSectorsNos, allIgnoredNos) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") // Find the proving period start for the deadline in question. @@ -612,8 +612,16 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // 2. Successful challenges will always penalize, even if we can't find the target sectors and mark them faulty. // You can run but you can't hide. - // TODO: this is using the sector _snapshot_ is that - // going to be an issue? + // Load sectors. We can't use the snapshot as a sector + // with an extended expiration could end up being + // scheduled to expire at the wrong time. + // + // Unfortunately, this means we'll need to load all + // sector infos twice (once for the proof, once to mark + // faulty). We can't use memoized power info because + // sectors may have been terminated. + sectors, err := LoadSectors(store, st.Sectors) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge powerDelta, err = dl.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") From f2fe3393a2a630aef72f582828139aaa34a868d9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Dec 2020 14:29:29 -0800 Subject: [PATCH 13/61] re-organize proof recording logic Re-organize proof recording logic to move it inside RecordProvenSectors. This means we can test it in the deadline invariant check. --- actors/builtin/miner/deadline_state.go | 45 ++++++++++++++++----- actors/builtin/miner/deadline_state_test.go | 15 ++++--- actors/builtin/miner/miner_actor.go | 23 +---------- actors/builtin/miner/miner_state.go | 9 ++--- actors/builtin/miner/miner_state_test.go | 2 +- actors/builtin/miner/miner_test.go | 2 + actors/builtin/miner/testing.go | 18 +++++++++ 7 files changed, 67 insertions(+), 47 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 0ebfa8ffc..3fc38432c 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -904,6 +904,12 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx // Reset PoSt submissions, snapshot proofs. dl.PostSubmissions = bitfield.New() + dl.PartitionsSnapshot = dl.Partitions + dl.ProofsSnapshot = dl.Proofs + dl.Proofs, err = adt.MakeEmptyArray(store).Root() + if err != nil { + return powerDelta, penalizedPower, xerrors.Errorf("failed to clear pending proofs array: %w", err) + } return powerDelta, penalizedPower, nil } @@ -936,7 +942,24 @@ func (dl *Deadline) RecordProvenSectors( store adt.Store, sectors Sectors, ssize abi.SectorSize, quant QuantSpec, faultExpiration abi.ChainEpoch, postPartitions []PoStPartition, + proofs []proof.PoStProof, ) (*PoStResult, error) { + + partitionIndexes := bitfield.New() + for _, partition := range postPartitions { + partitionIndexes.Set(partition.Index) + } + + // First check to see if we're proving any already proven partitions. + // This is faster than checking one by one. + if alreadyProven, err := bitfield.IntersectBitField(dl.PostSubmissions, partitionIndexes); err != nil { + return nil, xerrors.Errorf("failed to check proven partitions: %w", err) + } else if empty, err := alreadyProven.IsEmpty(); err != nil { + return nil, xerrors.Errorf("failed to check proven intersection is empty: %w", err) + } else if !empty { + return nil, xc.ErrIllegalArgument.Wrapf("partition already proven: %v", alreadyProven) + } + partitions, err := dl.PartitionsArray(store) if err != nil { return nil, err @@ -952,16 +975,6 @@ func (dl *Deadline) RecordProvenSectors( // Accumulate sectors info for proof verification. for _, post := range postPartitions { - // Note: In v3 we can remove this check because it will be rejected in the actor method. - alreadyProven, err := dl.PostSubmissions.IsSet(post.Index) - if err != nil { - return nil, xc.ErrIllegalState.Wrapf("failed to check if partition %d already posted: %w", post.Index, err) - } - if alreadyProven { - // Skip partitions already proven for this deadline. - continue - } - var partition Partition found, err := partitions.Get(post.Index, &partition) if err != nil { @@ -1027,6 +1040,18 @@ func (dl *Deadline) RecordProvenSectors( return nil, xc.ErrIllegalState.Wrapf("failed to persist partitions: %w", err) } + // Save proof. + if proofArr, err := adt.AsArray(store, dl.Proofs); err != nil { + return nil, xerrors.Errorf("failed to load proofs: %w", err) + } else if err := proofArr.AppendContinuous(&WindowedPoSt{ + Partitions: partitionIndexes, + Proofs: proofs, + }); err != nil { + return nil, xerrors.Errorf("failed to store proof: %w", err) + } else if dl.Proofs, err = proofArr.Root(); err != nil { + return nil, xerrors.Errorf("failed to save proofs: %w", err) + } + // Collect all sectors, faults, and recoveries for proof verification. allSectorNos, err := bitfield.MultiMerge(allSectors...) if err != nil { diff --git a/actors/builtin/miner/deadline_state_test.go b/actors/builtin/miner/deadline_state_test.go index 578812096..a39a6bc4f 100644 --- a/actors/builtin/miner/deadline_state_test.go +++ b/actors/builtin/miner/deadline_state_test.go @@ -79,7 +79,7 @@ func TestDeadlines(t *testing.T) { sectorArr := sectorsArr(t, store, sectors) // Prove everything - result, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 0, []miner.PoStPartition{{Index: 0}, {Index: 1}, {Index: 2}}) + result, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 0, []miner.PoStPartition{{Index: 0}, {Index: 1}, {Index: 2}}, nil) require.NoError(t, err) require.True(t, result.PowerDelta.Equals(power)) @@ -513,7 +513,7 @@ func TestDeadlines(t *testing.T) { postResult1, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, - }) + }, nil) require.NoError(t, err) assertBitfieldEquals(t, postResult1.Sectors, 1, 2, 3, 4, 5, 6, 7, 8) assertEmptyBitfield(t, postResult1.IgnoredSectors) @@ -531,9 +531,8 @@ func TestDeadlines(t *testing.T) { ).assert(t, store, dl) postResult2, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ - {Index: 1, Skipped: bf()}, // ignore already posted partitions {Index: 2, Skipped: bf()}, - }) + }, nil) require.NoError(t, err) assertBitfieldEquals(t, postResult2.Sectors, 9, 10) assertEmptyBitfield(t, postResult2.IgnoredSectors) @@ -601,7 +600,7 @@ func TestDeadlines(t *testing.T) { postResult, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf(1)}, {Index: 1, Skipped: bf(7)}, - }) + }, nil) require.NoError(t, err) // 1, 5, and 7 are expected to be faulty. @@ -669,7 +668,7 @@ func TestDeadlines(t *testing.T) { {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, {Index: 2, Skipped: bf(10)}, - }) + }, nil) require.NoError(t, err) assertBitfieldEquals(t, postResult1.Sectors, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -722,7 +721,7 @@ func TestDeadlines(t *testing.T) { _, err = dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf()}, {Index: 3, Skipped: bf()}, - }) + }, nil) require.Error(t, err) require.Contains(t, err.Error(), "no such partition") }) @@ -765,7 +764,7 @@ func TestDeadlines(t *testing.T) { {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, {Index: 2, Skipped: bf()}, - }) + }, nil) require.NoError(t, err) // 1 & 5 are still faulty diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 407b3239b..f4641ea51 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -417,16 +417,6 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) deadline, err := deadlines.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline %d", params.Deadline) - // TODO: we now check this twice: once here and once in the when - // we record the proofs. We can probably just do it once there. - alreadyProven, err := bitfield.IntersectBitField(deadline.PostSubmissions, partitionIndexes) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check proven partitions") - empty, err := alreadyProven.IsEmpty() - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check proven intersection is empty") - if !empty { - rt.Abortf(exitcode.ErrIllegalArgument, "partition already proven: %v", alreadyProven) - } - // Record proven sectors/partitions, returning updates to power and the final set of sectors // proven/skipped. // @@ -437,7 +427,7 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) // While we could perform _all_ operations at the end of challenge window, we do as we can here to avoid // overloading cron. faultExpiration := currDeadline.Last() + FaultMaxAge - postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions) + postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions, params.Proofs) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to process post submission for deadline %d", params.Deadline) // Make sure we actually proved something. @@ -456,17 +446,6 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) rt.Abortf(exitcode.ErrIllegalArgument, "cannot prove partitions with no active sectors") } - // Store proofs in case they need to be challenged later. - proofs, err := adt.AsArray(store, deadline.Proofs) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - err = proofs.AppendContinuous(&WindowedPoSt{ - Partitions: partitionIndexes, - Proofs: params.Proofs, - }) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to append new proofs") - deadline.Proofs, err = proofs.Root() - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed store proofs") - err = deadlines.UpdateDeadline(store, params.Deadline, deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index 3c4e53d16..f4636617e 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1152,12 +1152,9 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad } // Setup snapshots for challenges. - deadline.PartitionsSnapshot = deadline.Partitions - deadline.ProofsSnapshot = deadline.Proofs - deadline.Proofs, err = adt.MakeEmptyArray(store).Root() - if err != nil { - return nil, xerrors.Errorf("failed to clear pending proofs array: %w", err) - } + // TODO: we should probably put this into ProcessDeadlineEnd but + // then we'd need to pass the sectors in? Another reason to not + // snapshot the sector set. deadline.SectorsSnapshot = st.Sectors // Capture deadline's faulty power after new faults have been detected, but before it is diff --git a/actors/builtin/miner/miner_state_test.go b/actors/builtin/miner/miner_state_test.go index 0a5076ee5..87dcb165e 100644 --- a/actors/builtin/miner/miner_state_test.go +++ b/actors/builtin/miner/miner_state_test.go @@ -687,7 +687,7 @@ func TestSectorAssignment(t *testing.T) { // Now make sure proving activates power. - result, err := dl.RecordProvenSectors(harness.store, sectorArr, sectorSize, quantSpec, 0, postPartitions) + result, err := dl.RecordProvenSectors(harness.store, sectorArr, sectorSize, quantSpec, 0, postPartitions, nil) require.NoError(t, err) expectedPowerDelta := miner.PowerForSectors(sectorSize, selectSectors(t, sectorInfos, allSectorBf)) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 15f4fa8a0..2d19290c4 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1903,6 +1903,8 @@ func TestWindowPost(t *testing.T) { // Advance to end-of-deadline cron to verify no penalties. advanceDeadline(rt, actor, &cronConfig{}) actor.checkState(rt) + + // Proof should exist in state. }) t.Run("test duplicate proof rejected", func(t *testing.T) { diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index 340867a2b..4974ad181 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -208,6 +208,24 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua for _, p := range postSubmissions { acc.Require(p <= partitionCount, "invalid PoSt submission for partition %d of %d", p, partitionCount) } + + expectedPoSts := deadline.PostSubmissions + proofs, err := adt.AsArray(store, deadline.Proofs) + acc.RequireNoError(err, "failed to load proofs") + var post WindowedPoSt + err = proofs.ForEach(&post, func(idx int64) error { + acc.Require(len(post.Proofs) == 0, "invalid number of proofs") + contains, err := util.BitFieldContainsAll(expectedPoSts, post.Partitions) + acc.RequireNoError(err, "failed to check if posts were expected") + acc.Require(contains, "unexpected post") + expectedPoSts, err = bitfield.SubtractBitField(expectedPoSts, post.Partitions) + acc.RequireNoError(err, "failed to subtract found posts from expected posts") + return nil + }) + acc.RequireNoError(err, "failed to iterate over proofs") + missingPoSts, err := expectedPoSts.All(1 << 20) + acc.RequireNoError(err, "failed to expand missing posts") + acc.Require(len(missingPoSts) == 0, "missing posts for partitions: %v", missingPoSts) } // Check memoized sector and power values. From fc21f82e61f2ed417cd88cff7b421869c59e3518 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Dec 2020 14:52:25 -0800 Subject: [PATCH 14/61] forbid terminating sectors in a deadline being proved --- actors/builtin/miner/miner_actor.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index f4641ea51..0b2b8422f 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -1306,9 +1306,8 @@ type TerminateSectorsReturn = miner0.TerminateSectorsReturn // AddressedPartitionsMax per epoch until the queue is empty. // // The sectors are immediately ignored for Window PoSt proofs, and should be -// masked in the same way as faulty sectors. A miner terminating sectors in the -// current deadline must be careful to compute an appropriate Window PoSt proof -// for the sectors that will be active at the time the PoSt is submitted. +// masked in the same way as faulty sectors. A miner may not terminate sectors in the +// current deadline or the next deadline to be proven. // // This function may be invoked with no new sectors to explicitly process the // next batch of sectors. @@ -1353,6 +1352,12 @@ func (a Actor) TerminateSectors(rt Runtime, params *TerminateSectorsParams) *Ter builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors") err = toProcess.ForEach(func(dlIdx uint64, partitionSectors PartitionSectorMap) error { + // If the deadline the current or next deadline to prove, don't allow terminating sectors. + // We assume that deadlines are immutable when being proven. + if !deadlineIsMutable(st.ProvingPeriodStart, dlIdx, currEpoch) { + rt.Abortf(exitcode.ErrIllegalArgument, "cannot terminate sectors in immutable deadline %d", dlIdx) + } + quant := st.QuantSpecForDeadline(dlIdx) deadline, err := deadlines.LoadDeadline(store, dlIdx) From 53387d5247fa431671fe88ab0c9be415dc38b244 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 11 Dec 2020 16:42:13 -0800 Subject: [PATCH 15/61] fix tests --- actors/builtin/miner/testing.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index 4974ad181..a6fb3e679 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -214,7 +214,8 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua acc.RequireNoError(err, "failed to load proofs") var post WindowedPoSt err = proofs.ForEach(&post, func(idx int64) error { - acc.Require(len(post.Proofs) == 0, "invalid number of proofs") + // This is allowed to be 0 for testing. + //acc.Require(len(post.Proofs) == 0, "invalid number of proofs") contains, err := util.BitFieldContainsAll(expectedPoSts, post.Partitions) acc.RequireNoError(err, "failed to check if posts were expected") acc.Require(contains, "unexpected post") From 00c3c3ed76f6c6bfbbe41c0c7422188c56aaaa20 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Dec 2020 17:10:59 -0800 Subject: [PATCH 16/61] remove unecessary --- actors/builtin/miner/miner_actor.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 0b2b8422f..97bb801b4 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -349,11 +349,6 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) rt.Abortf(exitcode.ErrIllegalArgument, "expected at most %d bytes of randomness, got %d", abi.RandomnessLength, len(params.ChainCommitRand)) } - partitionIndexes := bitfield.New() - for _, partition := range params.Partitions { - partitionIndexes.Set(partition.Index) - } - var postResult *PoStResult var info *MinerInfo rt.StateTransaction(&st, func() { From 8d81b55683e58adca3b895cd3b1742c29168f28e Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Dec 2020 18:19:03 -0800 Subject: [PATCH 17/61] first-pass at addressing review comments --- actors/builtin/miner/cbor_gen.go | 34 +++++----- actors/builtin/miner/deadline_state.go | 65 ++++++++++++-------- actors/builtin/miner/deadline_state_test.go | 8 +-- actors/builtin/miner/deadlines.go | 2 +- actors/builtin/miner/miner_actor.go | 45 +++++++------- actors/builtin/miner/miner_test.go | 14 ++--- actors/builtin/miner/partition_state.go | 2 +- actors/builtin/miner/partition_state_test.go | 40 ++++++------ actors/builtin/miner/testing.go | 6 +- gen/gen.go | 2 +- 10 files changed, 115 insertions(+), 103 deletions(-) diff --git a/actors/builtin/miner/cbor_gen.go b/actors/builtin/miner/cbor_gen.go index 9166f64d1..1aef217f3 100644 --- a/actors/builtin/miner/cbor_gen.go +++ b/actors/builtin/miner/cbor_gen.go @@ -801,15 +801,15 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return err } - // t.PostSubmissions (bitfield.BitField) (struct) - if err := t.PostSubmissions.MarshalCBOR(w); err != nil { + // t.PartitionsPoSted (bitfield.BitField) (struct) + if err := t.PartitionsPoSted.MarshalCBOR(w); err != nil { return err } - // t.Proofs (cid.Cid) (struct) + // t.PoStSubmissions (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.Proofs); err != nil { - return xerrors.Errorf("failed to write cid field t.Proofs: %w", err) + if err := cbg.WriteCidBuf(scratch, w, t.PoStSubmissions); err != nil { + return xerrors.Errorf("failed to write cid field t.PoStSubmissions: %w", err) } // t.PartitionsSnapshot (cid.Cid) (struct) @@ -818,10 +818,10 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.PartitionsSnapshot: %w", err) } - // t.ProofsSnapshot (cid.Cid) (struct) + // t.PoStSubmissionsSnapshot (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.ProofsSnapshot); err != nil { - return xerrors.Errorf("failed to write cid field t.ProofsSnapshot: %w", err) + if err := cbg.WriteCidBuf(scratch, w, t.PoStSubmissionsSnapshot); err != nil { + return xerrors.Errorf("failed to write cid field t.PoStSubmissionsSnapshot: %w", err) } // t.SectorsSnapshot (cid.Cid) (struct) @@ -921,25 +921,25 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { } } - // t.PostSubmissions (bitfield.BitField) (struct) + // t.PartitionsPoSted (bitfield.BitField) (struct) { - if err := t.PostSubmissions.UnmarshalCBOR(br); err != nil { - return xerrors.Errorf("unmarshaling t.PostSubmissions: %w", err) + if err := t.PartitionsPoSted.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.PartitionsPoSted: %w", err) } } - // t.Proofs (cid.Cid) (struct) + // t.PoStSubmissions (cid.Cid) (struct) { c, err := cbg.ReadCid(br) if err != nil { - return xerrors.Errorf("failed to read cid field t.Proofs: %w", err) + return xerrors.Errorf("failed to read cid field t.PoStSubmissions: %w", err) } - t.Proofs = c + t.PoStSubmissions = c } // t.PartitionsSnapshot (cid.Cid) (struct) @@ -954,16 +954,16 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.PartitionsSnapshot = c } - // t.ProofsSnapshot (cid.Cid) (struct) + // t.PoStSubmissionsSnapshot (cid.Cid) (struct) { c, err := cbg.ReadCid(br) if err != nil { - return xerrors.Errorf("failed to read cid field t.ProofsSnapshot: %w", err) + return xerrors.Errorf("failed to read cid field t.PoStSubmissionsSnapshot: %w", err) } - t.ProofsSnapshot = c + t.PoStSubmissionsSnapshot = c } // t.SectorsSnapshot (cid.Cid) (struct) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 3fc38432c..68b0be4f2 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -53,29 +53,42 @@ type Deadline struct { // Memoized sum of faulty power in partitions. FaultyPower PowerPair - // Bitfield of partitions that have been posted. - PostSubmissions bitfield.BitField + // Partitions that have been proved by window PoSts so far during the + // current challenge window. + // + // Except during the challenge window immediately following the actors + // v3 upgrade, all partitions marked in this bitfield must also be + // marked in one of the "Partitions" bitfields in the Proofs AMT. This + // bitfield exists so we can efficiently determine which partitions have + // been proven, while the per-proof bitfields exist so that proofs can + // be challenged later. + PartitionsPoSted bitfield.BitField // AMT of WindowPoSt proofs. // TODO: this AMT could be inefficient. The proof will be ~200 bytes so // we could end up writing ~1600 bytes to update this AMT. // We could also mis-estimate gas, so we need to be very careful here. - Proofs cid.Cid // AMT[]WindowedPoSt + PoStSubmissions cid.Cid // AMT[]WindowedPoSt // Snapshot of partition state at the end of the previous challenge // window for this deadline. PartitionsSnapshot cid.Cid // Snapshot of the proofs submitted by the end of the previous challenge // window for this deadline. - ProofsSnapshot cid.Cid + PoStSubmissionsSnapshot cid.Cid // Snapshot of the miner's sectors at the end of the previous challenge // window for this deadline. SectorsSnapshot cid.Cid } type WindowedPoSt struct { + // Partitions proved by this WindowedPoSt. Partitions bitfield.BitField - Proofs []proof.PoStProof + // Array of proofs, one per distinct registered proof type present in + // the sectors being proven. In the usual case of a single proof type, + // this array will always have a single element (independent of number + // of partitions). + Proofs []proof.PoStProof } const DeadlinePartitionsAmtBitwidth = 3 @@ -163,17 +176,17 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { } return &Deadline{ - Partitions: emptyPartitionsArrayCid, - ExpirationsEpochs: emptyDeadlineExpirationArrayCid, - EarlyTerminations: bitfield.New(), - LiveSectors: 0, - TotalSectors: 0, - FaultyPower: NewPowerPairZero(), - PostSubmissions: bitfield.New(), - Proofs: emptyProofsArrayCid, - PartitionsSnapshot: emptyPartitionsArrayCid, - SectorsSnapshot: emptySectorsArrayCid, - ProofsSnapshot: emptyProofsArrayCid, + Partitions: emptyPartitionsArrayCid, + ExpirationsEpochs: emptyDeadlineExpirationArrayCid, + EarlyTerminations: bitfield.New(), + LiveSectors: 0, + TotalSectors: 0, + FaultyPower: NewPowerPairZero(), + PartitionsPoSted: bitfield.New(), + PoStSubmissions: emptyProofsArrayCid, + PartitionsSnapshot: emptyPartitionsArrayCid, + SectorsSnapshot: emptySectorsArrayCid, + PoStSubmissionsSnapshot: emptyProofsArrayCid, }, nil } @@ -725,7 +738,7 @@ func (dl *Deadline) RemovePartitions(store adt.Store, toRemove bitfield.BitField return live, dead, removedPower, nil } -func (dl *Deadline) DeclareFaults( +func (dl *Deadline) RecordFaults( store adt.Store, sectors Sectors, ssize abi.SectorSize, quant QuantSpec, faultExpirationEpoch abi.ChainEpoch, partitionSectors PartitionSectorMap, ) (powerDelta PowerPair, err error) { @@ -746,7 +759,7 @@ func (dl *Deadline) DeclareFaults( return xc.ErrNotFound.Wrapf("no such partition %d", partIdx) } - newFaults, partitionPowerDelta, partitionNewFaultyPower, err := partition.DeclareFaults( + newFaults, partitionPowerDelta, partitionNewFaultyPower, err := partition.RecordFaults( store, sectors, sectorNos, faultExpirationEpoch, ssize, quant, ) if err != nil { @@ -839,7 +852,7 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx detectedAny := false var rescheduledPartitions []uint64 for partIdx := uint64(0); partIdx < partitions.Length(); partIdx++ { - proven, err := dl.PostSubmissions.IsSet(partIdx) + proven, err := dl.PartitionsPoSted.IsSet(partIdx) if err != nil { return powerDelta, penalizedPower, xerrors.Errorf("failed to check submission for partition %d: %w", partIdx, err) } @@ -903,10 +916,10 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx } // Reset PoSt submissions, snapshot proofs. - dl.PostSubmissions = bitfield.New() + dl.PartitionsPoSted = bitfield.New() dl.PartitionsSnapshot = dl.Partitions - dl.ProofsSnapshot = dl.Proofs - dl.Proofs, err = adt.MakeEmptyArray(store).Root() + dl.PoStSubmissionsSnapshot = dl.PoStSubmissions + dl.PoStSubmissions, err = adt.MakeEmptyArray(store).Root() if err != nil { return powerDelta, penalizedPower, xerrors.Errorf("failed to clear pending proofs array: %w", err) } @@ -952,7 +965,7 @@ func (dl *Deadline) RecordProvenSectors( // First check to see if we're proving any already proven partitions. // This is faster than checking one by one. - if alreadyProven, err := bitfield.IntersectBitField(dl.PostSubmissions, partitionIndexes); err != nil { + if alreadyProven, err := bitfield.IntersectBitField(dl.PartitionsPoSted, partitionIndexes); err != nil { return nil, xerrors.Errorf("failed to check proven partitions: %w", err) } else if empty, err := alreadyProven.IsEmpty(); err != nil { return nil, xerrors.Errorf("failed to check proven intersection is empty: %w", err) @@ -1018,7 +1031,7 @@ func (dl *Deadline) RecordProvenSectors( powerDelta = powerDelta.Add(newPowerDelta).Add(recoveredPower) // Record the post. - dl.PostSubmissions.Set(post.Index) + dl.PartitionsPoSted.Set(post.Index) // At this point, the partition faults represents the expected faults for the proof, with new skipped // faults and recoveries taken into account. @@ -1041,14 +1054,14 @@ func (dl *Deadline) RecordProvenSectors( } // Save proof. - if proofArr, err := adt.AsArray(store, dl.Proofs); err != nil { + if proofArr, err := adt.AsArray(store, dl.PoStSubmissions); err != nil { return nil, xerrors.Errorf("failed to load proofs: %w", err) } else if err := proofArr.AppendContinuous(&WindowedPoSt{ Partitions: partitionIndexes, Proofs: proofs, }); err != nil { return nil, xerrors.Errorf("failed to store proof: %w", err) - } else if dl.Proofs, err = proofArr.Root(); err != nil { + } else if dl.PoStSubmissions, err = proofArr.Root(); err != nil { return nil, xerrors.Errorf("failed to save proofs: %w", err) } diff --git a/actors/builtin/miner/deadline_state_test.go b/actors/builtin/miner/deadline_state_test.go index a39a6bc4f..5912aab03 100644 --- a/actors/builtin/miner/deadline_state_test.go +++ b/actors/builtin/miner/deadline_state_test.go @@ -173,7 +173,7 @@ func TestDeadlines(t *testing.T) { addSectors(t, store, dl, proveFirst) // Mark faulty. - powerDelta, err := dl.DeclareFaults( + powerDelta, err := dl.RecordFaults( store, sectorsArr(t, store, sectors), sectorSize, quantSpec, 9, map[uint64]bitfield.BitField{ 0: bf(1), @@ -742,7 +742,7 @@ func TestDeadlines(t *testing.T) { })) // Retract recovery for sector 1. - powerDelta, err := dl.DeclareFaults(store, sectorArr, sectorSize, quantSpec, 13, map[uint64]bitfield.BitField{ + powerDelta, err := dl.RecordFaults(store, sectorArr, sectorSize, quantSpec, 13, map[uint64]bitfield.BitField{ 0: bf(1), }) @@ -848,7 +848,7 @@ func TestDeadlines(t *testing.T) { sectorArr := sectorsArr(t, store, allSectors) // Declare sectors 1 & 6 faulty. - _, err := dl.DeclareFaults(store, sectorArr, sectorSize, quantSpec, 17, map[uint64]bitfield.BitField{ + _, err := dl.RecordFaults(store, sectorArr, sectorSize, quantSpec, 17, map[uint64]bitfield.BitField{ 0: bf(1), 4: bf(6), }) @@ -951,7 +951,7 @@ func (s expectedDeadlineState) assert(t *testing.T, store adt.Store, dl *miner.D assertBitfieldsEqual(t, s.recovering, recoveries) assertBitfieldsEqual(t, s.terminations, terminations) assertBitfieldsEqual(t, s.unproven, unproven) - assertBitfieldsEqual(t, s.posts, dl.PostSubmissions) + assertBitfieldsEqual(t, s.posts, dl.PartitionsPoSted) partitions, err := dl.PartitionsArray(store) require.NoError(t, err) diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index ebf148e05..73bfab2bc 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -67,7 +67,7 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE return currentEpoch < dlInfo.Open-WPoStChallengeWindow } -func deadlineCanCompact(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { +func deadlineAvailableForCompaction(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() // Make sure the current epoch is at least finality after the last diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 97bb801b4..14893d43a 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -497,22 +497,22 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // TODO: move into deadline state function. { // Load the target state. - deadlines, err := st.LoadDeadlines(store) + deadlinesCurrent, err := st.LoadDeadlines(store) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadlines") - dl, err := deadlines.LoadDeadline(store, params.Deadline) + dlCurrent, err := deadlinesCurrent.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") - proofs, err := adt.AsArray(store, dl.ProofsSnapshot) + proofsSnapshot, err := adt.AsArray(store, dlCurrent.PoStSubmissionsSnapshot) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - partitionsSnapshot, err := adt.AsArray(store, dl.PartitionsSnapshot) + partitionsSnapshot, err := adt.AsArray(store, dlCurrent.PartitionsSnapshot) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") // Load the target proof. var post WindowedPoSt - found, err := proofs.Get(params.ProofIndex, &post) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof") + found, err := proofsSnapshot.Get(params.ProofIndex, &post) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof %d", params.ProofIndex) if !found { rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.ProofIndex) } @@ -520,20 +520,20 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa var allSectors, allIgnored []bitfield.BitField faults := make(PartitionSectorMap) err = post.Partitions.ForEach(func(partIdx uint64) error { - var partition Partition - if found, err := partitionsSnapshot.Get(partIdx, &partition); err != nil { + var partitionSnapshot Partition + if found, err := partitionsSnapshot.Get(partIdx, &partitionSnapshot); err != nil { return err } else if !found { return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.ProofIndex) } // Record sectors for proof verification - allSectors = append(allSectors, partition.Sectors) - allIgnored = append(allIgnored, partition.Faults) - allIgnored = append(allIgnored, partition.Terminated) + allSectors = append(allSectors, partitionSnapshot.Sectors) + allIgnored = append(allIgnored, partitionSnapshot.Faults) + allIgnored = append(allIgnored, partitionSnapshot.Terminated) // Record active sectors for marking faults. - active, err := partition.ActiveSectors() + active, err := partitionSnapshot.ActiveSectors() if err != nil { return err } @@ -547,9 +547,8 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // NOTE: This also includes power that was // activated at the end of the last challenge // window, and power from sectors that have since - // expired. That means we may end up over-penalizing, - // but that's fine. The miner submitted a bad proof. - faultyPower = faultyPower.Add(partition.ActivePower()) + // expired. + faultyPower = faultyPower.Add(partitionSnapshot.ActivePower()) return nil }) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") @@ -561,7 +560,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") // Load sectors as seen at the end of the last proving period. - sectorsSnapshot, err := LoadSectors(store, dl.SectorsSnapshot) + sectorsSnapshot, err := LoadSectors(store, dlCurrent.SectorsSnapshot) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") sectorInfos, err := sectorsSnapshot.LoadForProof(allSectorsNos, allIgnoredNos) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") @@ -597,18 +596,18 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa sectors, err := LoadSectors(store, st.Sectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge - powerDelta, err = dl.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) + powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") // Delete challenged proof so it can't be charged multiple times. - err = proofs.Delete(params.ProofIndex) + err = proofsSnapshot.Delete(params.ProofIndex) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete challenged proof") - dl.ProofsSnapshot, err = proofs.Root() + dlCurrent.PoStSubmissionsSnapshot, err = proofsSnapshot.Root() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update proofs") - err = deadlines.UpdateDeadline(store, params.Deadline, dl) + err = deadlinesCurrent.UpdateDeadline(store, params.Deadline, dlCurrent) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) - err = st.SaveDeadlines(store, deadlines) + err = st.SaveDeadlines(store, deadlinesCurrent) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to save deadlines") } @@ -1456,7 +1455,7 @@ func (a Actor) DeclareFaults(rt Runtime, params *DeclareFaultsParams) *abi.Empty builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline %d", dlIdx) faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge - deadlinePowerDelta, err := deadline.DeclareFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, pm) + deadlinePowerDelta, err := deadline.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, pm) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults for deadline %d", dlIdx) err = deadlines.UpdateDeadline(store, dlIdx, deadline) @@ -1595,7 +1594,7 @@ func (a Actor) CompactPartitions(rt Runtime, params *CompactPartitionsParams) *a info := getMinerInfo(rt, &st) rt.ValidateImmediateCallerIs(append(info.ControlAddresses, info.Owner, info.Worker)...) - if !deadlineCanCompact(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { + if !deadlineAvailableForCompaction(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { rt.Abortf(exitcode.ErrForbidden, "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, ChainFinality) } diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 2d19290c4..4b5be5ee0 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -134,7 +134,7 @@ func TestConstruction(t *testing.T) { rt.StoreGet(deadlines.Due[i], &deadline) assert.True(t, deadline.Partitions.Defined()) assert.True(t, deadline.ExpirationsEpochs.Defined()) - assertEmptyBitfield(t, deadline.PostSubmissions) + assertEmptyBitfield(t, deadline.PartitionsPoSted) assertEmptyBitfield(t, deadline.EarlyTerminations) assert.Equal(t, uint64(0), deadline.LiveSectors) } @@ -493,7 +493,7 @@ func TestCommitments(t *testing.T) { require.NoError(t, err) deadline, partition := actor.getDeadlineAndPartition(rt, dlIdx, pIdx) assert.Equal(t, uint64(1), deadline.LiveSectors) - assertEmptyBitfield(t, deadline.PostSubmissions) + assertEmptyBitfield(t, deadline.PartitionsPoSted) assertEmptyBitfield(t, deadline.EarlyTerminations) quant := st.QuantSpecForDeadline(dlIdx) @@ -1219,7 +1219,7 @@ func TestCCUpgrade(t *testing.T) { require.NoError(t, err) sectorArr, err := miner.LoadSectors(rt.AdtStore(), st.Sectors) require.NoError(t, err) - newFaults, _, _, err := partition.DeclareFaults(rt.AdtStore(), sectorArr, bf(uint64(oldSectors[0].SectorNumber)), 100000, + newFaults, _, _, err := partition.RecordFaults(rt.AdtStore(), sectorArr, bf(uint64(oldSectors[0].SectorNumber)), 100000, actor.sectorSize, quant) require.NoError(t, err) assertBitfieldEquals(t, newFaults, uint64(oldSectors[0].SectorNumber)) @@ -1898,7 +1898,7 @@ func TestWindowPost(t *testing.T) { // Verify proof recorded deadline := actor.getDeadline(rt, dlIdx) - assertBitfieldEquals(t, deadline.PostSubmissions, pIdx) + assertBitfieldEquals(t, deadline.PartitionsPoSted, pIdx) // Advance to end-of-deadline cron to verify no penalties. advanceDeadline(rt, actor, &cronConfig{}) @@ -1934,7 +1934,7 @@ func TestWindowPost(t *testing.T) { // Verify proof recorded deadline := actor.getDeadline(rt, dlIdx) - assertBitfieldEquals(t, deadline.PostSubmissions, pIdx) + assertBitfieldEquals(t, deadline.PartitionsPoSted, pIdx) // Submit a duplicate proof for the same partition. This will be rejected because after ignoring the // already-proven partition, there are no sectors remaining. @@ -1999,7 +1999,7 @@ func TestWindowPost(t *testing.T) { }) // Verify proof recorded deadline := actor.getDeadline(rt, dlIdx) - assertBitfieldEquals(t, deadline.PostSubmissions, 0) + assertBitfieldEquals(t, deadline.PartitionsPoSted, 0) } { // Attempt PoSt for both partitions, thus duplicating proof for partition 0, so rejected @@ -2030,7 +2030,7 @@ func TestWindowPost(t *testing.T) { }) // Verify both proofs now recorded deadline := actor.getDeadline(rt, dlIdx) - assertBitfieldEquals(t, deadline.PostSubmissions, 0, 1) + assertBitfieldEquals(t, deadline.PartitionsPoSted, 0, 1) } // Advance to end-of-deadline cron to verify no penalties. diff --git a/actors/builtin/miner/partition_state.go b/actors/builtin/miner/partition_state.go index 7f1599ae1..b544d2ca5 100644 --- a/actors/builtin/miner/partition_state.go +++ b/actors/builtin/miner/partition_state.go @@ -236,7 +236,7 @@ func (p *Partition) addFaults( // - The sectors' expirations are rescheduled to the fault expiration epoch, as "early" (if not expiring earlier). // // Returns the power of the now-faulty sectors. -func (p *Partition) DeclareFaults( +func (p *Partition) RecordFaults( store adt.Store, sectors Sectors, sectorNos bitfield.BitField, faultExpirationEpoch abi.ChainEpoch, ssize abi.SectorSize, quant QuantSpec, ) (newFaults bitfield.BitField, powerDelta, newFaultyPower PowerPair, err error) { diff --git a/actors/builtin/miner/partition_state_test.go b/actors/builtin/miner/partition_state_test.go index 0963b35a1..8044f9f49 100644 --- a/actors/builtin/miner/partition_state_test.go +++ b/actors/builtin/miner/partition_state_test.go @@ -93,7 +93,7 @@ func TestPartitions(t *testing.T) { sectorArr := sectorsArr(t, store, sectors) faultSet := bf(4, 5) - _, powerDelta, newFaultyPower, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, faultSet)) @@ -129,7 +129,7 @@ func TestPartitions(t *testing.T) { sectorArr := sectorsArr(t, store, sectors) faultSet := bf(4, 5) - _, powerDelta, newFaultyPower, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) expectedFaultyPower := miner.PowerForSectors(sectorSize, selectSectors(t, sectors, faultSet)) @@ -137,7 +137,7 @@ func TestPartitions(t *testing.T) { assert.True(t, powerDelta.Equals(expectedFaultyPower.Neg())) faultSet = bf(5, 6) - newFaults, powerDelta, newFaultyPower, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(3), sectorSize, quantSpec) + newFaults, powerDelta, newFaultyPower, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(3), sectorSize, quantSpec) require.NoError(t, err) assertBitfieldEquals(t, newFaults, 6) expectedFaultyPower = miner.PowerForSectors(sectorSize, selectSectors(t, sectors, bf(6))) @@ -158,7 +158,7 @@ func TestPartitions(t *testing.T) { sectorArr := sectorsArr(t, store, sectors) faultSet := bf(99) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.Error(t, err) assert.Contains(t, err.Error(), "not all sectors are assigned to the partition") }) @@ -169,7 +169,7 @@ func TestPartitions(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 4 and 5 as recoveries @@ -186,7 +186,7 @@ func TestPartitions(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 4 and 5 as recoveries @@ -195,14 +195,14 @@ func TestPartitions(t *testing.T) { require.NoError(t, err) // declaring no faults doesn't do anything. - newFaults, _, _, err := partition.DeclareFaults(store, sectorArr, bf(), abi.ChainEpoch(7), sectorSize, quantSpec) + newFaults, _, _, err := partition.RecordFaults(store, sectorArr, bf(), abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) assertBitfieldEmpty(t, newFaults) // no new faults. assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), bf(4, 5, 6), bf(4, 5), bf(), bf()) // removing sector 5 alters recovery set and recovery power - newFaults, _, _, err = partition.DeclareFaults(store, sectorArr, bf(5), abi.ChainEpoch(10), sectorSize, quantSpec) + newFaults, _, _, err = partition.RecordFaults(store, sectorArr, bf(5), abi.ChainEpoch(10), sectorSize, quantSpec) require.NoError(t, err) assertBitfieldEmpty(t, newFaults) // these faults aren't new. @@ -215,7 +215,7 @@ func TestPartitions(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 4 and 5 as recoveries @@ -248,7 +248,7 @@ func TestPartitions(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 3, 4 and 5 as recoveries. 3 is not faulty so it's skipped @@ -283,7 +283,7 @@ func TestPartitions(t *testing.T) { // Mark sector 2 faulty, we should skip it when rescheduling faultSet := bf(2) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // Add an unproven sector. We _should_ reschedule the expiration. @@ -369,7 +369,7 @@ func TestPartitions(t *testing.T) { // fault sector 2 faultSet := bf(2) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // remove 3 sectors starting with 2 @@ -420,7 +420,7 @@ func TestPartitions(t *testing.T) { // fault sector 3, 4, 5 and 6 faultSet := bf(3, 4, 5, 6) - _, _, _, err = partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // mark 4and 5 as a recoveries @@ -501,7 +501,7 @@ func TestPartitions(t *testing.T) { require.NoError(t, err) // Fault declaration for terminated sectors fails. - newFaults, _, _, err := partition.DeclareFaults(store, sectorArr, terminations, abi.ChainEpoch(5), sectorSize, quantSpec) + newFaults, _, _, err := partition.RecordFaults(store, sectorArr, terminations, abi.ChainEpoch(5), sectorSize, quantSpec) require.NoError(t, err) empty, err := newFaults.IsEmpty() require.NoError(t, err) @@ -514,7 +514,7 @@ func TestPartitions(t *testing.T) { // add one fault with an early termination faultSet := bf(4) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(2), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(2), sectorSize, quantSpec) require.NoError(t, err) // pop first expiration set @@ -555,7 +555,7 @@ func TestPartitions(t *testing.T) { store, partition := setup(t) sectorArr := sectorsArr(t, store, sectors) - _, _, _, err := partition.DeclareFaults(store, sectorArr, bf(5), abi.ChainEpoch(2), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, bf(5), abi.ChainEpoch(2), sectorSize, quantSpec) require.NoError(t, err) // add a recovery @@ -598,7 +598,7 @@ func TestPartitions(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err = partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 4 and 5 as recoveries @@ -638,7 +638,7 @@ func TestPartitions(t *testing.T) { // fault sector 3, 4, 5 and 6 faultSet := bf(3, 4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // mark 4and 5 as a recoveries @@ -791,7 +791,7 @@ func TestRecordSkippedFaults(t *testing.T) { // declare 4 & 5 as faulty faultSet := bf(4, 5) - _, _, _, err = partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err = partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) assertPartitionState(t, store, partition, quantSpec, sectorSize, sectors, bf(1, 2, 3, 4, 5, 6), faultSet, bf(), terminations, bf()) @@ -814,7 +814,7 @@ func TestRecordSkippedFaults(t *testing.T) { // make 4, 5 and 6 faulty faultSet := bf(4, 5, 6) - _, _, _, err := partition.DeclareFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) + _, _, _, err := partition.RecordFaults(store, sectorArr, faultSet, abi.ChainEpoch(7), sectorSize, quantSpec) require.NoError(t, err) // add 4 and 5 as recoveries diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index a6fb3e679..6a43c4acf 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -202,15 +202,15 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua acc.RequireNoError(err, "error iterating partitions") // Check PoSt submissions - if postSubmissions, err := deadline.PostSubmissions.All(1 << 20); err != nil { + if postSubmissions, err := deadline.PartitionsPoSted.All(1 << 20); err != nil { acc.Addf("error expanding post submissions: %v", err) } else { for _, p := range postSubmissions { acc.Require(p <= partitionCount, "invalid PoSt submission for partition %d of %d", p, partitionCount) } - expectedPoSts := deadline.PostSubmissions - proofs, err := adt.AsArray(store, deadline.Proofs) + expectedPoSts := deadline.PartitionsPoSted + proofs, err := adt.AsArray(store, deadline.PoStSubmissions) acc.RequireNoError(err, "failed to load proofs") var post WindowedPoSt err = proofs.ForEach(&post, func(idx int64) error { diff --git a/gen/gen.go b/gen/gen.go index 2d18be41c..a5bafd50f 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -180,7 +180,6 @@ func main() { miner.VestingFunds{}, miner.VestingFund{}, miner.WindowedPoSt{}, - miner.ChallengeWindowedPoStParams{}, // method params and returns // miner.ConstructorParams{}, // in power actor //miner.SubmitWindowedPoStParams{}, // Aliased from v0 @@ -200,6 +199,7 @@ func main() { //miner.CompactPartitionsParams{}, // Aliased from v0 //miner.CompactSectorNumbersParams{}, // Aliased from v0 //miner.CronEventPayload{}, // Aliased from v0 + miner.ChallengeWindowedPoStParams{}, // other types //miner.FaultDeclaration{}, // Aliased from v0 //miner.RecoveryDeclaration{}, // Aliased from v0 From f6db8a06d0289f0e54c12fc207e05d381829e6cc Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 15 Dec 2020 18:21:38 -0800 Subject: [PATCH 18/61] reduce log level for successful window post challenges --- actors/builtin/miner/miner_actor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 14893d43a..43909d2d2 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -2280,7 +2280,7 @@ func challengeWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors [] if err = rt.VerifyPoSt(pvInfo); err == nil { rt.Abortf(exitcode.ErrIllegalArgument, "valid PoSt %+v", pvInfo) } else { - rt.Log(rtt.WARN, "PoSt successfully challenged: %s", err) + rt.Log(rtt.INFO, "PoSt successfully challenged: %s", err) } } From 104bdb5aaa844d00f3a4426f05ff4b1aa529607b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Dec 2020 16:04:44 -0800 Subject: [PATCH 19/61] fixup after rebase --- actors/builtin/miner/deadline_state.go | 12 ++++++------ actors/builtin/miner/miner_actor.go | 4 ++-- actors/builtin/miner/testing.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 68b0be4f2..0f80fd4d1 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -93,7 +93,7 @@ type WindowedPoSt struct { const DeadlinePartitionsAmtBitwidth = 3 const DeadlineExpirationAmtBitwidth = 5 -const DeadlineProofsAmtBitwidth = 5 +const DeadlinePoStSubmissionsAmtBitwidth = 5 // // Deadlines (plural) @@ -165,7 +165,7 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { return nil, xerrors.Errorf("failed to construct empty deadline expiration array: %w", err) } - emptyProofsArrayCid, err := adt.StoreEmptyArray(store, DeadlineProofsAmtBitwidth) + emptyPoStSubmissionsArrayCid, err := adt.StoreEmptyArray(store, DeadlinePoStSubmissionsAmtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to construct empty proofs array: %w", err) } @@ -183,10 +183,10 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { TotalSectors: 0, FaultyPower: NewPowerPairZero(), PartitionsPoSted: bitfield.New(), - PoStSubmissions: emptyProofsArrayCid, + PoStSubmissions: emptyPoStSubmissionsArrayCid, PartitionsSnapshot: emptyPartitionsArrayCid, SectorsSnapshot: emptySectorsArrayCid, - PoStSubmissionsSnapshot: emptyProofsArrayCid, + PoStSubmissionsSnapshot: emptyPoStSubmissionsArrayCid, }, nil } @@ -919,7 +919,7 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx dl.PartitionsPoSted = bitfield.New() dl.PartitionsSnapshot = dl.Partitions dl.PoStSubmissionsSnapshot = dl.PoStSubmissions - dl.PoStSubmissions, err = adt.MakeEmptyArray(store).Root() + dl.PoStSubmissions, err = adt.StoreEmptyArray(store, DeadlinePoStSubmissionsAmtBitwidth) if err != nil { return powerDelta, penalizedPower, xerrors.Errorf("failed to clear pending proofs array: %w", err) } @@ -1054,7 +1054,7 @@ func (dl *Deadline) RecordProvenSectors( } // Save proof. - if proofArr, err := adt.AsArray(store, dl.PoStSubmissions); err != nil { + if proofArr, err := adt.AsArray(store, dl.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth); err != nil { return nil, xerrors.Errorf("failed to load proofs: %w", err) } else if err := proofArr.AppendContinuous(&WindowedPoSt{ Partitions: partitionIndexes, diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 43909d2d2..6fa0668b9 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -503,10 +503,10 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa dlCurrent, err := deadlinesCurrent.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") - proofsSnapshot, err := adt.AsArray(store, dlCurrent.PoStSubmissionsSnapshot) + proofsSnapshot, err := adt.AsArray(store, dlCurrent.PoStSubmissionsSnapshot, DeadlinePoStSubmissionsAmtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - partitionsSnapshot, err := adt.AsArray(store, dlCurrent.PartitionsSnapshot) + partitionsSnapshot, err := adt.AsArray(store, dlCurrent.PartitionsSnapshot, DeadlinePoStSubmissionsAmtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") // Load the target proof. diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index 6a43c4acf..b49e97ee8 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -210,7 +210,7 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua } expectedPoSts := deadline.PartitionsPoSted - proofs, err := adt.AsArray(store, deadline.PoStSubmissions) + proofs, err := adt.AsArray(store, deadline.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth) acc.RequireNoError(err, "failed to load proofs") var post WindowedPoSt err = proofs.ForEach(&post, func(idx int64) error { From 171b7459efdb400516c7fc1f39f7814797e62f7c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 16 Dec 2020 16:13:37 -0800 Subject: [PATCH 20/61] fix migration --- actors/migration/nv9/miner.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/actors/migration/nv9/miner.go b/actors/migration/nv9/miner.go index 83132c0bd..97667a119 100644 --- a/actors/migration/nv9/miner.go +++ b/actors/migration/nv9/miner.go @@ -11,6 +11,7 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/v3/actors/util/adt" adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" ) @@ -77,6 +78,12 @@ func (m *minerMigrator) migrateDeadlines(ctx context.Context, store cbor.IpldSto outDeadlines := miner3.Deadlines{Due: [miner3.WPoStPeriodDeadlines]cid.Cid{}} + // Start from an empty template to zero-initialize new fields. + deadlineTemplate, err := miner3.ConstructDeadline(adt.WrapStore(ctx, store)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to construct new deadline template") + } + for i, c := range inDeadlines.Due { var inDeadline miner2.Deadline if err = store.Get(ctx, c, &inDeadline); err != nil { @@ -93,15 +100,14 @@ func (m *minerMigrator) migrateDeadlines(ctx context.Context, store cbor.IpldSto return cid.Undef, xerrors.Errorf("bitfield queue: %w", err) } - outDeadline := miner3.Deadline{ - Partitions: partitions, - ExpirationsEpochs: expirationEpochs, - PostSubmissions: inDeadline.PostSubmissions, - EarlyTerminations: inDeadline.EarlyTerminations, - LiveSectors: inDeadline.LiveSectors, - TotalSectors: inDeadline.TotalSectors, - FaultyPower: miner3.PowerPair(inDeadline.FaultyPower), - } + outDeadline := *deadlineTemplate + outDeadline.Partitions = partitions + outDeadline.ExpirationsEpochs = expirationEpochs + outDeadline.PartitionsPoSted = inDeadline.PostSubmissions + outDeadline.EarlyTerminations = inDeadline.EarlyTerminations + outDeadline.LiveSectors = inDeadline.LiveSectors + outDeadline.TotalSectors = inDeadline.TotalSectors + outDeadline.FaultyPower = miner3.PowerPair(inDeadline.FaultyPower) outDlCid, err := store.Put(ctx, &outDeadline) if err != nil { From 8fa54b17649aed1b32c6be8e1f75f0fa52dda123 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 10:53:59 -0800 Subject: [PATCH 21/61] add a todo to finish some params --- actors/builtin/miner/miner_actor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 6fa0668b9..ee2bc81f3 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -613,6 +613,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // Penalties. { + // TODO(PARAM): consider applying a multiplier on the penalty penaltyTarget := PledgePenaltyForContinuedFault( epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, From 8a3269b3977140ade84d4426e0fd932323714ccd Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 13:22:59 -0800 Subject: [PATCH 22/61] restrict post challenges to a challenge window This way, we can guarantee that nothing has moved and no sector infos for the deadline have been deleted. --- actors/builtin/miner/cbor_gen.go | 22 ++--------------- actors/builtin/miner/deadline_state.go | 9 ------- actors/builtin/miner/deadlines.go | 17 +++++++------ actors/builtin/miner/miner_actor.go | 34 ++++++++------------------ actors/builtin/miner/miner_state.go | 6 ----- actors/builtin/miner/policy.go | 5 ++++ 6 files changed, 27 insertions(+), 66 deletions(-) diff --git a/actors/builtin/miner/cbor_gen.go b/actors/builtin/miner/cbor_gen.go index 1aef217f3..41d25a49d 100644 --- a/actors/builtin/miner/cbor_gen.go +++ b/actors/builtin/miner/cbor_gen.go @@ -754,7 +754,7 @@ func (t *Deadlines) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufDeadline = []byte{139} +var lengthBufDeadline = []byte{138} func (t *Deadline) MarshalCBOR(w io.Writer) error { if t == nil { @@ -824,12 +824,6 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.PoStSubmissionsSnapshot: %w", err) } - // t.SectorsSnapshot (cid.Cid) (struct) - - if err := cbg.WriteCidBuf(scratch, w, t.SectorsSnapshot); err != nil { - return xerrors.Errorf("failed to write cid field t.SectorsSnapshot: %w", err) - } - return nil } @@ -847,7 +841,7 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input should be of type array") } - if extra != 11 { + if extra != 10 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -965,18 +959,6 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.PoStSubmissionsSnapshot = c - } - // t.SectorsSnapshot (cid.Cid) (struct) - - { - - c, err := cbg.ReadCid(br) - if err != nil { - return xerrors.Errorf("failed to read cid field t.SectorsSnapshot: %w", err) - } - - t.SectorsSnapshot = c - } return nil } diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 0f80fd4d1..5da1c04bf 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -76,9 +76,6 @@ type Deadline struct { // Snapshot of the proofs submitted by the end of the previous challenge // window for this deadline. PoStSubmissionsSnapshot cid.Cid - // Snapshot of the miner's sectors at the end of the previous challenge - // window for this deadline. - SectorsSnapshot cid.Cid } type WindowedPoSt struct { @@ -170,11 +167,6 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { return nil, xerrors.Errorf("failed to construct empty proofs array: %w", err) } - emptySectorsArrayCid, err := adt.StoreEmptyArray(store, SectorsAmtBitwidth) - if err != nil { - return nil, xerrors.Errorf("failed to construct empty sectors array: %w", err) - } - return &Deadline{ Partitions: emptyPartitionsArrayCid, ExpirationsEpochs: emptyDeadlineExpirationArrayCid, @@ -185,7 +177,6 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { PartitionsPoSted: bitfield.New(), PoStSubmissions: emptyPoStSubmissionsArrayCid, PartitionsSnapshot: emptyPartitionsArrayCid, - SectorsSnapshot: emptySectorsArrayCid, PoStSubmissionsSnapshot: emptyPoStSubmissionsArrayCid, }, nil } diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index 73bfab2bc..b7077230d 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -67,13 +67,16 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE return currentEpoch < dlInfo.Open-WPoStChallengeWindow } -func deadlineAvailableForCompaction(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { +func deadlineAvailableForChallenge(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() - // Make sure the current epoch is at least finality after the last - // window post to give the network time to challenge the sectors, and at - // least 1 window before the next window opens to avoid compacting an - // immutable deadline. - return currentEpoch >= (dlInfo.Close-WPoStProvingPeriod)+ChainFinality && - currentEpoch < dlInfo.Open-WPoStChallengeWindow + // We can challenge a proof in a deadline: + // 1. After it closes. + // 2. + return currentEpoch >= dlInfo.Close && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStProofChallengePeriod +} + +func deadlineAvailableForCompaction(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { + return deadlineIsMutable(provingPeriodStart, dlIdx, currentEpoch) && + !deadlineAvailableForChallenge(provingPeriodStart, dlIdx, currentEpoch) } diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index ee2bc81f3..969513ac2 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -484,8 +484,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa powerDelta := NewPowerPairZero() var st State rt.StateTransaction(&st, func() { - if st.CurrentDeadline == params.Deadline { - // TODO: blank out the previous deadline as well? + if !deadlineAvailableForChallenge(st.ProvingPeriodStart, params.Deadline, currEpoch) { rt.Abortf(exitcode.ErrForbidden, "cannot challenge a window post for an open deadline") } @@ -559,10 +558,10 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") - // Load sectors as seen at the end of the last proving period. - sectorsSnapshot, err := LoadSectors(store, dlCurrent.SectorsSnapshot) + // Load sectors. + sectors, err := LoadSectors(store, st.Sectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") - sectorInfos, err := sectorsSnapshot.LoadForProof(allSectorsNos, allIgnoredNos) + sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") // Find the proving period start for the deadline in question. @@ -576,25 +575,12 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // Fails if validation succeeds. challengeWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs) - // Ok, so, here's the tricky part. Now we need to mark these all as _bad_. However, compaction could have - // happened, so the sectors could have moved. - // - // We've addressed this in two ways: - // - // 1. Compaction cannot be done for Finality epochs after a challenge window. - // 2. Successful challenges will always penalize, even if we can't find the target sectors and mark them faulty. - // You can run but you can't hide. - - // Load sectors. We can't use the snapshot as a sector - // with an extended expiration could end up being - // scheduled to expire at the wrong time. + // Ok, now we record faults. This always works because + // we don't allow compaction/moving sectors during the + // challenge window. // - // Unfortunately, this means we'll need to load all - // sector infos twice (once for the proof, once to mark - // faulty). We can't use memoized power info because - // sectors may have been terminated. - sectors, err := LoadSectors(store, st.Sectors) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") + // However, some of these sectors may have been + // terminated. That's fine, we'll skip them. faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") @@ -1597,7 +1583,7 @@ func (a Actor) CompactPartitions(rt Runtime, params *CompactPartitionsParams) *a if !deadlineAvailableForCompaction(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { rt.Abortf(exitcode.ErrForbidden, - "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, ChainFinality) + "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, WPoStProofChallengePeriod) } submissionPartitionLimit := loadPartitionsSectorsMax(info.WindowPoStPartitionSectors) diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index f4636617e..c3dae5fdd 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1151,12 +1151,6 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad return nil, xerrors.Errorf("failed to process end of deadline %d: %w", dlInfo.Index, err) } - // Setup snapshots for challenges. - // TODO: we should probably put this into ProcessDeadlineEnd but - // then we'd need to pass the sectors in? Another reason to not - // snapshot the sector set. - deadline.SectorsSnapshot = st.Sectors - // Capture deadline's faulty power after new faults have been detected, but before it is // dropped along with faulty sectors expiring this round. totalFaultyPower = deadline.FaultyPower diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index f626f03e2..e32080852 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -196,6 +196,11 @@ const DealLimitDenominator = 134217728 // PARAM_SPEC // for permissioned actor methods and winning block elections. const ConsensusFaultIneligibilityDuration = ChainFinality +// PoStChallengePeriod is the period after a challenge window ends during which +// PoSts submitted during that period may be challenged. +// TODO: this name can easily be confused with WPoStChallengeWindow. +const WPoStProofChallengePeriod = ChainFinality // PARAM_TODO + // DealWeight and VerifiedDealWeight are spacetime occupied by regular deals and verified deals in a sector. // Sum of DealWeight and VerifiedDealWeight should be less than or equal to total SpaceTime of a sector. // Sectors full of VerifiedDeals will have a SectorQuality of VerifiedDealWeightMultiplier/QualityBaseMultiplier. From a678dd834375d842d01c28cecd2597ada9430c30 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 13:56:54 -0800 Subject: [PATCH 23/61] ignore unproven sectors for proof verification This should never be the case, but there's no reason not to ignore these. Basically, if the proof _had_ included these sectors, they should no longer be in the unproven bitfield. --- actors/builtin/miner/miner_actor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 969513ac2..b85d9fad9 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -530,6 +530,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa allSectors = append(allSectors, partitionSnapshot.Sectors) allIgnored = append(allIgnored, partitionSnapshot.Faults) allIgnored = append(allIgnored, partitionSnapshot.Terminated) + allIgnored = append(allIgnored, partitionSnapshot.Unproven) // Record active sectors for marking faults. active, err := partitionSnapshot.ActiveSectors() From 99ad44ddee42f771f59c0f0c5da73836da3b74cc Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 14:32:45 -0800 Subject: [PATCH 24/61] increase the proof challenge period to 2x finality --- actors/builtin/miner/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index e32080852..946fb0879 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -199,7 +199,7 @@ const ConsensusFaultIneligibilityDuration = ChainFinality // PoStChallengePeriod is the period after a challenge window ends during which // PoSts submitted during that period may be challenged. // TODO: this name can easily be confused with WPoStChallengeWindow. -const WPoStProofChallengePeriod = ChainFinality // PARAM_TODO +const WPoStProofChallengePeriod = 2 * ChainFinality // PARAM_TODO // DealWeight and VerifiedDealWeight are spacetime occupied by regular deals and verified deals in a sector. // Sum of DealWeight and VerifiedDealWeight should be less than or equal to total SpaceTime of a sector. From 04ed504af1829003a52951e7f85d0f4c4a46d1ce Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 14:33:03 -0800 Subject: [PATCH 25/61] test constraints on proving period times This ensures we don't accidentally violate some constraint and make something impossible. --- actors/builtin/miner/policy_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/actors/builtin/miner/policy_test.go b/actors/builtin/miner/policy_test.go index 71e712418..feee96c10 100644 --- a/actors/builtin/miner/policy_test.go +++ b/actors/builtin/miner/policy_test.go @@ -6,6 +6,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/filecoin-project/specs-actors/v3/actors/builtin" "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" @@ -144,3 +145,24 @@ func assertEqual(t *testing.T, a, b big.Int) { assert.Equal(t, a, b) } } + +func TestPoStTimeConstraints(t *testing.T) { + // period during which a deadline is "immutable" + immutablePeriod := 2 * miner.WPoStChallengeWindow + + // If we have less than finality, an attacker could try a very long + // (almost finality) fork and leave no time to challenge. + require.True(t, miner.WPoStProofChallengePeriod >= miner.ChainFinality, + "the proof challenge period must exceed finality") + + // This is an arbitrary constraint, but we should ensure we leave _some_ + // time for compaction. + compactionPeriod := miner.WPoStProvingPeriod - (immutablePeriod + miner.WPoStProofChallengePeriod) + require.True(t, compactionPeriod > 3*builtin.EpochsInHour, + "there must be at least a 3 hour window for partition compaction") + + // If this constraint breaks, the challenge lookback may be _before_ the + // immutability period starts. + require.True(t, miner.WPoStChallengeLookback < immutablePeriod-miner.WPoStChallengeWindow, + "the challenge lookback must be inside the immutability period") +} From 7c8ab0de0b133f15a350fcfce3945c25381d43f3 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 15:11:15 -0800 Subject: [PATCH 26/61] fix challenge window logic and test it --- actors/builtin/miner/deadlines.go | 5 +- actors/builtin/miner/deadlines_helper_test.go | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 actors/builtin/miner/deadlines_helper_test.go diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index b7077230d..bfe5dbbf5 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -70,10 +70,7 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE func deadlineAvailableForChallenge(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() - // We can challenge a proof in a deadline: - // 1. After it closes. - // 2. - return currentEpoch >= dlInfo.Close && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStProofChallengePeriod + return !dlInfo.IsOpen() && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStProofChallengePeriod } func deadlineAvailableForCompaction(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { diff --git a/actors/builtin/miner/deadlines_helper_test.go b/actors/builtin/miner/deadlines_helper_test.go new file mode 100644 index 000000000..4aa04d910 --- /dev/null +++ b/actors/builtin/miner/deadlines_helper_test.go @@ -0,0 +1,47 @@ +package miner + +import ( + "testing" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/stretchr/testify/assert" +) + +func TestCompactionWindow(t *testing.T) { + periodStart := abi.ChainEpoch(1024) + dlInfo := NewDeadlineInfo(periodStart, 0, 0) + assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open-WPoStChallengeWindow-1), + "compaction is possible up till the blackout period") + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open-WPoStChallengeWindow), + "compaction is not possible during the prior window") + + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open+10), + "compaction is not possible during the window") + + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Close), + "compaction is not possible immediately after the window") + + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Last()+WPoStProofChallengePeriod), + "compaction is not possible before the proof challenge period has passed") + + assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod), + "compaction is possible after the proof challenge period has passed") + + assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open+WPoStProvingPeriod-WPoStChallengeWindow-1), + "compaction remains possible until the next blackout") + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open+WPoStProvingPeriod-WPoStChallengeWindow), + "compaction is not possible during the next blackout") +} + +func TestChallengeWindow(t *testing.T) { + periodStart := abi.ChainEpoch(1024) + dlInfo := NewDeadlineInfo(periodStart, 0, 0) + assert.False(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Open), + "proof challenge is not possible while the window is open") + assert.True(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close), + "proof challenge is possible after the window is closes") + assert.True(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod-1), + "proof challenge is possible until the proof challenge period has passed") + assert.False(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod), + "proof challenge is not possible after the proof challenge period has passed") +} From 85ec7c29039cc37bbec9d7457c2da4fbcb5d23f0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 17 Dec 2020 15:21:56 -0800 Subject: [PATCH 27/61] fix miner test for new compaction blackout window --- actors/builtin/miner/miner_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 4b5be5ee0..0018633e6 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -3397,8 +3397,8 @@ func TestCompactPartitions(t *testing.T) { sectors := bitfield.NewFromSet([]uint64{uint64(sector1)}) actor.terminateSectors(rt, sectors, expectedFee) - // Wait Finality epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.ChainFinality) + // Wait WPoStProofChallengePeriod epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) // compacting partition will remove sector1 but retain sector 2, 3 and 4. partId := uint64(0) @@ -3427,8 +3427,8 @@ func TestCompactPartitions(t *testing.T) { // fault sector1 actor.declareFaults(rt, info[0]) - // Wait Finality epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.ChainFinality) + // Wait WPoStProofChallengePeriod epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) partId := uint64(0) deadlineId := uint64(0) @@ -3453,8 +3453,8 @@ func TestCompactPartitions(t *testing.T) { // create 2 sectors in partition 0 actor.commitAndProveSectors(rt, 2, defaultSectorExpiration, [][]abi.DealID{{10}, {20}}) - // Wait Finality epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, deadlineEpoch+miner.ChainFinality) + // Wait WPoStProofChallengePeriod epochs so we can compact the sector. + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) partId := uint64(0) deadlineId := uint64(0) From dd51a6232ebd06dfbc264b7f5fa04264dd42c981 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Dec 2020 11:36:50 -0800 Subject: [PATCH 28/61] verify proof recorded in window post test --- actors/builtin/miner/miner_test.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 0018633e6..4577d538d 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1900,11 +1900,25 @@ func TestWindowPost(t *testing.T) { deadline := actor.getDeadline(rt, dlIdx) assertBitfieldEquals(t, deadline.PartitionsPoSted, pIdx) + postsCid := deadline.PoStSubmissions + + posts, err := adt.AsArray(store, postsCid, miner.DeadlinePoStSubmissionsAmtBitwidth) + require.NoError(t, err) + require.EqualValues(t, posts.Length(), 1) + var post miner.WindowedPoSt + found, err := posts.Get(0, &post) + require.NoError(t, err) + require.True(t, found) + assertBitfieldEquals(t, post.Partitions, pIdx) + // Advance to end-of-deadline cron to verify no penalties. advanceDeadline(rt, actor, &cronConfig{}) actor.checkState(rt) - // Proof should exist in state. + deadline = actor.getDeadline(rt, dlIdx) + + // Proofs should exist in snapshot. + require.Equal(t, deadline.PoStSubmissionsSnapshot, postsCid) }) t.Run("test duplicate proof rejected", func(t *testing.T) { From 7109f6f3dff2ea4be99cb782b251f01e15b87160 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Fri, 18 Dec 2020 12:23:26 -0800 Subject: [PATCH 29/61] test bad proofs accepted --- actors/builtin/miner/miner_test.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 4577d538d..74572d888 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1871,7 +1871,7 @@ func TestWindowPost(t *testing.T) { WithEpoch(precommitEpoch). WithBalance(bigBalance, big.Zero()) - t.Run("test proof", func(t *testing.T) { + testBasicPoSt := func(proofs []proof.PoStProof) { rt := builder.Build(t) actor.constructAndVerify(rt) store := rt.AdtStore() @@ -1892,7 +1892,7 @@ func TestWindowPost(t *testing.T) { partitions := []miner.PoStPartition{ {Index: pIdx, Skipped: bitfield.New()}, } - actor.submitWindowPoSt(rt, dlinfo, partitions, []*miner.SectorOnChainInfo{sector}, &poStConfig{ + actor.submitWindowPoStRaw(rt, dlinfo, partitions, []*miner.SectorOnChainInfo{sector}, proofs, &poStConfig{ expectedPowerDelta: pwr, }) @@ -1919,6 +1919,18 @@ func TestWindowPost(t *testing.T) { // Proofs should exist in snapshot. require.Equal(t, deadline.PoStSubmissionsSnapshot, postsCid) + } + + t.Run("test proof", func(t *testing.T) { + testBasicPoSt(makePoStProofs(actor.postProofType)) + }) + + t.Run("test bad proof accepted", func(t *testing.T) { + proofs := makePoStProofs(actor.postProofType) + for i := range proofs { + proofs[i].ProofBytes[0] = 'X' + } + testBasicPoSt(proofs) }) t.Run("test duplicate proof rejected", func(t *testing.T) { @@ -5208,13 +5220,16 @@ func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Inf } func (h *actorHarness) submitWindowPoSt(rt *mock.Runtime, deadline *dline.Info, partitions []miner.PoStPartition, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { + h.submitWindowPoStRaw(rt, deadline, partitions, infos, makePoStProofs(h.postProofType), poStCfg) +} + +func (h *actorHarness) submitWindowPoStRaw(rt *mock.Runtime, deadline *dline.Info, partitions []miner.PoStPartition, infos []*miner.SectorOnChainInfo, proofs []proof.PoStProof, poStCfg *poStConfig) { rt.SetCaller(h.worker, builtin.AccountActorCodeID) commitRand := abi.Randomness("chaincommitment") rt.ExpectGetRandomnessTickets(crypto.DomainSeparationTag_PoStChainCommit, deadline.Challenge, nil, commitRand) rt.ExpectValidateCallerAddr(append(h.controlAddrs, h.owner, h.worker)...) - proofs := makePoStProofs(h.postProofType) if poStCfg != nil { // expect power update if !poStCfg.expectedPowerDelta.IsZero() { From f5c1527ef0357dd45c03528ae6c2f0a15e29cc79 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 15:44:54 -0800 Subject: [PATCH 30/61] verify post proofs eagerly when recovering sectors --- actors/builtin/miner/deadline_state.go | 43 +++++++----- actors/builtin/miner/deadline_state_test.go | 14 ++-- actors/builtin/miner/miner_actor.go | 31 +++++++-- actors/builtin/miner/miner_state_test.go | 2 +- actors/builtin/miner/miner_test.go | 75 ++++++++++++++++++++- actors/builtin/miner/testing.go | 28 -------- 6 files changed, 131 insertions(+), 62 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 1bb5927b6..53b9556f2 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -928,11 +928,8 @@ type PoStResult struct { Sectors bitfield.BitField // IgnoredSectors is a subset of Sectors that should be ignored. IgnoredSectors bitfield.BitField -} - -// PenaltyPower is the power from this PoSt that should be penalized. -func (p *PoStResult) PenaltyPower() PowerPair { - return p.NewFaultyPower.Add(p.RetractedRecoveryPower) + // Bitfield of partitions that were proven. + Partitions bitfield.BitField } // RecordProvenSectors processes a series of posts, recording proven partitions @@ -947,7 +944,6 @@ func (dl *Deadline) RecordProvenSectors( store adt.Store, sectors Sectors, ssize abi.SectorSize, quant QuantSpec, faultExpiration abi.ChainEpoch, postPartitions []PoStPartition, - proofs []proof.PoStProof, ) (*PoStResult, error) { partitionIndexes := bitfield.New() @@ -1045,18 +1041,6 @@ func (dl *Deadline) RecordProvenSectors( return nil, xc.ErrIllegalState.Wrapf("failed to persist partitions: %w", err) } - // Save proof. - if proofArr, err := adt.AsArray(store, dl.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth); err != nil { - return nil, xerrors.Errorf("failed to load proofs: %w", err) - } else if err := proofArr.AppendContinuous(&WindowedPoSt{ - Partitions: partitionIndexes, - Proofs: proofs, - }); err != nil { - return nil, xerrors.Errorf("failed to store proof: %w", err) - } else if dl.PoStSubmissions, err = proofArr.Root(); err != nil { - return nil, xerrors.Errorf("failed to save proofs: %w", err) - } - // Collect all sectors, faults, and recoveries for proof verification. allSectorNos, err := bitfield.MultiMerge(allSectors...) if err != nil { @@ -1074,9 +1058,32 @@ func (dl *Deadline) RecordProvenSectors( NewFaultyPower: newFaultyPowerTotal, RecoveredPower: recoveredPowerTotal, RetractedRecoveryPower: retractedRecoveryPowerTotal, + Partitions: partitionIndexes, }, nil } +func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitField, proofs []proof.PoStProof) error { + // Save proof. + proofArr, err := adt.AsArray(store, dl.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth) + if err != nil { + return xerrors.Errorf("failed to load proofs: %w", err) + } + err = proofArr.AppendContinuous(&WindowedPoSt{ + Partitions: partitions, + Proofs: proofs, + }) + if err != nil { + return xerrors.Errorf("failed to store proof: %w", err) + } + + root, err := proofArr.Root() + if err != nil { + return xerrors.Errorf("failed to save proofs: %w", err) + } + dl.PoStSubmissions = root + return nil +} + // RescheduleSectorExpirations reschedules the expirations of the given sectors // to the target epoch, skipping any sectors it can't find. // diff --git a/actors/builtin/miner/deadline_state_test.go b/actors/builtin/miner/deadline_state_test.go index 5912aab03..8e3121890 100644 --- a/actors/builtin/miner/deadline_state_test.go +++ b/actors/builtin/miner/deadline_state_test.go @@ -79,7 +79,7 @@ func TestDeadlines(t *testing.T) { sectorArr := sectorsArr(t, store, sectors) // Prove everything - result, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 0, []miner.PoStPartition{{Index: 0}, {Index: 1}, {Index: 2}}, nil) + result, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 0, []miner.PoStPartition{{Index: 0}, {Index: 1}, {Index: 2}}) require.NoError(t, err) require.True(t, result.PowerDelta.Equals(power)) @@ -513,7 +513,7 @@ func TestDeadlines(t *testing.T) { postResult1, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, - }, nil) + }) require.NoError(t, err) assertBitfieldEquals(t, postResult1.Sectors, 1, 2, 3, 4, 5, 6, 7, 8) assertEmptyBitfield(t, postResult1.IgnoredSectors) @@ -532,7 +532,7 @@ func TestDeadlines(t *testing.T) { postResult2, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 2, Skipped: bf()}, - }, nil) + }) require.NoError(t, err) assertBitfieldEquals(t, postResult2.Sectors, 9, 10) assertEmptyBitfield(t, postResult2.IgnoredSectors) @@ -600,7 +600,7 @@ func TestDeadlines(t *testing.T) { postResult, err := dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf(1)}, {Index: 1, Skipped: bf(7)}, - }, nil) + }) require.NoError(t, err) // 1, 5, and 7 are expected to be faulty. @@ -668,7 +668,7 @@ func TestDeadlines(t *testing.T) { {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, {Index: 2, Skipped: bf(10)}, - }, nil) + }) require.NoError(t, err) assertBitfieldEquals(t, postResult1.Sectors, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) @@ -721,7 +721,7 @@ func TestDeadlines(t *testing.T) { _, err = dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ {Index: 0, Skipped: bf()}, {Index: 3, Skipped: bf()}, - }, nil) + }) require.Error(t, err) require.Contains(t, err.Error(), "no such partition") }) @@ -764,7 +764,7 @@ func TestDeadlines(t *testing.T) { {Index: 0, Skipped: bf()}, {Index: 1, Skipped: bf()}, {Index: 2, Skipped: bf()}, - }, nil) + }) require.NoError(t, err) // 1 & 5 are still faulty diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index b85d9fad9..e85b65527 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -422,12 +422,11 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) // While we could perform _all_ operations at the end of challenge window, we do as we can here to avoid // overloading cron. faultExpiration := currDeadline.Last() + FaultMaxAge - postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions, params.Proofs) + postResult, err = deadline.RecordProvenSectors(store, sectors, info.SectorSize, QuantSpecForDeadline(currDeadline), faultExpiration, params.Partitions) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to process post submission for deadline %d", params.Deadline) // Make sure we actually proved something. - // TODO: refactor RecordProvenSectors to return what we actually need. provenSectors, err := bitfield.SubtractBitField(postResult.Sectors, postResult.IgnoredSectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to determine proven sectors for deadline %d", params.Deadline) @@ -441,6 +440,18 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) rt.Abortf(exitcode.ErrIllegalArgument, "cannot prove partitions with no active sectors") } + // If we're not recovering power, record the proof for optimistic verification. + if postResult.RecoveredPower.IsZero() { + err = deadline.RecordPoStProofs(store, postResult.Partitions, params.Proofs) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to record proof for optimistic verification", params.Deadline) + } else { + // otherwise, check the proof + sectorInfos, err := sectors.LoadForProof(postResult.Sectors, postResult.IgnoredSectors) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post verification") + + verifyWindowedPost(rt, currDeadline.Challenge, sectorInfos, params.Proofs, false) + } + err = deadlines.UpdateDeadline(store, params.Deadline, deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) @@ -574,7 +585,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa targetDeadline := NewDeadlineInfo(ppStart, params.Deadline, currEpoch) // Fails if validation succeeds. - challengeWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs) + verifyWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs, true) // Ok, now we record faults. This always works because // we don't allow compaction/moving sectors during the @@ -2236,7 +2247,7 @@ func havePendingEarlyTerminations(rt Runtime, st *State) bool { return !noEarlyTerminations } -func challengeWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof) { +func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof, expectFail bool) { minerActorID, err := addr.IDFromAddress(rt.Receiver()) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "runtime provided bad receiver address %v", rt.Receiver()) @@ -2265,10 +2276,16 @@ func challengeWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors [] } // Verify the PoSt Proof - if err = rt.VerifyPoSt(pvInfo); err == nil { - rt.Abortf(exitcode.ErrIllegalArgument, "valid PoSt %+v", pvInfo) + err = rt.VerifyPoSt(pvInfo) + + if expectFail { + if err == nil { + rt.Abortf(exitcode.ErrIllegalArgument, "failed to challenge valid post %+v", pvInfo) + return + } + rt.Log(rtt.INFO, "post successfully challenged %+v: %s", pvInfo, err) } else { - rt.Log(rtt.INFO, "PoSt successfully challenged: %s", err) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "invalid PoSt %+v", pvInfo) } } diff --git a/actors/builtin/miner/miner_state_test.go b/actors/builtin/miner/miner_state_test.go index 87dcb165e..0a5076ee5 100644 --- a/actors/builtin/miner/miner_state_test.go +++ b/actors/builtin/miner/miner_state_test.go @@ -687,7 +687,7 @@ func TestSectorAssignment(t *testing.T) { // Now make sure proving activates power. - result, err := dl.RecordProvenSectors(harness.store, sectorArr, sectorSize, quantSpec, 0, postPartitions, nil) + result, err := dl.RecordProvenSectors(harness.store, sectorArr, sectorSize, quantSpec, 0, postPartitions) require.NoError(t, err) expectedPowerDelta := miner.PowerForSectors(sectorSize, selectSectors(t, sectorInfos, allSectorBf)) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 74572d888..87ea840ce 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -5119,7 +5119,7 @@ func (h *actorHarness) advancePastDeadlineEndWithCron(rt *mock.Runtime) { type poStConfig struct { expectedPowerDelta miner.PowerPair - //verificationError error + verificationError error } func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { @@ -5230,6 +5230,79 @@ func (h *actorHarness) submitWindowPoStRaw(rt *mock.Runtime, deadline *dline.Inf rt.ExpectValidateCallerAddr(append(h.controlAddrs, h.owner, h.worker)...) + challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) + + // only sectors that are not skipped and not existing non-recovered faults will be verified + allIgnored := bf() + allRecovered := bf() + dln := h.getDeadline(rt, deadline.Index) + + for _, p := range partitions { + partition := h.getPartition(rt, dln, p.Index) + expectedFaults, err := bitfield.SubtractBitField(partition.Faults, partition.Recoveries) + require.NoError(h.t, err) + allIgnored, err = bitfield.MultiMerge(allIgnored, expectedFaults, p.Skipped) + require.NoError(h.t, err) + recovered, err := bitfield.SubtractBitField(partition.Recoveries, p.Skipped) + require.NoError(h.t, err) + allRecovered, err = bitfield.MergeBitFields(allRecovered, recovered) + require.NoError(h.t, err) + } + optimistic, err := allRecovered.IsEmpty() + require.NoError(h.t, err) + + // find the first non-faulty, non-skipped sector in poSt to replace all faulty sectors. + var goodInfo *miner.SectorOnChainInfo + for _, ci := range infos { + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if !contains { + goodInfo = ci + break + } + } + + // goodInfo == nil indicates all the sectors have been skipped and should PoSt verification should not occur + if !optimistic && goodInfo != nil { + var buf bytes.Buffer + receiver := rt.Receiver() + err := receiver.MarshalCBOR(&buf) + require.NoError(h.t, err) + + rt.ExpectGetRandomnessBeacon(crypto.DomainSeparationTag_WindowedPoStChallengeSeed, deadline.Challenge, buf.Bytes(), abi.Randomness(challengeRand)) + + actorId, err := addr.IDFromAddress(h.receiver) + require.NoError(h.t, err) + + // if not all sectors are skipped + proofInfos := make([]proof.SectorInfo, len(infos)) + for i, ci := range infos { + si := ci + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if contains { + si = goodInfo + } + proofInfos[i] = proof.SectorInfo{ + SealProof: si.SealProof, + SectorNumber: si.SectorNumber, + SealedCID: si.SealedCID, + } + } + + vi := proof.WindowPoStVerifyInfo{ + Randomness: abi.PoStRandomness(challengeRand), + Proofs: proofs, + ChallengedSectors: proofInfos, + Prover: abi.ActorID(actorId), + } + var verifResult error + if poStCfg != nil { + verifResult = poStCfg.verificationError + } + rt.ExpectVerifyPoSt(vi, verifResult) + } + if poStCfg != nil { // expect power update if !poStCfg.expectedPowerDelta.IsZero() { diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index b49e97ee8..312b8b037 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -201,34 +201,6 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua }) acc.RequireNoError(err, "error iterating partitions") - // Check PoSt submissions - if postSubmissions, err := deadline.PartitionsPoSted.All(1 << 20); err != nil { - acc.Addf("error expanding post submissions: %v", err) - } else { - for _, p := range postSubmissions { - acc.Require(p <= partitionCount, "invalid PoSt submission for partition %d of %d", p, partitionCount) - } - - expectedPoSts := deadline.PartitionsPoSted - proofs, err := adt.AsArray(store, deadline.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth) - acc.RequireNoError(err, "failed to load proofs") - var post WindowedPoSt - err = proofs.ForEach(&post, func(idx int64) error { - // This is allowed to be 0 for testing. - //acc.Require(len(post.Proofs) == 0, "invalid number of proofs") - contains, err := util.BitFieldContainsAll(expectedPoSts, post.Partitions) - acc.RequireNoError(err, "failed to check if posts were expected") - acc.Require(contains, "unexpected post") - expectedPoSts, err = bitfield.SubtractBitField(expectedPoSts, post.Partitions) - acc.RequireNoError(err, "failed to subtract found posts from expected posts") - return nil - }) - acc.RequireNoError(err, "failed to iterate over proofs") - missingPoSts, err := expectedPoSts.All(1 << 20) - acc.RequireNoError(err, "failed to expand missing posts") - acc.Require(len(missingPoSts) == 0, "missing posts for partitions: %v", missingPoSts) - } - // Check memoized sector and power values. live, err := bitfield.MultiMerge(allLiveSectors...) if err != nil { From 5ab98b8ccb1f63711368e8a073e1c9e4879a8c06 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 18:18:46 -0800 Subject: [PATCH 31/61] load partitions snapshot with the correct bitwidth --- actors/builtin/miner/deadline_state.go | 24 ++++++++++++++++++++++++ actors/builtin/miner/miner_actor.go | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 53b9556f2..4a4b60812 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -190,6 +190,14 @@ func (d *Deadline) PartitionsArray(store adt.Store) (*adt.Array, error) { return arr, nil } +func (d *Deadline) PartitionsSnapshotArray(store adt.Store) (*adt.Array, error) { + arr, err := adt.AsArray(store, d.PartitionsSnapshot, DeadlinePartitionsAmtBitwidth) + if err != nil { + return nil, xc.ErrIllegalState.Wrapf("failed to load partitions: %w", err) + } + return arr, nil +} + func (d *Deadline) LoadPartition(store adt.Store, partIdx uint64) (*Partition, error) { partitions, err := d.PartitionsArray(store) if err != nil { @@ -206,6 +214,22 @@ func (d *Deadline) LoadPartition(store adt.Store, partIdx uint64) (*Partition, e return &partition, nil } +func (d *Deadline) LoadPartitionSnapshot(store adt.Store, partIdx uint64) (*Partition, error) { + partitions, err := d.PartitionsSnapshotArray(store) + if err != nil { + return nil, err + } + var partition Partition + found, err := partitions.Get(partIdx, &partition) + if err != nil { + return nil, xc.ErrIllegalState.Wrapf("failed to lookup partition %d: %w", partIdx, err) + } + if !found { + return nil, xc.ErrNotFound.Wrapf("no partition %d", partIdx) + } + return &partition, nil +} + // Adds some partition numbers to the set expiring at an epoch. func (d *Deadline) AddExpirationPartitions(store adt.Store, expirationEpoch abi.ChainEpoch, partitions []uint64, quant QuantSpec) error { // Avoid doing any work if there's nothing to reschedule. diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index e85b65527..94feb20b1 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -516,7 +516,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa proofsSnapshot, err := adt.AsArray(store, dlCurrent.PoStSubmissionsSnapshot, DeadlinePoStSubmissionsAmtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - partitionsSnapshot, err := adt.AsArray(store, dlCurrent.PartitionsSnapshot, DeadlinePoStSubmissionsAmtBitwidth) + partitionsSnapshot, err := dlCurrent.PartitionsSnapshotArray(store) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") // Load the target proof. From 8fc47a680b313e7c18ed8c57e1f859ea6f82eea1 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 18:19:18 -0800 Subject: [PATCH 32/61] use a dedicated function for bad proof penalties --- actors/builtin/miner/miner_actor.go | 3 +-- actors/builtin/miner/monies.go | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 94feb20b1..84cf6963b 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -611,8 +611,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // Penalties. { - // TODO(PARAM): consider applying a multiplier on the penalty - penaltyTarget := PledgePenaltyForContinuedFault( + penaltyTarget := PledgePenaltyForInvalidWindowPoSt( epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, faultyPower.QA, diff --git a/actors/builtin/miner/monies.go b/actors/builtin/miner/monies.go index dde026dca..031679dd3 100644 --- a/actors/builtin/miner/monies.go +++ b/actors/builtin/miner/monies.go @@ -118,6 +118,13 @@ func PledgePenaltyForTermination(dayReward abi.TokenAmount, sectorAge abi.ChainE big.Mul(big.NewInt(builtin.EpochsInDay), TerminationRewardFactor.Denominator)))) // (epochs*AttoFIL/day -> AttoFIL) } +// The penalty for a sector continuing faulty for another proving period. +// It is a projection of the expected reward earned by the sector. +// TODO(PARAM): consider applying a multiplier on the penalty +func PledgePenaltyForInvalidWindowPoSt(rewardEstimate, networkQAPowerEstimate smoothing.FilterEstimate, qaSectorPower abi.StoragePower) abi.TokenAmount { + return PledgePenaltyForContinuedFault(rewardEstimate, networkQAPowerEstimate, qaSectorPower) +} + // Computes the PreCommit deposit given sector qa weight and current network conditions. // PreCommit Deposit = BR(PreCommitDepositProjectionPeriod) func PreCommitDepositForPower(rewardEstimate, networkQAPowerEstimate smoothing.FilterEstimate, qaSectorPower abi.StoragePower) abi.TokenAmount { From 8e906efafde8e06857e72c0f59158b2e28c435e5 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 18:20:15 -0800 Subject: [PATCH 33/61] check invariants for proof snapshots --- actors/builtin/miner/testing.go | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index 312b8b037..d68e552a2 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -201,6 +201,52 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua }) acc.RequireNoError(err, "error iterating partitions") + // Check partitions snapshot + snapshotSectors := bitfield.New() + partitionSnapshotCount := uint64(0) + partitionsSnapshot, err := deadline.PartitionsSnapshotArray(store) + acc.RequireNoError(err, "error loading partitions snapshot") + err = partitionsSnapshot.ForEach(&partition, func(i int64) error { + pIdx := uint64(i) + // Common partition checks. + acc.Require(pIdx == partitionSnapshotCount, "Non-sequential partitions, expected index %d, found %d", partitionSnapshotCount, pIdx) + partitionSnapshotCount++ + + acc := acc.WithPrefix("partition snapshot %d: ", pIdx) // Shadow + summary := CheckPartitionStateInvariants(&partition, store, quant, ssize, sectors, acc) + + if contains, err := util.BitFieldContainsAny(snapshotSectors, summary.AllSectors); err != nil { + acc.Addf("error checking bitfield contains: %v", err) + } else { + acc.Require(!contains, "duplicate sector in partition %d", pIdx) + } + + snapshotSectors, err = bitfield.MergeBitFields(snapshotSectors, summary.AllSectors) + if err != nil { + acc.Addf("error merging partition sector numbers with all: %v", err) + snapshotSectors = bitfield.New() + } + + // We can't really check the power here. But we _can_ make sure there are no declared recoveries, etc in the snapshots. + + acc.Require(partition.RecoveringPower.IsZero(), "snapshot partition has recovering power") + if noRecoveries, err := partition.Recoveries.IsEmpty(); err != nil { + acc.Addf("error counting recoveries: %v", err) + } else { + acc.Require(noRecoveries, "snapshot partition has pending recoveries") + } + + acc.Require(partition.UnprovenPower.IsZero(), "snapshot partition has unproven power") + if noUnproven, err := partition.Unproven.IsEmpty(); err != nil { + acc.Addf("error counting unproven: %v", err) + } else { + acc.Require(noUnproven, "snapshot partition has unproven sectors") + } + + return nil + }) + acc.RequireNoError(err, "error iterating partitions snapshot") + // Check memoized sector and power values. live, err := bitfield.MultiMerge(allLiveSectors...) if err != nil { From 90da4114992af06bfededcdb1a0e5d542be623d0 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 18:21:20 -0800 Subject: [PATCH 34/61] test basic post challenging --- actors/builtin/miner/miner_test.go | 237 ++++++++++++++++++----------- 1 file changed, 149 insertions(+), 88 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 87ea840ce..1301f4c76 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1871,7 +1871,9 @@ func TestWindowPost(t *testing.T) { WithEpoch(precommitEpoch). WithBalance(bigBalance, big.Zero()) - testBasicPoSt := func(proofs []proof.PoStProof) { + testBasicPoSt := func(challengeSucceed bool) { + proofs := makePoStProofs(actor.postProofType) + rt := builder.Build(t) actor.constructAndVerify(rt) store := rt.AdtStore() @@ -1919,18 +1921,25 @@ func TestWindowPost(t *testing.T) { // Proofs should exist in snapshot. require.Equal(t, deadline.PoStSubmissionsSnapshot, postsCid) + + var result *poStChallengeResult + if challengeSucceed { + expectedFee := miner.PledgePenaltyForInvalidWindowPoSt(actor.epochRewardSmooth, actor.epochQAPowerSmooth, pwr.QA) + result = &poStChallengeResult{ + expectedPowerDelta: pwr.Neg(), + expectedPenalty: expectedFee, + expectedPledgeDelta: big.Zero(), + } + } + actor.challengeWindowPoSt(rt, dlinfo, 0, []*miner.SectorOnChainInfo{sector}, result) } t.Run("test proof", func(t *testing.T) { - testBasicPoSt(makePoStProofs(actor.postProofType)) + testBasicPoSt(true) }) - t.Run("test bad proof accepted", func(t *testing.T) { - proofs := makePoStProofs(actor.postProofType) - for i := range proofs { - proofs[i].ProofBytes[0] = 'X' - } - testBasicPoSt(proofs) + t.Run("test bad proof accepted and challenged", func(t *testing.T) { + testBasicPoSt(false) }) t.Run("test duplicate proof rejected", func(t *testing.T) { @@ -4638,6 +4647,22 @@ func (h *actorHarness) getPartition(rt *mock.Runtime, deadline *miner.Deadline, return partition } +func (h *actorHarness) getPartitionSnapshot(rt *mock.Runtime, deadline *miner.Deadline, idx uint64) *miner.Partition { + partition, err := deadline.LoadPartitionSnapshot(rt.AdtStore(), idx) + require.NoError(h.t, err) + return partition +} + +func (h *actorHarness) getSubmittedProof(rt *mock.Runtime, deadline *miner.Deadline, idx uint64) *miner.WindowedPoSt { + proofs, err := adt.AsArray(rt.AdtStore(), deadline.PoStSubmissionsSnapshot, miner.DeadlinePoStSubmissionsAmtBitwidth) + require.NoError(h.t, err) + var post miner.WindowedPoSt + found, err := proofs.Get(idx, &post) + require.NoError(h.t, err) + require.True(h.t, found) + return &post +} + func (h *actorHarness) getDeadlineAndPartition(rt *mock.Runtime, dlIdx, pIdx uint64) (*miner.Deadline, *miner.Partition) { deadline := h.getDeadline(rt, dlIdx) partition := h.getPartition(rt, deadline, pIdx) @@ -5117,106 +5142,142 @@ func (h *actorHarness) advancePastDeadlineEndWithCron(rt *mock.Runtime) { rt.SetEpoch(deadline.NextPeriodStart()) } -type poStConfig struct { - expectedPowerDelta miner.PowerPair - verificationError error +type poStChallengeResult struct { + expectedPowerDelta miner.PowerPair + expectedPledgeDelta abi.TokenAmount + expectedPenalty abi.TokenAmount } -func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { +func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, proofIndex uint64, infos []*miner.SectorOnChainInfo, expectSuccess *poStChallengeResult) { rt.SetCaller(h.worker, builtin.AccountActorCodeID) rt.ExpectValidateCallerAny() - // TODO: adapt this to verify post on challenge, instead of on submit. - /* - proofs := makePoStProofs(h.postProofType) - challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) + currentReward := reward.ThisEpochRewardReturn{ + ThisEpochBaselinePower: h.baselinePower, + ThisEpochRewardSmoothed: h.epochRewardSmooth, + } + rt.ExpectSend(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), ¤tReward, exitcode.Ok) + + networkPower := big.NewIntUnsigned(1 << 50) + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), + &power.CurrentTotalPowerReturn{ + RawBytePower: networkPower, + QualityAdjPower: networkPower, + PledgeCollateral: h.networkPledge, + QualityAdjPowerSmoothed: h.epochQAPowerSmooth, + }, + exitcode.Ok) - // only sectors that are not skipped and not existing non-recovered faults will be verified - allIgnored := bf() - dln := h.getDeadline(rt, deadline.Index) + challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) - for _, p := range partitions { - partition := h.getPartition(rt, dln, p.Index) - expectedFaults, err := bitfield.SubtractBitField(partition.Faults, partition.Recoveries) - require.NoError(h.t, err) - allIgnored, err = bitfield.MultiMerge(allIgnored, expectedFaults, p.Skipped) - require.NoError(h.t, err) - } + // only sectors that are not skipped and not existing non-recovered faults will be verified + allIgnored := bf() + dln := h.getDeadline(rt, deadline.Index) - // find the first non-faulty, non-skipped sector in poSt to replace all faulty sectors. - var goodInfo *miner.SectorOnChainInfo - for _, ci := range infos { - contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) - require.NoError(h.t, err) - if !contains { - goodInfo = ci - break - } + post := h.getSubmittedProof(rt, dln, proofIndex) + + var err error + err = post.Partitions.ForEach(func(idx uint64) error { + partition := h.getPartitionSnapshot(rt, dln, idx) + allIgnored, err = bitfield.MergeBitFields(allIgnored, partition.Faults) + require.NoError(h.t, err) + noRecoveries, err := partition.Recoveries.IsEmpty() + require.NoError(h.t, err) + require.True(h.t, noRecoveries) + return nil + }) + require.NoError(h.t, err) + + // find the first non-faulty, non-skipped sector in poSt to replace all faulty sectors. + var goodInfo *miner.SectorOnChainInfo + for _, ci := range infos { + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if !contains { + goodInfo = ci + break } + } + require.NotNil(h.t, goodInfo, "stored proof should prove at least one sector") - // goodInfo == nil indicates all the sectors have been skipped and should PoSt verification should not occur - if goodInfo != nil { - var buf bytes.Buffer - receiver := rt.Receiver() - err := receiver.MarshalCBOR(&buf) - require.NoError(h.t, err) + var buf bytes.Buffer + receiver := rt.Receiver() + err = receiver.MarshalCBOR(&buf) + require.NoError(h.t, err) - rt.ExpectGetRandomnessBeacon(crypto.DomainSeparationTag_WindowedPoStChallengeSeed, deadline.Challenge, buf.Bytes(), abi.Randomness(challengeRand)) + rt.ExpectGetRandomnessBeacon(crypto.DomainSeparationTag_WindowedPoStChallengeSeed, deadline.Challenge, buf.Bytes(), abi.Randomness(challengeRand)) - actorId, err := addr.IDFromAddress(h.receiver) - require.NoError(h.t, err) + actorId, err := addr.IDFromAddress(h.receiver) + require.NoError(h.t, err) - // if not all sectors are skipped - proofInfos := make([]proof.SectorInfo, len(infos)) - for i, ci := range infos { - si := ci - contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) - require.NoError(h.t, err) - if contains { - si = goodInfo - } - proofInfos[i] = proof.SectorInfo{ - SealProof: si.SealProof, - SectorNumber: si.SectorNumber, - SealedCID: si.SealedCID, - } - } + // if not all sectors are skipped + proofInfos := make([]proof.SectorInfo, len(infos)) + for i, ci := range infos { + si := ci + contains, err := allIgnored.IsSet(uint64(ci.SectorNumber)) + require.NoError(h.t, err) + if contains { + si = goodInfo + } + proofInfos[i] = proof.SectorInfo{ + SealProof: si.SealProof, + SectorNumber: si.SectorNumber, + SealedCID: si.SealedCID, + } + } - vi := proof.WindowPoStVerifyInfo{ - Randomness: abi.PoStRandomness(challengeRand), - Proofs: proofs, - ChallengedSectors: proofInfos, - Prover: abi.ActorID(actorId), - } - var verifResult error - if poStCfg != nil { - verifResult = poStCfg.verificationError + vi := proof.WindowPoStVerifyInfo{ + Randomness: abi.PoStRandomness(challengeRand), + Proofs: post.Proofs, + ChallengedSectors: proofInfos, + Prover: abi.ActorID(actorId), + } + var verifResult error + if expectSuccess != nil { + // if we succeed at challenging, proof verification needs to fail. + verifResult = fmt.Errorf("invalid post") + } + rt.ExpectVerifyPoSt(vi, verifResult) + + if expectSuccess != nil { + // expect power update + if !expectSuccess.expectedPowerDelta.IsZero() { + claim := &power.UpdateClaimedPowerParams{ + RawByteDelta: expectSuccess.expectedPowerDelta.Raw, + QualityAdjustedDelta: expectSuccess.expectedPowerDelta.QA, } - rt.ExpectVerifyPoSt(vi, verifResult) + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdateClaimedPower, claim, abi.NewTokenAmount(0), + nil, exitcode.Ok) } - if poStCfg != nil { - // expect power update - if !poStCfg.expectedPowerDelta.IsZero() { - claim := &power.UpdateClaimedPowerParams{ - RawByteDelta: poStCfg.expectedPowerDelta.Raw, - QualityAdjustedDelta: poStCfg.expectedPowerDelta.QA, - } - rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdateClaimedPower, claim, abi.NewTokenAmount(0), - nil, exitcode.Ok) - } + // expect pledge update + if !expectSuccess.expectedPledgeDelta.IsZero() { + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdatePledgeTotal, + &expectSuccess.expectedPledgeDelta, abi.NewTokenAmount(0), nil, exitcode.Ok) } - params := miner.SubmitWindowedPoStParams{ - Deadline: deadline.Index, - Partitions: partitions, - Proofs: proofs, - ChainCommitEpoch: deadline.Challenge, - ChainCommitRand: commitRand, + // expect penalty + if !expectSuccess.expectedPenalty.IsZero() { + rt.ExpectSend(builtin.BurntFundsActorAddr, builtin.MethodSend, nil, expectSuccess.expectedPenalty, nil, exitcode.Ok) } + } - rt.Call(h.a.SubmitWindowedPoSt, ¶ms) - rt.Verify() - */ + params := miner.ChallengeWindowedPoStParams{ + Deadline: deadline.Index, + ProofIndex: proofIndex, + } + if expectSuccess == nil { + rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "failed to challenge valid post", func() { + rt.Call(h.a.ChallengeWindowedPoSt, ¶ms) + }) + } else { + rt.Call(h.a.ChallengeWindowedPoSt, ¶ms) + } + rt.Verify() +} + +type poStConfig struct { + expectedPowerDelta miner.PowerPair + verificationError error } func (h *actorHarness) submitWindowPoSt(rt *mock.Runtime, deadline *dline.Info, partitions []miner.PoStPartition, infos []*miner.SectorOnChainInfo, poStCfg *poStConfig) { From 956320749d4c662ad795fcd4ae39395d03fd651a Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 18:40:47 -0800 Subject: [PATCH 35/61] fix proving period start in test This needs to retain the same offset. Otherwise, state validation will fail. --- actors/builtin/miner/miner_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 1301f4c76..1fadfd59f 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -2446,8 +2446,9 @@ func TestDeadlineCron(t *testing.T) { // setup state to simulate moving forward all the way to expiry dlIdx, _, err := st.FindSector(rt.AdtStore(), sectors[0].SectorNumber) require.NoError(t, err) - expirationPeriod := (expiration/miner.WPoStProvingPeriod + 1) * miner.WPoStProvingPeriod - st.ProvingPeriodStart = expirationPeriod + remainingEpochs := expiration - st.ProvingPeriodStart + remainingPeriods := remainingEpochs/miner.WPoStProvingPeriod + 1 + st.ProvingPeriodStart += remainingPeriods * miner.WPoStProvingPeriod st.CurrentDeadline = dlIdx rt.ReplaceState(st) @@ -2479,8 +2480,9 @@ func TestDeadlineCron(t *testing.T) { // setup state to simulate moving forward all the way to expiry dlIdx, _, err := st.FindSector(rt.AdtStore(), sectors[0].SectorNumber) require.NoError(t, err) - expirationPeriod := (expiration/miner.WPoStProvingPeriod + 1) * miner.WPoStProvingPeriod - st.ProvingPeriodStart = expirationPeriod + remainingEpochs := expiration - st.ProvingPeriodStart + remainingPeriods := remainingEpochs/miner.WPoStProvingPeriod + 1 + st.ProvingPeriodStart += remainingPeriods * miner.WPoStProvingPeriod st.CurrentDeadline = dlIdx rt.ReplaceState(st) From aae3d8433b603cd03d3418130be305124dc32ee6 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 4 Jan 2021 19:39:44 -0800 Subject: [PATCH 36/61] remove some tricky checks from the deadline state invariants Unfortunately, we may not _have_ all the sectors mentioned in the snapshot by the time we check these invarianats. But really, we have this check to make sure we don't have recovering/unproven power in the snapshot. --- actors/builtin/miner/testing.go | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index d68e552a2..c067f4a99 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -201,33 +201,12 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua }) acc.RequireNoError(err, "error iterating partitions") - // Check partitions snapshot - snapshotSectors := bitfield.New() - partitionSnapshotCount := uint64(0) + // Check partitions snapshot to make sure we take the snapshot after + // dealing with recovering power and unproven power. partitionsSnapshot, err := deadline.PartitionsSnapshotArray(store) acc.RequireNoError(err, "error loading partitions snapshot") err = partitionsSnapshot.ForEach(&partition, func(i int64) error { - pIdx := uint64(i) - // Common partition checks. - acc.Require(pIdx == partitionSnapshotCount, "Non-sequential partitions, expected index %d, found %d", partitionSnapshotCount, pIdx) - partitionSnapshotCount++ - - acc := acc.WithPrefix("partition snapshot %d: ", pIdx) // Shadow - summary := CheckPartitionStateInvariants(&partition, store, quant, ssize, sectors, acc) - - if contains, err := util.BitFieldContainsAny(snapshotSectors, summary.AllSectors); err != nil { - acc.Addf("error checking bitfield contains: %v", err) - } else { - acc.Require(!contains, "duplicate sector in partition %d", pIdx) - } - - snapshotSectors, err = bitfield.MergeBitFields(snapshotSectors, summary.AllSectors) - if err != nil { - acc.Addf("error merging partition sector numbers with all: %v", err) - snapshotSectors = bitfield.New() - } - - // We can't really check the power here. But we _can_ make sure there are no declared recoveries, etc in the snapshots. + acc := acc.WithPrefix("partition snapshot %d: ", i) // Shadow acc.Require(partition.RecoveringPower.IsZero(), "snapshot partition has recovering power") if noRecoveries, err := partition.Recoveries.IsEmpty(); err != nil { From d5288c22c9f53a514e72260040cde81667a4e791 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 5 Jan 2021 23:47:13 -0800 Subject: [PATCH 37/61] partially address code review feedback --- actors/builtin/methods.go | 2 +- actors/builtin/miner/cbor_gen.go | 70 ++++++++--------- actors/builtin/miner/deadline_state.go | 75 +++++++++--------- actors/builtin/miner/deadline_state_test.go | 22 ++++++ actors/builtin/miner/deadlines.go | 21 ++++- actors/builtin/miner/deadlines_helper_test.go | 12 +-- actors/builtin/miner/miner_actor.go | 78 +++++++++---------- actors/builtin/miner/miner_test.go | 44 +++++------ actors/builtin/miner/policy.go | 7 +- actors/builtin/miner/policy_test.go | 8 +- gen/gen.go | 2 +- 11 files changed, 189 insertions(+), 152 deletions(-) diff --git a/actors/builtin/methods.go b/actors/builtin/methods.go index 795f73d80..4de1e2b99 100644 --- a/actors/builtin/methods.go +++ b/actors/builtin/methods.go @@ -99,7 +99,7 @@ var MethodsMiner = struct { ConfirmUpdateWorkerKey abi.MethodNum RepayDebt abi.MethodNum ChangeOwnerAddress abi.MethodNum - ChallengeWindowedPoSt abi.MethodNum + DisputeWindowedPoSt abi.MethodNum }{MethodConstructor, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24} var MethodsVerifiedRegistry = struct { diff --git a/actors/builtin/miner/cbor_gen.go b/actors/builtin/miner/cbor_gen.go index 41d25a49d..bd2eebc39 100644 --- a/actors/builtin/miner/cbor_gen.go +++ b/actors/builtin/miner/cbor_gen.go @@ -779,6 +779,11 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.ExpirationsEpochs: %w", err) } + // t.PartitionsPoSted (bitfield.BitField) (struct) + if err := t.PartitionsPoSted.MarshalCBOR(w); err != nil { + return err + } + // t.EarlyTerminations (bitfield.BitField) (struct) if err := t.EarlyTerminations.MarshalCBOR(w); err != nil { return err @@ -801,15 +806,10 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return err } - // t.PartitionsPoSted (bitfield.BitField) (struct) - if err := t.PartitionsPoSted.MarshalCBOR(w); err != nil { - return err - } - - // t.PoStSubmissions (cid.Cid) (struct) + // t.OptimisticPoStSubmissions (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.PoStSubmissions); err != nil { - return xerrors.Errorf("failed to write cid field t.PoStSubmissions: %w", err) + if err := cbg.WriteCidBuf(scratch, w, t.OptimisticPoStSubmissions); err != nil { + return xerrors.Errorf("failed to write cid field t.OptimisticPoStSubmissions: %w", err) } // t.PartitionsSnapshot (cid.Cid) (struct) @@ -818,10 +818,10 @@ func (t *Deadline) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("failed to write cid field t.PartitionsSnapshot: %w", err) } - // t.PoStSubmissionsSnapshot (cid.Cid) (struct) + // t.OptimisticPoStSubmissionsSnapshot (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.PoStSubmissionsSnapshot); err != nil { - return xerrors.Errorf("failed to write cid field t.PoStSubmissionsSnapshot: %w", err) + if err := cbg.WriteCidBuf(scratch, w, t.OptimisticPoStSubmissionsSnapshot); err != nil { + return xerrors.Errorf("failed to write cid field t.OptimisticPoStSubmissionsSnapshot: %w", err) } return nil @@ -868,6 +868,15 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.ExpirationsEpochs = c + } + // t.PartitionsPoSted (bitfield.BitField) (struct) + + { + + if err := t.PartitionsPoSted.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.PartitionsPoSted: %w", err) + } + } // t.EarlyTerminations (bitfield.BitField) (struct) @@ -915,25 +924,16 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { } } - // t.PartitionsPoSted (bitfield.BitField) (struct) - - { - - if err := t.PartitionsPoSted.UnmarshalCBOR(br); err != nil { - return xerrors.Errorf("unmarshaling t.PartitionsPoSted: %w", err) - } - - } - // t.PoStSubmissions (cid.Cid) (struct) + // t.OptimisticPoStSubmissions (cid.Cid) (struct) { c, err := cbg.ReadCid(br) if err != nil { - return xerrors.Errorf("failed to read cid field t.PoStSubmissions: %w", err) + return xerrors.Errorf("failed to read cid field t.OptimisticPoStSubmissions: %w", err) } - t.PoStSubmissions = c + t.OptimisticPoStSubmissions = c } // t.PartitionsSnapshot (cid.Cid) (struct) @@ -948,16 +948,16 @@ func (t *Deadline) UnmarshalCBOR(r io.Reader) error { t.PartitionsSnapshot = c } - // t.PoStSubmissionsSnapshot (cid.Cid) (struct) + // t.OptimisticPoStSubmissionsSnapshot (cid.Cid) (struct) { c, err := cbg.ReadCid(br) if err != nil { - return xerrors.Errorf("failed to read cid field t.PoStSubmissionsSnapshot: %w", err) + return xerrors.Errorf("failed to read cid field t.OptimisticPoStSubmissionsSnapshot: %w", err) } - t.PoStSubmissionsSnapshot = c + t.OptimisticPoStSubmissionsSnapshot = c } return nil @@ -2460,14 +2460,14 @@ func (t *WindowedPoSt) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufChallengeWindowedPoStParams = []byte{130} +var lengthBufDisputeWindowedPoStParams = []byte{130} -func (t *ChallengeWindowedPoStParams) MarshalCBOR(w io.Writer) error { +func (t *DisputeWindowedPoStParams) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufChallengeWindowedPoStParams); err != nil { + if _, err := w.Write(lengthBufDisputeWindowedPoStParams); err != nil { return err } @@ -2479,17 +2479,17 @@ func (t *ChallengeWindowedPoStParams) MarshalCBOR(w io.Writer) error { return err } - // t.ProofIndex (uint64) (uint64) + // t.PoStIndex (uint64) (uint64) - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.ProofIndex)); err != nil { + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.PoStIndex)); err != nil { return err } return nil } -func (t *ChallengeWindowedPoStParams) UnmarshalCBOR(r io.Reader) error { - *t = ChallengeWindowedPoStParams{} +func (t *DisputeWindowedPoStParams) UnmarshalCBOR(r io.Reader) error { + *t = DisputeWindowedPoStParams{} br := cbg.GetPeeker(r) scratch := make([]byte, 8) @@ -2520,7 +2520,7 @@ func (t *ChallengeWindowedPoStParams) UnmarshalCBOR(r io.Reader) error { t.Deadline = uint64(extra) } - // t.ProofIndex (uint64) (uint64) + // t.PoStIndex (uint64) (uint64) { @@ -2531,7 +2531,7 @@ func (t *ChallengeWindowedPoStParams) UnmarshalCBOR(r io.Reader) error { if maj != cbg.MajUnsignedInt { return fmt.Errorf("wrong type for uint64 field") } - t.ProofIndex = uint64(extra) + t.PoStIndex = uint64(extra) } return nil diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 4a4b60812..769244568 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -41,6 +41,10 @@ type Deadline struct { // recovered, and this queue will not be updated at that time. ExpirationsEpochs cid.Cid // AMT[ChainEpoch]BitField + // Partitions that have been proved by window PoSts so far during the + // current challenge window. + PartitionsPoSted bitfield.BitField + // Partitions with sectors that terminated early. EarlyTerminations bitfield.BitField @@ -53,29 +57,21 @@ type Deadline struct { // Memoized sum of faulty power in partitions. FaultyPower PowerPair - // Partitions that have been proved by window PoSts so far during the - // current challenge window. - // - // Except during the challenge window immediately following the actors - // v3 upgrade, all partitions marked in this bitfield must also be - // marked in one of the "Partitions" bitfields in the Proofs AMT. This - // bitfield exists so we can efficiently determine which partitions have - // been proven, while the per-proof bitfields exist so that proofs can - // be challenged later. - PartitionsPoSted bitfield.BitField - - // AMT of WindowPoSt proofs. - // TODO: this AMT could be inefficient. The proof will be ~200 bytes so - // we could end up writing ~1600 bytes to update this AMT. - // We could also mis-estimate gas, so we need to be very careful here. - PoStSubmissions cid.Cid // AMT[]WindowedPoSt + // AMT of optimistically accepted WindowPoSt proofs, submitted during + // the current challenge window. At the end of the challenge window, + // this AMT will be moved to PoStSubmissionsSnapshot. WindowPoSt proofs + // verified on-chain do not appear in this AMT. + OptimisticPoStSubmissions cid.Cid // AMT[]WindowedPoSt // Snapshot of partition state at the end of the previous challenge // window for this deadline. PartitionsSnapshot cid.Cid // Snapshot of the proofs submitted by the end of the previous challenge // window for this deadline. - PoStSubmissionsSnapshot cid.Cid + // + // These proofs may be disputed via DisputeWindowedPoSt. Successfully + // disputed window PoSts are removed from the snapshot. + OptimisticPoStSubmissionsSnapshot cid.Cid } type WindowedPoSt struct { @@ -91,7 +87,10 @@ type WindowedPoSt struct { // Bitwidth of AMTs determined empirically from mutation patterns and projections of mainnet data. const DeadlinePartitionsAmtBitwidth = 3 // Usually a small array const DeadlineExpirationAmtBitwidth = 5 -const DeadlinePoStSubmissionsAmtBitwidth = 5 + +// Given that 4 partitions can be proven in one post, this AMT's height will +// only exceed the partition AMT's height at ~0.75EiB of storage. +const DeadlineOptimisticPoStSubmissionsAmtBitwidth = 2 // // Deadlines (plural) @@ -163,22 +162,22 @@ func ConstructDeadline(store adt.Store) (*Deadline, error) { return nil, xerrors.Errorf("failed to construct empty deadline expiration array: %w", err) } - emptyPoStSubmissionsArrayCid, err := adt.StoreEmptyArray(store, DeadlinePoStSubmissionsAmtBitwidth) + emptyPoStSubmissionsArrayCid, err := adt.StoreEmptyArray(store, DeadlineOptimisticPoStSubmissionsAmtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to construct empty proofs array: %w", err) } return &Deadline{ - Partitions: emptyPartitionsArrayCid, - ExpirationsEpochs: emptyDeadlineExpirationArrayCid, - EarlyTerminations: bitfield.New(), - LiveSectors: 0, - TotalSectors: 0, - FaultyPower: NewPowerPairZero(), - PartitionsPoSted: bitfield.New(), - PoStSubmissions: emptyPoStSubmissionsArrayCid, - PartitionsSnapshot: emptyPartitionsArrayCid, - PoStSubmissionsSnapshot: emptyPoStSubmissionsArrayCid, + Partitions: emptyPartitionsArrayCid, + ExpirationsEpochs: emptyDeadlineExpirationArrayCid, + EarlyTerminations: bitfield.New(), + LiveSectors: 0, + TotalSectors: 0, + FaultyPower: NewPowerPairZero(), + PartitionsPoSted: bitfield.New(), + OptimisticPoStSubmissions: emptyPoStSubmissionsArrayCid, + PartitionsSnapshot: emptyPartitionsArrayCid, + OptimisticPoStSubmissionsSnapshot: emptyPoStSubmissionsArrayCid, }, nil } @@ -193,7 +192,7 @@ func (d *Deadline) PartitionsArray(store adt.Store) (*adt.Array, error) { func (d *Deadline) PartitionsSnapshotArray(store adt.Store) (*adt.Array, error) { arr, err := adt.AsArray(store, d.PartitionsSnapshot, DeadlinePartitionsAmtBitwidth) if err != nil { - return nil, xc.ErrIllegalState.Wrapf("failed to load partitions: %w", err) + return nil, xerrors.Errorf("failed to load partitions: %w", err) } return arr, nil } @@ -222,7 +221,7 @@ func (d *Deadline) LoadPartitionSnapshot(store adt.Store, partIdx uint64) (*Part var partition Partition found, err := partitions.Get(partIdx, &partition) if err != nil { - return nil, xc.ErrIllegalState.Wrapf("failed to lookup partition %d: %w", partIdx, err) + return nil, xerrors.Errorf("failed to lookup partition %d: %w", partIdx, err) } if !found { return nil, xc.ErrNotFound.Wrapf("no partition %d", partIdx) @@ -934,15 +933,14 @@ func (dl *Deadline) ProcessDeadlineEnd(store adt.Store, quant QuantSpec, faultEx // Reset PoSt submissions, snapshot proofs. dl.PartitionsPoSted = bitfield.New() dl.PartitionsSnapshot = dl.Partitions - dl.PoStSubmissionsSnapshot = dl.PoStSubmissions - dl.PoStSubmissions, err = adt.StoreEmptyArray(store, DeadlinePoStSubmissionsAmtBitwidth) + dl.OptimisticPoStSubmissionsSnapshot = dl.OptimisticPoStSubmissions + dl.OptimisticPoStSubmissions, err = adt.StoreEmptyArray(store, DeadlineOptimisticPoStSubmissionsAmtBitwidth) if err != nil { return powerDelta, penalizedPower, xerrors.Errorf("failed to clear pending proofs array: %w", err) } return powerDelta, penalizedPower, nil } -// TODO: We're only using PowerDelta at this point. We should simplify. type PoStResult struct { // Power activated or deactivated (positive or negative). PowerDelta PowerPair @@ -974,6 +972,11 @@ func (dl *Deadline) RecordProvenSectors( for _, partition := range postPartitions { partitionIndexes.Set(partition.Index) } + if numPartitions, err := partitionIndexes.Count(); err != nil { + return nil, xerrors.Errorf("failed to count posted partitions: %w", err) + } else if numPartitions != uint64(len(postPartitions)) { + return nil, xc.ErrIllegalArgument.Wrapf("duplicate partitions proven") + } // First check to see if we're proving any already proven partitions. // This is faster than checking one by one. @@ -1088,7 +1091,7 @@ func (dl *Deadline) RecordProvenSectors( func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitField, proofs []proof.PoStProof) error { // Save proof. - proofArr, err := adt.AsArray(store, dl.PoStSubmissions, DeadlinePoStSubmissionsAmtBitwidth) + proofArr, err := adt.AsArray(store, dl.OptimisticPoStSubmissions, DeadlineOptimisticPoStSubmissionsAmtBitwidth) if err != nil { return xerrors.Errorf("failed to load proofs: %w", err) } @@ -1104,7 +1107,7 @@ func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitFie if err != nil { return xerrors.Errorf("failed to save proofs: %w", err) } - dl.PoStSubmissions = root + dl.OptimisticPoStSubmissions = root return nil } diff --git a/actors/builtin/miner/deadline_state_test.go b/actors/builtin/miner/deadline_state_test.go index 8e3121890..7cf3632c6 100644 --- a/actors/builtin/miner/deadline_state_test.go +++ b/actors/builtin/miner/deadline_state_test.go @@ -726,6 +726,28 @@ func TestDeadlines(t *testing.T) { require.Contains(t, err.Error(), "no such partition") }) + t.Run("post partition twice", func(t *testing.T) { + store := ipld.NewADTStore(context.Background()) + + dl := emptyDeadline(t, store) + addSectors(t, store, dl, true) + + // add an inactive sector + power, err := dl.AddSectors(store, partitionSize, false, extraSectors, sectorSize, quantSpec) + require.NoError(t, err) + expectedPower := miner.PowerForSectors(sectorSize, extraSectors) + assert.True(t, expectedPower.Equals(power)) + + sectorArr := sectorsArr(t, store, allSectors) + + _, err = dl.RecordProvenSectors(store, sectorArr, sectorSize, quantSpec, 13, []miner.PoStPartition{ + {Index: 0, Skipped: bf()}, + {Index: 0, Skipped: bf()}, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "duplicate partitions proven") + }) + t.Run("retract recoveries", func(t *testing.T) { store := ipld.NewADTStore(context.Background()) dl := emptyDeadline(t, store) diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index bfe5dbbf5..1adfbf75e 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -57,7 +57,8 @@ func FindSector(store adt.Store, deadlines *Deadlines, sectorNum abi.SectorNumbe return 0, 0, xerrors.Errorf("sector %d not due at any deadline", sectorNum) } -// Returns true if the deadline at the given index is currently mutable. +// Returns true if the deadline at the given index is currently mutable. A +// "mutable" deadline may have new sectors assigned to it. func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { // Get the next non-elapsed deadline (i.e., the next time we care about // mutations to the deadline). @@ -67,13 +68,25 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE return currentEpoch < dlInfo.Open-WPoStChallengeWindow } -func deadlineAvailableForChallenge(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { +// Returns true if optimistically accepted posts submitted to the given deadline +// may be disputed. Specifically, this ensures that: +// +// 1. Optimistic PoSts may not be disputed while the challenge window is open. +// 2. Optimistic PoSts may not be disputed after the miner could have compacted the deadline. +func deadlineAvailableForOptimisticPoStDispute(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() - return !dlInfo.IsOpen() && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStProofChallengePeriod + return !dlInfo.IsOpen() && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStDisputeWindow } +// Returns true if the given deadline may compacted in the current epoch. +// Deadlines may not be compacted when: +// +// 1. The deadline is currently being challenged. +// 2. The deadline is to be challenged next. +// 3. Optimistically accepted posts from the deadline's last challenge window +// can currently be disputed. func deadlineAvailableForCompaction(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { return deadlineIsMutable(provingPeriodStart, dlIdx, currentEpoch) && - !deadlineAvailableForChallenge(provingPeriodStart, dlIdx, currentEpoch) + !deadlineAvailableForOptimisticPoStDispute(provingPeriodStart, dlIdx, currentEpoch) } diff --git a/actors/builtin/miner/deadlines_helper_test.go b/actors/builtin/miner/deadlines_helper_test.go index 4aa04d910..f3858700d 100644 --- a/actors/builtin/miner/deadlines_helper_test.go +++ b/actors/builtin/miner/deadlines_helper_test.go @@ -21,10 +21,10 @@ func TestCompactionWindow(t *testing.T) { assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Close), "compaction is not possible immediately after the window") - assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Last()+WPoStProofChallengePeriod), + assert.False(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Last()+WPoStDisputeWindow), "compaction is not possible before the proof challenge period has passed") - assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod), + assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Close+WPoStDisputeWindow), "compaction is possible after the proof challenge period has passed") assert.True(t, deadlineAvailableForCompaction(periodStart, 0, dlInfo.Open+WPoStProvingPeriod-WPoStChallengeWindow-1), @@ -36,12 +36,12 @@ func TestCompactionWindow(t *testing.T) { func TestChallengeWindow(t *testing.T) { periodStart := abi.ChainEpoch(1024) dlInfo := NewDeadlineInfo(periodStart, 0, 0) - assert.False(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Open), + assert.False(t, deadlineAvailableForOptimisticPoStDispute(periodStart, 0, dlInfo.Open), "proof challenge is not possible while the window is open") - assert.True(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close), + assert.True(t, deadlineAvailableForOptimisticPoStDispute(periodStart, 0, dlInfo.Close), "proof challenge is possible after the window is closes") - assert.True(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod-1), + assert.True(t, deadlineAvailableForOptimisticPoStDispute(periodStart, 0, dlInfo.Close+WPoStDisputeWindow-1), "proof challenge is possible until the proof challenge period has passed") - assert.False(t, deadlineAvailableForChallenge(periodStart, 0, dlInfo.Close+WPoStProofChallengePeriod), + assert.False(t, deadlineAvailableForOptimisticPoStDispute(periodStart, 0, dlInfo.Close+WPoStDisputeWindow), "proof challenge is not possible after the proof challenge period has passed") } diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 84cf6963b..b7c723ade 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -72,7 +72,7 @@ func (a Actor) Exports() []interface{} { 21: a.ConfirmUpdateWorkerKey, 22: a.RepayDebt, 23: a.ChangeOwnerAddress, - 24: a.ChallengeWindowedPoSt, + 24: a.DisputeWindowedPoSt, } } @@ -417,7 +417,7 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) // // NOTE: This function does not actually check the proofs but does assume that they're correct. Instead, // it snapshots the deadline's state and the submitted proofs at the end of the challenge window and - // allows third-parties to challenge these proofs. + // allows third-parties to dispute these proofs. // // While we could perform _all_ operations at the end of challenge window, we do as we can here to avoid // overloading cron. @@ -449,7 +449,8 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) sectorInfos, err := sectors.LoadForProof(postResult.Sectors, postResult.IgnoredSectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post verification") - verifyWindowedPost(rt, currDeadline.Challenge, sectorInfos, params.Proofs, false) + err = verifyWindowedPost(rt, currDeadline.Challenge, sectorInfos, params.Proofs) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "window post failed") } err = deadlines.UpdateDeadline(store, params.Deadline, deadline) @@ -472,13 +473,13 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) return nil } -type ChallengeWindowedPoStParams struct { - Deadline uint64 - ProofIndex uint64 // only one is allowed at a time to avoid loading too many sector infos. +type DisputeWindowedPoStParams struct { + Deadline uint64 + PoStIndex uint64 // only one is allowed at a time to avoid loading too many sector infos. } -func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStParams) *abi.EmptyValue { - rt.ValidateImmediateCallerAcceptAny() +func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams) *abi.EmptyValue { + rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) if params.Deadline >= WPoStPeriodDeadlines { rt.Abortf(exitcode.ErrIllegalArgument, "invalid deadline %d of %d", params.Deadline, WPoStPeriodDeadlines) @@ -495,12 +496,12 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa powerDelta := NewPowerPairZero() var st State rt.StateTransaction(&st, func() { - if !deadlineAvailableForChallenge(st.ProvingPeriodStart, params.Deadline, currEpoch) { - rt.Abortf(exitcode.ErrForbidden, "cannot challenge a window post for an open deadline") + if !deadlineAvailableForOptimisticPoStDispute(st.ProvingPeriodStart, params.Deadline, currEpoch) { + rt.Abortf(exitcode.ErrForbidden, "can only dispute window posts during the dispute window (%d epochs after the challenge window closes)", WPoStDisputeWindow) } info := getMinerInfo(rt, &st) - faultyPower := NewPowerPairZero() + disputedPower := NewPowerPairZero() store := adt.AsStore(rt) // Check proof @@ -513,7 +514,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa dlCurrent, err := deadlinesCurrent.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") - proofsSnapshot, err := adt.AsArray(store, dlCurrent.PoStSubmissionsSnapshot, DeadlinePoStSubmissionsAmtBitwidth) + proofsSnapshot, err := adt.AsArray(store, dlCurrent.OptimisticPoStSubmissionsSnapshot, DeadlineOptimisticPoStSubmissionsAmtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") partitionsSnapshot, err := dlCurrent.PartitionsSnapshotArray(store) @@ -521,20 +522,20 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // Load the target proof. var post WindowedPoSt - found, err := proofsSnapshot.Get(params.ProofIndex, &post) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof %d", params.ProofIndex) + found, err := proofsSnapshot.Get(params.PoStIndex, &post) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof %d", params.PoStIndex) if !found { - rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.ProofIndex) + rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.PoStIndex) } var allSectors, allIgnored []bitfield.BitField - faults := make(PartitionSectorMap) + disputedSectors := make(PartitionSectorMap) err = post.Partitions.ForEach(func(partIdx uint64) error { var partitionSnapshot Partition if found, err := partitionsSnapshot.Get(partIdx, &partitionSnapshot); err != nil { return err } else if !found { - return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.ProofIndex) + return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.PoStIndex) } // Record sectors for proof verification @@ -548,18 +549,18 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa if err != nil { return err } - err = faults.Add(partIdx, active) + err = disputedSectors.Add(partIdx, active) if err != nil { return err } - // Record faulty power for penalties. + // Record disputed power for penalties. // // NOTE: This also includes power that was // activated at the end of the last challenge // window, and power from sectors that have since // expired. - faultyPower = faultyPower.Add(partitionSnapshot.ActivePower()) + disputedPower = disputedPower.Add(partitionSnapshot.ActivePower()) return nil }) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") @@ -574,7 +575,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa sectors, err := LoadSectors(store, st.Sectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors for post challenge") + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors to dispute window post") // Find the proving period start for the deadline in question. ppStart := st.ProvingPeriodStart @@ -585,7 +586,12 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa targetDeadline := NewDeadlineInfo(ppStart, params.Deadline, currEpoch) // Fails if validation succeeds. - verifyWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs, true) + err = verifyWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs) + if err == nil { + rt.Abortf(exitcode.ErrIllegalArgument, "failed to dispute valid post") + return + } + rt.Log(rtt.INFO, "successfully disputed: %s", err) // Ok, now we record faults. This always works because // we don't allow compaction/moving sectors during the @@ -594,13 +600,13 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa // However, some of these sectors may have been // terminated. That's fine, we'll skip them. faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge - powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, faults) + powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, disputedSectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") - // Delete challenged proof so it can't be charged multiple times. - err = proofsSnapshot.Delete(params.ProofIndex) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete challenged proof") - dlCurrent.PoStSubmissionsSnapshot, err = proofsSnapshot.Root() + // Delete disputed proof so it can't be charged multiple times. + err = proofsSnapshot.Delete(params.PoStIndex) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete disputed proof") + dlCurrent.OptimisticPoStSubmissionsSnapshot, err = proofsSnapshot.Root() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update proofs") err = deadlinesCurrent.UpdateDeadline(store, params.Deadline, dlCurrent) @@ -614,7 +620,7 @@ func (a Actor) ChallengeWindowedPoSt(rt Runtime, params *ChallengeWindowedPoStPa penaltyTarget := PledgePenaltyForInvalidWindowPoSt( epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, - faultyPower.QA, + disputedPower.QA, ) err := st.ApplyPenalty(penaltyTarget) @@ -1594,7 +1600,7 @@ func (a Actor) CompactPartitions(rt Runtime, params *CompactPartitionsParams) *a if !deadlineAvailableForCompaction(st.ProvingPeriodStart, params.Deadline, rt.CurrEpoch()) { rt.Abortf(exitcode.ErrForbidden, - "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, WPoStProofChallengePeriod) + "cannot compact deadline %d during its challenge window, or the prior challenge window, or before %d epochs have passed since its last challenge window ended", params.Deadline, WPoStDisputeWindow) } submissionPartitionLimit := loadPartitionsSectorsMax(info.WindowPoStPartitionSectors) @@ -2246,7 +2252,7 @@ func havePendingEarlyTerminations(rt Runtime, st *State) bool { return !noEarlyTerminations } -func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof, expectFail bool) { +func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*SectorOnChainInfo, proofs []proof.PoStProof) error { minerActorID, err := addr.IDFromAddress(rt.Receiver()) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "runtime provided bad receiver address %v", rt.Receiver()) @@ -2276,16 +2282,10 @@ func verifyWindowedPost(rt Runtime, challengeEpoch abi.ChainEpoch, sectors []*Se // Verify the PoSt Proof err = rt.VerifyPoSt(pvInfo) - - if expectFail { - if err == nil { - rt.Abortf(exitcode.ErrIllegalArgument, "failed to challenge valid post %+v", pvInfo) - return - } - rt.Log(rtt.INFO, "post successfully challenged %+v: %s", pvInfo, err) - } else { - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "invalid PoSt %+v", pvInfo) + if err != nil { + return fmt.Errorf("invalid PoSt %+v: %w", pvInfo, err) } + return nil } // SealVerifyParams is the structure of information that must be sent with a diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 1fadfd59f..37852b3ce 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1871,7 +1871,7 @@ func TestWindowPost(t *testing.T) { WithEpoch(precommitEpoch). WithBalance(bigBalance, big.Zero()) - testBasicPoSt := func(challengeSucceed bool) { + testBasicPoSt := func(disputeSucceed bool) { proofs := makePoStProofs(actor.postProofType) rt := builder.Build(t) @@ -1902,9 +1902,9 @@ func TestWindowPost(t *testing.T) { deadline := actor.getDeadline(rt, dlIdx) assertBitfieldEquals(t, deadline.PartitionsPoSted, pIdx) - postsCid := deadline.PoStSubmissions + postsCid := deadline.OptimisticPoStSubmissions - posts, err := adt.AsArray(store, postsCid, miner.DeadlinePoStSubmissionsAmtBitwidth) + posts, err := adt.AsArray(store, postsCid, miner.DeadlineOptimisticPoStSubmissionsAmtBitwidth) require.NoError(t, err) require.EqualValues(t, posts.Length(), 1) var post miner.WindowedPoSt @@ -1920,25 +1920,25 @@ func TestWindowPost(t *testing.T) { deadline = actor.getDeadline(rt, dlIdx) // Proofs should exist in snapshot. - require.Equal(t, deadline.PoStSubmissionsSnapshot, postsCid) + require.Equal(t, deadline.OptimisticPoStSubmissionsSnapshot, postsCid) - var result *poStChallengeResult - if challengeSucceed { + var result *poStDisputeResult + if disputeSucceed { expectedFee := miner.PledgePenaltyForInvalidWindowPoSt(actor.epochRewardSmooth, actor.epochQAPowerSmooth, pwr.QA) - result = &poStChallengeResult{ + result = &poStDisputeResult{ expectedPowerDelta: pwr.Neg(), expectedPenalty: expectedFee, expectedPledgeDelta: big.Zero(), } } - actor.challengeWindowPoSt(rt, dlinfo, 0, []*miner.SectorOnChainInfo{sector}, result) + actor.disputeWindowPoSt(rt, dlinfo, 0, []*miner.SectorOnChainInfo{sector}, result) } t.Run("test proof", func(t *testing.T) { testBasicPoSt(true) }) - t.Run("test bad proof accepted and challenged", func(t *testing.T) { + t.Run("test bad proof accepted and disputed", func(t *testing.T) { testBasicPoSt(false) }) @@ -3435,7 +3435,7 @@ func TestCompactPartitions(t *testing.T) { actor.terminateSectors(rt, sectors, expectedFee) // Wait WPoStProofChallengePeriod epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStDisputeWindow) // compacting partition will remove sector1 but retain sector 2, 3 and 4. partId := uint64(0) @@ -3465,7 +3465,7 @@ func TestCompactPartitions(t *testing.T) { actor.declareFaults(rt, info[0]) // Wait WPoStProofChallengePeriod epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStDisputeWindow) partId := uint64(0) deadlineId := uint64(0) @@ -3491,7 +3491,7 @@ func TestCompactPartitions(t *testing.T) { actor.commitAndProveSectors(rt, 2, defaultSectorExpiration, [][]abi.DealID{{10}, {20}}) // Wait WPoStProofChallengePeriod epochs so we can compact the sector. - advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStProofChallengePeriod) + advanceToEpochWithCron(rt, actor, rt.Epoch()+miner.WPoStDisputeWindow) partId := uint64(0) deadlineId := uint64(0) @@ -4656,7 +4656,7 @@ func (h *actorHarness) getPartitionSnapshot(rt *mock.Runtime, deadline *miner.De } func (h *actorHarness) getSubmittedProof(rt *mock.Runtime, deadline *miner.Deadline, idx uint64) *miner.WindowedPoSt { - proofs, err := adt.AsArray(rt.AdtStore(), deadline.PoStSubmissionsSnapshot, miner.DeadlinePoStSubmissionsAmtBitwidth) + proofs, err := adt.AsArray(rt.AdtStore(), deadline.OptimisticPoStSubmissionsSnapshot, miner.DeadlineOptimisticPoStSubmissionsAmtBitwidth) require.NoError(h.t, err) var post miner.WindowedPoSt found, err := proofs.Get(idx, &post) @@ -5144,15 +5144,15 @@ func (h *actorHarness) advancePastDeadlineEndWithCron(rt *mock.Runtime) { rt.SetEpoch(deadline.NextPeriodStart()) } -type poStChallengeResult struct { +type poStDisputeResult struct { expectedPowerDelta miner.PowerPair expectedPledgeDelta abi.TokenAmount expectedPenalty abi.TokenAmount } -func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, proofIndex uint64, infos []*miner.SectorOnChainInfo, expectSuccess *poStChallengeResult) { +func (h *actorHarness) disputeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, proofIndex uint64, infos []*miner.SectorOnChainInfo, expectSuccess *poStDisputeResult) { rt.SetCaller(h.worker, builtin.AccountActorCodeID) - rt.ExpectValidateCallerAny() + rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) currentReward := reward.ThisEpochRewardReturn{ ThisEpochBaselinePower: h.baselinePower, @@ -5263,16 +5263,16 @@ func (h *actorHarness) challengeWindowPoSt(rt *mock.Runtime, deadline *dline.Inf } } - params := miner.ChallengeWindowedPoStParams{ - Deadline: deadline.Index, - ProofIndex: proofIndex, + params := miner.DisputeWindowedPoStParams{ + Deadline: deadline.Index, + PoStIndex: proofIndex, } if expectSuccess == nil { - rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "failed to challenge valid post", func() { - rt.Call(h.a.ChallengeWindowedPoSt, ¶ms) + rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "failed to dispute valid post", func() { + rt.Call(h.a.DisputeWindowedPoSt, ¶ms) }) } else { - rt.Call(h.a.ChallengeWindowedPoSt, ¶ms) + rt.Call(h.a.DisputeWindowedPoSt, ¶ms) } rt.Verify() } diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index 946fb0879..c6845367a 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -196,10 +196,9 @@ const DealLimitDenominator = 134217728 // PARAM_SPEC // for permissioned actor methods and winning block elections. const ConsensusFaultIneligibilityDuration = ChainFinality -// PoStChallengePeriod is the period after a challenge window ends during which -// PoSts submitted during that period may be challenged. -// TODO: this name can easily be confused with WPoStChallengeWindow. -const WPoStProofChallengePeriod = 2 * ChainFinality // PARAM_TODO +// WPoStDisputeWindow is the period after a challenge window ends during which +// PoSts submitted during that period may be disputed. +const WPoStDisputeWindow = 2 * ChainFinality // PARAM_TODO // DealWeight and VerifiedDealWeight are spacetime occupied by regular deals and verified deals in a sector. // Sum of DealWeight and VerifiedDealWeight should be less than or equal to total SpaceTime of a sector. diff --git a/actors/builtin/miner/policy_test.go b/actors/builtin/miner/policy_test.go index feee96c10..2f5e0473b 100644 --- a/actors/builtin/miner/policy_test.go +++ b/actors/builtin/miner/policy_test.go @@ -151,13 +151,13 @@ func TestPoStTimeConstraints(t *testing.T) { immutablePeriod := 2 * miner.WPoStChallengeWindow // If we have less than finality, an attacker could try a very long - // (almost finality) fork and leave no time to challenge. - require.True(t, miner.WPoStProofChallengePeriod >= miner.ChainFinality, - "the proof challenge period must exceed finality") + // (almost finality) fork and leave no time to dispute. + require.True(t, miner.WPoStDisputeWindow >= miner.ChainFinality, + "the proof dispute period must exceed finality") // This is an arbitrary constraint, but we should ensure we leave _some_ // time for compaction. - compactionPeriod := miner.WPoStProvingPeriod - (immutablePeriod + miner.WPoStProofChallengePeriod) + compactionPeriod := miner.WPoStProvingPeriod - (immutablePeriod + miner.WPoStDisputeWindow) require.True(t, compactionPeriod > 3*builtin.EpochsInHour, "there must be at least a 3 hour window for partition compaction") diff --git a/gen/gen.go b/gen/gen.go index a5bafd50f..4f4c1b54f 100644 --- a/gen/gen.go +++ b/gen/gen.go @@ -199,7 +199,7 @@ func main() { //miner.CompactPartitionsParams{}, // Aliased from v0 //miner.CompactSectorNumbersParams{}, // Aliased from v0 //miner.CronEventPayload{}, // Aliased from v0 - miner.ChallengeWindowedPoStParams{}, + miner.DisputeWindowedPoStParams{}, // other types //miner.FaultDeclaration{}, // Aliased from v0 //miner.RecoveryDeclaration{}, // Aliased from v0 From c322a4419176f085049ef3d63b1b7ea255c0873f Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 6 Jan 2021 14:32:13 -0800 Subject: [PATCH 38/61] move proving period constraints into an init function This ensures we never break these constraints, even in debug builds. --- actors/builtin/miner/policy.go | 25 +++++++++++++++++++++++++ actors/builtin/miner/policy_test.go | 21 --------------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index c6845367a..d5a9254b8 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -41,6 +41,31 @@ func init() { if abi.ChainEpoch(WPoStPeriodDeadlines)*WPoStChallengeWindow != WPoStProvingPeriod { panic(fmt.Sprintf("incompatible proving period %d and challenge window %d", WPoStProvingPeriod, WPoStChallengeWindow)) } + + // Check to make sure the dispute window is longer than finality so there's always some time to dispute bad proofs. + if WPoStDisputeWindow <= ChainFinality { + panic(fmt.Sprintf("the proof dispute period %d must exceed finality %d", WPoStDisputeWindow, ChainFinality)) + } + + // A deadline becomes immutable one challenge window before it's challenge window opens. + // The challenge lookback must fall within this immutability period. + if WPoStChallengeLookback > WPoStChallengeWindow { + panic(fmt.Sprintf("the challenge lookback cannot exceed one challenge window")) + } + + // Deadlines are immutable when the challenge window is open, and during + // the previous challenge window. + immutableWindow := 2 * WPoStChallengeWindow + + // We want to reserve at least one deadline's worth of time to compact a + // deadline. + minCompactionWindow := WPoStChallengeWindow + + // Make sure we have enough time in the proving period to do everything we need. + if (minCompactionWindow + immutableWindow + WPoStDisputeWindow) <= WPoStProvingPeriod { + panic(fmt.Sprintf("together, the minimum compaction window (%d) immutability window (%d) and the dispute window (%d) exceed the proving period (%d)", + minCompactionWindow, immutableWindow, WPoStDisputeWindow, WPoStProvingPeriod)) + } } // The maximum number of partitions that can be loaded in a single invocation. diff --git a/actors/builtin/miner/policy_test.go b/actors/builtin/miner/policy_test.go index 2f5e0473b..881ced8bc 100644 --- a/actors/builtin/miner/policy_test.go +++ b/actors/builtin/miner/policy_test.go @@ -145,24 +145,3 @@ func assertEqual(t *testing.T, a, b big.Int) { assert.Equal(t, a, b) } } - -func TestPoStTimeConstraints(t *testing.T) { - // period during which a deadline is "immutable" - immutablePeriod := 2 * miner.WPoStChallengeWindow - - // If we have less than finality, an attacker could try a very long - // (almost finality) fork and leave no time to dispute. - require.True(t, miner.WPoStDisputeWindow >= miner.ChainFinality, - "the proof dispute period must exceed finality") - - // This is an arbitrary constraint, but we should ensure we leave _some_ - // time for compaction. - compactionPeriod := miner.WPoStProvingPeriod - (immutablePeriod + miner.WPoStDisputeWindow) - require.True(t, compactionPeriod > 3*builtin.EpochsInHour, - "there must be at least a 3 hour window for partition compaction") - - // If this constraint breaks, the challenge lookback may be _before_ the - // immutability period starts. - require.True(t, miner.WPoStChallengeLookback < immutablePeriod-miner.WPoStChallengeWindow, - "the challenge lookback must be inside the immutability period") -} From 559e414b6fb58f883a30f62db4bb0265286ab11b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 6 Jan 2021 15:49:04 -0800 Subject: [PATCH 39/61] fix test imports --- actors/builtin/miner/policy_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/actors/builtin/miner/policy_test.go b/actors/builtin/miner/policy_test.go index 881ced8bc..71e712418 100644 --- a/actors/builtin/miner/policy_test.go +++ b/actors/builtin/miner/policy_test.go @@ -6,7 +6,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/filecoin-project/specs-actors/v3/actors/builtin" "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" From 7de15da17aca00e86f6094cb97494eb7b0b42708 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 6 Jan 2021 15:50:14 -0800 Subject: [PATCH 40/61] fix policy check --- actors/builtin/miner/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index d5a9254b8..51857679e 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -62,7 +62,7 @@ func init() { minCompactionWindow := WPoStChallengeWindow // Make sure we have enough time in the proving period to do everything we need. - if (minCompactionWindow + immutableWindow + WPoStDisputeWindow) <= WPoStProvingPeriod { + if (minCompactionWindow + immutableWindow + WPoStDisputeWindow) > WPoStProvingPeriod { panic(fmt.Sprintf("together, the minimum compaction window (%d) immutability window (%d) and the dispute window (%d) exceed the proving period (%d)", minCompactionWindow, immutableWindow, WPoStDisputeWindow, WPoStProvingPeriod)) } From 3465b05d423cf477410dadaac5c8481b9afddaff Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 6 Jan 2021 16:04:13 -0800 Subject: [PATCH 41/61] add a bunch of dispute window tests --- actors/builtin/miner/miner_test.go | 59 ++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 37852b3ce..4ed6b3ef1 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -3506,24 +3506,75 @@ func TestCompactPartitions(t *testing.T) { rt := builder.Build(t) actor.constructAndVerify(rt) - rt.ExpectAbort(exitcode.ErrIllegalArgument, func() { + rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "invalid deadline 48", func() { actor.compactPartitions(rt, miner.WPoStPeriodDeadlines, bitfield.New()) }) actor.checkState(rt) }) - t.Run("fails if deadline is not mutable", func(t *testing.T) { + t.Run("fails if deadline is open for challenging", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + rt.SetEpoch(periodOffset) + rt.ExpectAbort(exitcode.ErrForbidden, func() { + actor.compactPartitions(rt, 0, bitfield.New()) + }) + actor.checkState(rt) + }) + + t.Run("fails if deadline is next up to be challenged", func(t *testing.T) { rt := builder.Build(t) actor.constructAndVerify(rt) - epoch := abi.ChainEpoch(200) - rt.SetEpoch(epoch) + rt.SetEpoch(periodOffset) rt.ExpectAbort(exitcode.ErrForbidden, func() { actor.compactPartitions(rt, 1, bitfield.New()) }) actor.checkState(rt) }) + t.Run("the deadline after the next deadline should still be open for compaction", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + rt.SetEpoch(periodOffset) + actor.compactPartitions(rt, 3, bitfield.New()) + actor.checkState(rt) + }) + + t.Run("deadlines challenged last proving period should still be in the dispute window", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + rt.SetEpoch(periodOffset) + rt.ExpectAbort(exitcode.ErrForbidden, func() { + actor.compactPartitions(rt, 47, bitfield.New()) + }) + actor.checkState(rt) + }) + + disputeEnd := periodOffset + miner.WPoStChallengeWindow + miner.WPoStDisputeWindow - 1 + t.Run("compaction should be forbidden during the dispute window", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + rt.SetEpoch(disputeEnd) + rt.ExpectAbort(exitcode.ErrForbidden, func() { + actor.compactPartitions(rt, 0, bitfield.New()) + }) + actor.checkState(rt) + }) + + t.Run("compaction should be allowed following the dispute window", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + rt.SetEpoch(disputeEnd + 1) + actor.compactPartitions(rt, 0, bitfield.New()) + actor.checkState(rt) + }) + t.Run("fails if partition count is above limit", func(t *testing.T) { rt := builder.Build(t) actor.constructAndVerify(rt) From 684491400cc8378930f774f3b40741aa000bd080 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 6 Jan 2021 16:12:24 -0800 Subject: [PATCH 42/61] use aliased adt import --- actors/migration/nv9/miner.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/actors/migration/nv9/miner.go b/actors/migration/nv9/miner.go index 97667a119..d1cb43ded 100644 --- a/actors/migration/nv9/miner.go +++ b/actors/migration/nv9/miner.go @@ -11,7 +11,6 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/v3/actors/util/adt" adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" ) @@ -79,7 +78,7 @@ func (m *minerMigrator) migrateDeadlines(ctx context.Context, store cbor.IpldSto outDeadlines := miner3.Deadlines{Due: [miner3.WPoStPeriodDeadlines]cid.Cid{}} // Start from an empty template to zero-initialize new fields. - deadlineTemplate, err := miner3.ConstructDeadline(adt.WrapStore(ctx, store)) + deadlineTemplate, err := miner3.ConstructDeadline(adt3.WrapStore(ctx, store)) if err != nil { return cid.Undef, xerrors.Errorf("failed to construct new deadline template") } From 95c05927fb21ff2135f2d1088d573a09aed71530 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 7 Jan 2021 11:03:21 -0800 Subject: [PATCH 43/61] move dispute info loading into deadline methods --- actors/builtin/miner/deadline_state.go | 104 ++++++++++++++++++++++++- actors/builtin/miner/miner_actor.go | 99 ++++++----------------- 2 files changed, 126 insertions(+), 77 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 769244568..c896e9f4c 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -1089,8 +1089,9 @@ func (dl *Deadline) RecordProvenSectors( }, nil } +// RecordPoStProofs records a set of optimistically accepted PoSt proofs +// (usually one), associating them with the given partitions. func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitField, proofs []proof.PoStProof) error { - // Save proof. proofArr, err := adt.AsArray(store, dl.OptimisticPoStSubmissions, DeadlineOptimisticPoStSubmissionsAmtBitwidth) if err != nil { return xerrors.Errorf("failed to load proofs: %w", err) @@ -1111,6 +1112,34 @@ func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitFie return nil } +// TakePoStProofs removes and returns a PoSt proof by index, along with the +// associated partitions. This method takes the PoSt from the PoSt submissions +// snapshot. +func (dl *Deadline) TakePoStProofs(store adt.Store, idx uint64) (partitions bitfield.BitField, proofs []proof.PoStProof, err error) { + proofArr, err := adt.AsArray(store, dl.OptimisticPoStSubmissionsSnapshot, DeadlineOptimisticPoStSubmissionsAmtBitwidth) + if err != nil { + return bitfield.New(), nil, xerrors.Errorf("failed to load proofs: %w", err) + } + var post WindowedPoSt + found, err := proofArr.Get(idx, &post) + if err != nil { + return bitfield.New(), nil, xerrors.Errorf("failed to retrieve proof %d: %w", idx, err) + } else if !found { + return bitfield.New(), nil, xc.ErrIllegalArgument.Wrapf("proof %d not found", idx) + } + err = proofArr.Delete(idx) + if err != nil { + return bitfield.New(), nil, xerrors.Errorf("failed to delete proof %d: %w", idx, err) + } + + root, err := proofArr.Root() + if err != nil { + return bitfield.New(), nil, xerrors.Errorf("failed to save proofs: %w", err) + } + dl.OptimisticPoStSubmissionsSnapshot = root + return post.Partitions, post.Proofs, nil +} + // RescheduleSectorExpirations reschedules the expirations of the given sectors // to the target epoch, skipping any sectors it can't find. // @@ -1175,6 +1204,79 @@ func (dl *Deadline) RescheduleSectorExpirations( return allReplaced, nil } +// DisputeInfo includes all the information necessary to dispute a post to the +// given partitions. +type DisputeInfo struct { + AllSectorNos, IgnoredSectorNos bitfield.BitField + DisputedSectors PartitionSectorMap + DisputedPower PowerPair +} + +// LoadPartitionsForDispute +func (dl *Deadline) LoadPartitionsForDispute(store adt.Store, partitions bitfield.BitField) (*DisputeInfo, error) { + partitionsSnapshot, err := dl.PartitionsSnapshotArray(store) + if err != nil { + return nil, xerrors.Errorf("failed to load partitions: %w", err) + } + + var allSectors, allIgnored []bitfield.BitField + disputedSectors := make(PartitionSectorMap) + disputedPower := NewPowerPairZero() + err = partitions.ForEach(func(partIdx uint64) error { + var partitionSnapshot Partition + if found, err := partitionsSnapshot.Get(partIdx, &partitionSnapshot); err != nil { + return err + } else if !found { + return xerrors.Errorf("failed to find partition %d", partIdx) + } + + // Record sectors for proof verification + allSectors = append(allSectors, partitionSnapshot.Sectors) + allIgnored = append(allIgnored, partitionSnapshot.Faults) + allIgnored = append(allIgnored, partitionSnapshot.Terminated) + allIgnored = append(allIgnored, partitionSnapshot.Unproven) + + // Record active sectors for marking faults. + active, err := partitionSnapshot.ActiveSectors() + if err != nil { + return err + } + err = disputedSectors.Add(partIdx, active) + if err != nil { + return err + } + + // Record disputed power for penalties. + // + // NOTE: This also includes power that was + // activated at the end of the last challenge + // window, and power from sectors that have since + // expired. + disputedPower = disputedPower.Add(partitionSnapshot.ActivePower()) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("when challenging post %d: %w", err) + } + + allSectorsNos, err := bitfield.MultiMerge(allSectors...) + if err != nil { + return nil, xerrors.Errorf("failed to merge sector bitfields: %w", err) + } + + allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) + if err != nil { + return nil, xerrors.Errorf("failed to merge fault bitfields: %w", err) + } + + return &DisputeInfo{ + AllSectorNos: allSectorsNos, + IgnoredSectorNos: allIgnoredNos, + DisputedSectors: disputedSectors, + DisputedPower: disputedPower, + }, nil +} + func (d *Deadline) ValidateState() error { if d.LiveSectors > d.TotalSectors { return xerrors.Errorf("Deadline left with more live sectors than total: %v", d) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index b7c723ade..eeab15fa1 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -505,88 +505,41 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams store := adt.AsStore(rt) // Check proof - // TODO: move into deadline state function. { - // Load the target state. + // Find the proving period start for the deadline in question. + ppStart := st.ProvingPeriodStart + if st.CurrentDeadline < params.Deadline { + ppStart -= WPoStProvingPeriod + } + targetDeadline := NewDeadlineInfo(ppStart, params.Deadline, currEpoch) + + // Load the target deadline. deadlinesCurrent, err := st.LoadDeadlines(store) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadlines") dlCurrent, err := deadlinesCurrent.LoadDeadline(store, params.Deadline) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deadline") - proofsSnapshot, err := adt.AsArray(store, dlCurrent.OptimisticPoStSubmissionsSnapshot, DeadlineOptimisticPoStSubmissionsAmtBitwidth) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs") - - partitionsSnapshot, err := dlCurrent.PartitionsSnapshotArray(store) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") - - // Load the target proof. - var post WindowedPoSt - found, err := proofsSnapshot.Get(params.PoStIndex, &post) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof %d", params.PoStIndex) - if !found { - rt.Abortf(exitcode.ErrIllegalArgument, "failed to find post %d", params.PoStIndex) - } - - var allSectors, allIgnored []bitfield.BitField - disputedSectors := make(PartitionSectorMap) - err = post.Partitions.ForEach(func(partIdx uint64) error { - var partitionSnapshot Partition - if found, err := partitionsSnapshot.Get(partIdx, &partitionSnapshot); err != nil { - return err - } else if !found { - return exitcode.ErrIllegalState.Wrapf("failed to find partition when challenging proof %d", params.PoStIndex) - } - - // Record sectors for proof verification - allSectors = append(allSectors, partitionSnapshot.Sectors) - allIgnored = append(allIgnored, partitionSnapshot.Faults) - allIgnored = append(allIgnored, partitionSnapshot.Terminated) - allIgnored = append(allIgnored, partitionSnapshot.Unproven) - - // Record active sectors for marking faults. - active, err := partitionSnapshot.ActiveSectors() - if err != nil { - return err - } - err = disputedSectors.Add(partIdx, active) - if err != nil { - return err - } - - // Record disputed power for penalties. - // - // NOTE: This also includes power that was - // activated at the end of the last challenge - // window, and power from sectors that have since - // expired. - disputedPower = disputedPower.Add(partitionSnapshot.ActivePower()) - return nil - }) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partitions") - - allSectorsNos, err := bitfield.MultiMerge(allSectors...) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge sector bitfields") + // Take the post from the snapshot for dispute. + // This operation REMOVES the PoSt from the snapshot so + // it can't be disputed again. If this method fails, + // this operation must be rolled back. + partitions, proofs, err := dlCurrent.TakePoStProofs(store, params.PoStIndex) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof for dispute") - allIgnoredNos, err := bitfield.MultiMerge(allIgnored...) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to merge fault bitfields") + // Load the partition info we need for the dispute. + disputeInfo, err := dlCurrent.LoadPartitionsForDispute(store, partitions) + disputedPower = disputeInfo.DisputedPower - // Load sectors. + // Load sectors for the dispute. sectors, err := LoadSectors(store, st.Sectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors array") - sectorInfos, err := sectors.LoadForProof(allSectorsNos, allIgnoredNos) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors to dispute window post") - - // Find the proving period start for the deadline in question. - ppStart := st.ProvingPeriodStart - if st.CurrentDeadline < params.Deadline { - ppStart -= WPoStProvingPeriod - } - targetDeadline := NewDeadlineInfo(ppStart, params.Deadline, currEpoch) + sectorInfos, err := sectors.LoadForProof(disputeInfo.AllSectorNos, disputeInfo.IgnoredSectorNos) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load sectors to dispute window post") - // Fails if validation succeeds. - err = verifyWindowedPost(rt, targetDeadline.Challenge, sectorInfos, post.Proofs) + // Check proof, we fail if validation succeeds. + err = verifyWindowedPost(rt, targetDeadline.Challenge, sectorInfos, proofs) if err == nil { rt.Abortf(exitcode.ErrIllegalArgument, "failed to dispute valid post") return @@ -600,15 +553,9 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams // However, some of these sectors may have been // terminated. That's fine, we'll skip them. faultExpirationEpoch := targetDeadline.Last() + FaultMaxAge - powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, disputedSectors) + powerDelta, err = dlCurrent.RecordFaults(store, sectors, info.SectorSize, QuantSpecForDeadline(targetDeadline), faultExpirationEpoch, disputeInfo.DisputedSectors) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to declare faults") - // Delete disputed proof so it can't be charged multiple times. - err = proofsSnapshot.Delete(params.PoStIndex) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete disputed proof") - dlCurrent.OptimisticPoStSubmissionsSnapshot, err = proofsSnapshot.Root() - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update proofs") - err = deadlinesCurrent.UpdateDeadline(store, params.Deadline, dlCurrent) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update deadline %d", params.Deadline) err = st.SaveDeadlines(store, deadlinesCurrent) From d58720d15a94b751b02b719f1bdc226db58ed3f2 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 7 Jan 2021 11:21:08 -0800 Subject: [PATCH 44/61] fix lints --- actors/builtin/miner/deadline_state.go | 2 +- actors/builtin/miner/miner_actor.go | 1 + actors/builtin/miner/policy.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index c896e9f4c..3ff87bef9 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -1256,7 +1256,7 @@ func (dl *Deadline) LoadPartitionsForDispute(store adt.Store, partitions bitfiel return nil }) if err != nil { - return nil, xerrors.Errorf("when challenging post %d: %w", err) + return nil, xerrors.Errorf("when disputing post: %w", err) } allSectorsNos, err := bitfield.MultiMerge(allSectors...) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index eeab15fa1..57c88ab9f 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -529,6 +529,7 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams // Load the partition info we need for the dispute. disputeInfo, err := dlCurrent.LoadPartitionsForDispute(store, partitions) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partition info for dispute") disputedPower = disputeInfo.DisputedPower // Load sectors for the dispute. diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index 51857679e..54a772328 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -50,7 +50,7 @@ func init() { // A deadline becomes immutable one challenge window before it's challenge window opens. // The challenge lookback must fall within this immutability period. if WPoStChallengeLookback > WPoStChallengeWindow { - panic(fmt.Sprintf("the challenge lookback cannot exceed one challenge window")) + panic("the challenge lookback cannot exceed one challenge window") } // Deadlines are immutable when the challenge window is open, and during From 3ebd74328f5fd7b0ae2447c70c0d2dfbc4162e5c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 7 Jan 2021 13:42:02 -0800 Subject: [PATCH 45/61] restrict the maximum size of post proofs --- actors/builtin/miner/miner_actor.go | 2 ++ actors/builtin/miner/policy.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 57c88ab9f..a01b678b1 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -367,6 +367,8 @@ func (a Actor) SubmitWindowedPoSt(rt Runtime, params *SubmitWindowedPoStParams) rt.Abortf(exitcode.ErrIllegalArgument, "expected exactly one proof, got %d", len(params.Proofs)) } else if params.Proofs[0].PoStProof != windowPoStProofType { rt.Abortf(exitcode.ErrIllegalArgument, "expected proof of type %s, got proof of type %s", params.Proofs[0], windowPoStProofType) + } else if len(params.Proofs[0].ProofBytes) > MaxPoStProofSize { + rt.Abortf(exitcode.ErrIllegalArgument, "expected proof to be smaller than %d bytes", MaxPoStProofSize) } // Validate that the miner didn't try to prove too many partitions at once. diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index 54a772328..fe228b6aa 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -93,6 +93,9 @@ const ( // Maximum size of a single prove-commit proof, in bytes (the expected size is 1920). const MaxProveCommitSize = 10240 +// Maximum size of a single prove-commit proof, in bytes (the expected size is 192). +const MaxPoStProofSize = 1024 + // Maximum number of control addresses a miner may register. const MaxControlAddresses = 10 From 396fe71645fdec7b7717e7ddfcdc96d74ddd1f04 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 7 Jan 2021 17:20:13 -0800 Subject: [PATCH 46/61] set the correct window post penalty --- actors/builtin/miner/monies.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/monies.go b/actors/builtin/miner/monies.go index 031679dd3..25fd58540 100644 --- a/actors/builtin/miner/monies.go +++ b/actors/builtin/miner/monies.go @@ -43,6 +43,9 @@ var ContinuedFaultProjectionPeriod = abi.ChainEpoch((builtin.EpochsInDay * Conti var TerminationPenaltyLowerBoundProjectionPeriod = abi.ChainEpoch((builtin.EpochsInDay * 35) / 10) // PARAM_SPEC +// FF + 2BR +var InvalidWindowPoStProjectionPeriod = abi.ChainEpoch(ContinuedFaultProjectionPeriod + 2*builtin.EpochsInDay) // PARAM_SPEC + // Fraction of assumed block reward penalized when a sector is terminated. var TerminationRewardFactor = builtin.BigFrac{ // PARAM_SPEC Numerator: big.NewInt(1), @@ -118,11 +121,9 @@ func PledgePenaltyForTermination(dayReward abi.TokenAmount, sectorAge abi.ChainE big.Mul(big.NewInt(builtin.EpochsInDay), TerminationRewardFactor.Denominator)))) // (epochs*AttoFIL/day -> AttoFIL) } -// The penalty for a sector continuing faulty for another proving period. -// It is a projection of the expected reward earned by the sector. -// TODO(PARAM): consider applying a multiplier on the penalty +// The penalty for optimistically proving a sector with an invalid window PoSt. func PledgePenaltyForInvalidWindowPoSt(rewardEstimate, networkQAPowerEstimate smoothing.FilterEstimate, qaSectorPower abi.StoragePower) abi.TokenAmount { - return PledgePenaltyForContinuedFault(rewardEstimate, networkQAPowerEstimate, qaSectorPower) + return ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate, qaSectorPower, InvalidWindowPoStProjectionPeriod) } // Computes the PreCommit deposit given sector qa weight and current network conditions. From 7d3f862f61fa37c38d795d2aaec686f679d8f069 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 15:36:11 -0800 Subject: [PATCH 47/61] add logic to calculate and pay out rewards Currently, the payout is zero. However, the logic now exists and we can discuss the ramifications. --- actors/builtin/miner/miner_actor.go | 39 ++++++++++++++++++++++++++--- actors/builtin/miner/miner_test.go | 15 +++++++---- actors/builtin/miner/policy.go | 7 ++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index d61623a27..e071260e9 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -482,6 +482,7 @@ type DisputeWindowedPoStParams struct { func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams) *abi.EmptyValue { rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) + reporter := rt.Caller() if params.Deadline >= WPoStPeriodDeadlines { rt.Abortf(exitcode.ErrIllegalArgument, "invalid deadline %d of %d", params.Deadline, WPoStPeriodDeadlines) @@ -494,6 +495,7 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams pwrTotal := requestCurrentTotalPower(rt) toBurn := abi.NewTokenAmount(0) + toReward := abi.NewTokenAmount(0) pledgeDelta := abi.NewTokenAmount(0) powerDelta := NewPowerPairZero() var st State @@ -567,24 +569,55 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams // Penalties. { - penaltyTarget := PledgePenaltyForInvalidWindowPoSt( + // Calculate the base penalty. + penaltyBase := PledgePenaltyForInvalidWindowPoSt( epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, disputedPower.QA, ) - err := st.ApplyPenalty(penaltyTarget) + // Calculate the target reward. + postProofType, err := info.SealProofType.RegisteredWindowPoStProof() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to determine post proof type") + rewardTarget := RewardForDisputedWindowPoSt(postProofType, disputedPower) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to compute reward for disputed window post") + + // Compute the target penalty by adding the + // base penalty to the target reward. We don't + // take reward out of the penalty as the miner + // could end up receiving a substantial + // portion of their fee back as a reward. + penaltyTarget := big.Add(penaltyBase, rewardTarget) + + err = st.ApplyPenalty(penaltyTarget) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to apply penalty") penaltyFromVesting, penaltyFromBalance, err := st.RepayPartialDebtInPriorityOrder(store, currEpoch, rt.CurrentBalance()) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to pay debt") toBurn = big.Add(penaltyFromVesting, penaltyFromBalance) + + // Now, move as much of the target reward as + // we can from the burn to the reward. + toReward = big.Min(toBurn, rewardTarget) + toBurn = big.Sub(toBurn, toReward) + pledgeDelta = penaltyFromVesting.Neg() } }) requestUpdatePower(rt, powerDelta) - notifyPledgeChanged(rt, pledgeDelta) + + if !toReward.IsZero() { + // Try to send the reward to the reporter. + code := rt.Send(reporter, builtin.MethodSend, nil, toReward, &builtin.Discard{}) + + // If we fail, log and burn the reward to make sure the balances remain correct. + if !code.IsSuccess() { + rt.Log(rtt.ERROR, "failed to send reward") + toBurn = big.Add(toBurn, toReward) + } + } burnFunds(rt, toBurn) + notifyPledgeChanged(rt, pledgeDelta) rt.StateReadonly(&st) err := st.CheckBalanceInvariants(rt.CurrentBalance()) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 4ed6b3ef1..76630e9c8 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1928,6 +1928,7 @@ func TestWindowPost(t *testing.T) { result = &poStDisputeResult{ expectedPowerDelta: pwr.Neg(), expectedPenalty: expectedFee, + expectedReward: big.Zero(), expectedPledgeDelta: big.Zero(), } } @@ -5199,6 +5200,7 @@ type poStDisputeResult struct { expectedPowerDelta miner.PowerPair expectedPledgeDelta abi.TokenAmount expectedPenalty abi.TokenAmount + expectedReward abi.TokenAmount } func (h *actorHarness) disputeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, proofIndex uint64, infos []*miner.SectorOnChainInfo, expectSuccess *poStDisputeResult) { @@ -5302,16 +5304,19 @@ func (h *actorHarness) disputeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdateClaimedPower, claim, abi.NewTokenAmount(0), nil, exitcode.Ok) } - // expect pledge update - if !expectSuccess.expectedPledgeDelta.IsZero() { - rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdatePledgeTotal, - &expectSuccess.expectedPledgeDelta, abi.NewTokenAmount(0), nil, exitcode.Ok) + // expect reward + if !expectSuccess.expectedReward.IsZero() { + rt.ExpectSend(h.worker, builtin.MethodSend, nil, expectSuccess.expectedReward, nil, exitcode.Ok) } - // expect penalty if !expectSuccess.expectedPenalty.IsZero() { rt.ExpectSend(builtin.BurntFundsActorAddr, builtin.MethodSend, nil, expectSuccess.expectedPenalty, nil, exitcode.Ok) } + // expect pledge update + if !expectSuccess.expectedPledgeDelta.IsZero() { + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.UpdatePledgeTotal, + &expectSuccess.expectedPledgeDelta, abi.NewTokenAmount(0), nil, exitcode.Ok) + } } params := miner.DisputeWindowedPoStParams{ diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index fe228b6aa..1c82a509d 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -345,3 +345,10 @@ func RewardForConsensusSlashReport(elapsedEpoch abi.ChainEpoch, collateral abi.T return big.Min(big.Div(num, denom), big.Div(big.Mul(collateral, consensusFaultMaxReporterShare.Numerator), consensusFaultMaxReporterShare.Denominator)) } + +// The reward given for successfully disputing a window post. +func RewardForDisputedWindowPoSt(proofType abi.RegisteredPoStProof, disputedPower PowerPair) abi.TokenAmount { + // This is currently zero but may be raised at some point to + // ensure that disputing proofs rational. + return big.Zero() +} From b442acee8d885db2df6e6e93d3a9915cbeb07a4b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 15:42:36 -0800 Subject: [PATCH 48/61] remove todo --- actors/builtin/miner/miner_actor.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index e071260e9..9b0c88a51 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -622,8 +622,6 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams err := st.CheckBalanceInvariants(rt.CurrentBalance()) builtin.RequireNoErr(rt, err, ErrBalanceInvariantBroken, "balance invariants broken") - - // TODO: Pay submitter some fee. return nil } From bcdb6434f92be7fae83e6a371a5819ca61c706ed Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 16:02:09 -0800 Subject: [PATCH 49/61] remove TODO --- actors/builtin/miner/miner_actor.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 9b0c88a51..5f3e64176 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -490,7 +490,11 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams currEpoch := rt.CurrEpoch() - // TODO: maybe stash this in the deadline for better accuracy? + // Note: these are going to be slightly inaccurate as time + // will have moved on from when the post was actually + // submitted. + // + // However, these are estimates _anyways_. epochReward := requestCurrentEpochBlockReward(rt) pwrTotal := requestCurrentTotalPower(rt) From bdd4a6115e1c5bd570b20aedc0f23a4e4ffb792b Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 16:43:26 -0800 Subject: [PATCH 50/61] disallow dispute before proving period start --- actors/builtin/miner/deadlines.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actors/builtin/miner/deadlines.go b/actors/builtin/miner/deadlines.go index 1adfbf75e..26b875a37 100644 --- a/actors/builtin/miner/deadlines.go +++ b/actors/builtin/miner/deadlines.go @@ -74,6 +74,10 @@ func deadlineIsMutable(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentE // 1. Optimistic PoSts may not be disputed while the challenge window is open. // 2. Optimistic PoSts may not be disputed after the miner could have compacted the deadline. func deadlineAvailableForOptimisticPoStDispute(provingPeriodStart abi.ChainEpoch, dlIdx uint64, currentEpoch abi.ChainEpoch) bool { + if provingPeriodStart > currentEpoch { + // We haven't started proving yet, there's nothing to dispute. + return false + } dlInfo := NewDeadlineInfo(provingPeriodStart, dlIdx, currentEpoch).NextNotElapsed() return !dlInfo.IsOpen() && currentEpoch < (dlInfo.Close-WPoStProvingPeriod)+WPoStDisputeWindow From bca44ac1f7ba26d81f6a892da194cb5fc2fb7337 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 18:27:06 -0800 Subject: [PATCH 51/61] remove another todo --- actors/builtin/miner/miner_state.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actors/builtin/miner/miner_state.go b/actors/builtin/miner/miner_state.go index 80bc893bf..1164af708 100644 --- a/actors/builtin/miner/miner_state.go +++ b/actors/builtin/miner/miner_state.go @@ -1129,9 +1129,8 @@ func (st *State) AdvanceDeadline(store adt.Store, currEpoch abi.ChainEpoch) (*Ad // No live sectors in this deadline, nothing to do. if deadline.LiveSectors == 0 { - // TODO: do we still need to clear the post submissions here? I - // think we're technically fine, but this is a strange - // edge-case. + // We should do some more checks here. See: + // Fix: https://github.com/filecoin-project/specs-actors/issues/1348 return &AdvanceDeadlineResult{ pledgeDelta, powerDelta, From 186d898154177c9cb9d101113af5340bdbe1553c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 18:32:22 -0800 Subject: [PATCH 52/61] make sure we don't record proofs that restore power These need to be checked up-front. --- actors/builtin/miner/miner_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 76630e9c8..3116b8b8e 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -2124,6 +2124,15 @@ func TestWindowPost(t *testing.T) { assertBitfieldEmpty(t, partition.Faults) assertBitfieldEmpty(t, partition.Recoveries) + // We restored power, so we should not have recorded a post. + deadline = actor.getDeadline(rt, dlIdx) + assertBitfieldEquals(t, deadline.PartitionsPoSted, pIdx) + postsCid := deadline.OptimisticPoStSubmissions + posts, err := adt.AsArray(rt.AdtStore(), postsCid, + miner.DeadlineOptimisticPoStSubmissionsAmtBitwidth) + require.NoError(t, err) + require.EqualValues(t, posts.Length(), 0) + // Next deadline cron does not charge for the fault advanceDeadline(rt, actor, &cronConfig{}) From 3d2f31a1fff0c7ccea0fa199c00505c3c5389ed3 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 20:23:40 -0800 Subject: [PATCH 53/61] add tests for end of dispute window --- actors/builtin/miner/miner_test.go | 115 ++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 3116b8b8e..2a44b244d 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1928,7 +1928,7 @@ func TestWindowPost(t *testing.T) { result = &poStDisputeResult{ expectedPowerDelta: pwr.Neg(), expectedPenalty: expectedFee, - expectedReward: big.Zero(), + expectedReward: big.Zero(), expectedPledgeDelta: big.Zero(), } } @@ -2327,6 +2327,119 @@ func TestWindowPost(t *testing.T) { }) actor.checkState(rt) }) + + t.Run("cannot dispute posts when the challenge window is open", func(t *testing.T) { + proofs := makePoStProofs(actor.postProofType) + + rt := builder.Build(t) + actor.constructAndVerify(rt) + store := rt.AdtStore() + sector := actor.commitAndProveSectors(rt, 1, defaultSectorExpiration, nil)[0] + pwr := miner.PowerForSector(actor.sectorSize, sector) + + st := getState(rt) + dlIdx, pIdx, err := st.FindSector(store, sector.SectorNumber) + require.NoError(t, err) + + // Skip over deadlines until the beginning of the one with the new sector + dlinfo := actor.deadline(rt) + for dlinfo.Index != dlIdx { + dlinfo = advanceDeadline(rt, actor, &cronConfig{}) + } + + // Submit PoSt + partitions := []miner.PoStPartition{ + {Index: pIdx, Skipped: bitfield.New()}, + } + actor.submitWindowPoStRaw(rt, dlinfo, partitions, []*miner.SectorOnChainInfo{sector}, proofs, &poStConfig{ + expectedPowerDelta: pwr, + }) + + // Dispute it. + params := miner.DisputeWindowedPoStParams{ + Deadline: dlinfo.Index, + PoStIndex: 0, + } + + rt.SetCaller(actor.worker, builtin.AccountActorCodeID) + rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) + + currentReward := reward.ThisEpochRewardReturn{ + ThisEpochBaselinePower: actor.baselinePower, + ThisEpochRewardSmoothed: actor.epochRewardSmooth, + } + rt.ExpectSend(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), ¤tReward, exitcode.Ok) + + networkPower := big.NewIntUnsigned(1 << 50) + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), + &power.CurrentTotalPowerReturn{ + RawBytePower: networkPower, + QualityAdjPower: networkPower, + PledgeCollateral: actor.networkPledge, + QualityAdjPowerSmoothed: actor.epochQAPowerSmooth, + }, + exitcode.Ok) + + rt.ExpectAbortContainsMessage(exitcode.ErrForbidden, "can only dispute window posts during the dispute window", func() { + rt.Call(actor.a.DisputeWindowedPoSt, ¶ms) + }) + rt.Verify() + }) + t.Run("can dispute up till window end, but not after", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + store := rt.AdtStore() + sector := actor.commitAndProveSectors(rt, 1, defaultSectorExpiration, nil)[0] + + st := getState(rt) + dlIdx, _, err := st.FindSector(store, sector.SectorNumber) + require.NoError(t, err) + + nextDl := miner.NewDeadlineInfo(st.ProvingPeriodStart, dlIdx, rt.Epoch()). + NextNotElapsed() + + advanceAndSubmitPoSts(rt, actor, sector) + + windowEnd := nextDl.Close + miner.WPoStDisputeWindow + + // first, try to dispute right before the window end. + // We expect this to fail "normally" (fail to disprove). + rt.SetEpoch(windowEnd-1) + actor.disputeWindowPoSt(rt, nextDl, 0, []*miner.SectorOnChainInfo{sector}, nil) + + // Now set the epoch at the window end. We expect a different error. + rt.SetEpoch(windowEnd) + + // Now try to dispute. + params := miner.DisputeWindowedPoStParams{ + Deadline: dlIdx, + PoStIndex: 0, + } + + rt.SetCaller(actor.worker, builtin.AccountActorCodeID) + rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) + + currentReward := reward.ThisEpochRewardReturn{ + ThisEpochBaselinePower: actor.baselinePower, + ThisEpochRewardSmoothed: actor.epochRewardSmooth, + } + rt.ExpectSend(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), ¤tReward, exitcode.Ok) + + networkPower := big.NewIntUnsigned(1 << 50) + rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), + &power.CurrentTotalPowerReturn{ + RawBytePower: networkPower, + QualityAdjPower: networkPower, + PledgeCollateral: actor.networkPledge, + QualityAdjPowerSmoothed: actor.epochQAPowerSmooth, + }, + exitcode.Ok) + + rt.ExpectAbortContainsMessage(exitcode.ErrForbidden, "can only dispute window posts during the dispute window", func() { + rt.Call(actor.a.DisputeWindowedPoSt, ¶ms) + }) + rt.Verify() + }) } func TestProveCommit(t *testing.T) { From d8c1378f7a0ebec5b3812d07e8916c0e516787a9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 11 Jan 2021 22:29:43 -0800 Subject: [PATCH 54/61] test terminating sectors while proving --- actors/builtin/miner/miner_test.go | 71 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 2a44b244d..c5a8a4f00 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -2357,28 +2357,14 @@ func TestWindowPost(t *testing.T) { // Dispute it. params := miner.DisputeWindowedPoStParams{ - Deadline: dlinfo.Index, + Deadline: dlinfo.Index, PoStIndex: 0, } rt.SetCaller(actor.worker, builtin.AccountActorCodeID) rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) - currentReward := reward.ThisEpochRewardReturn{ - ThisEpochBaselinePower: actor.baselinePower, - ThisEpochRewardSmoothed: actor.epochRewardSmooth, - } - rt.ExpectSend(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), ¤tReward, exitcode.Ok) - - networkPower := big.NewIntUnsigned(1 << 50) - rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), - &power.CurrentTotalPowerReturn{ - RawBytePower: networkPower, - QualityAdjPower: networkPower, - PledgeCollateral: actor.networkPledge, - QualityAdjPowerSmoothed: actor.epochQAPowerSmooth, - }, - exitcode.Ok) + expectQueryNetworkInfo(rt, actor) rt.ExpectAbortContainsMessage(exitcode.ErrForbidden, "can only dispute window posts during the dispute window", func() { rt.Call(actor.a.DisputeWindowedPoSt, ¶ms) @@ -2404,7 +2390,7 @@ func TestWindowPost(t *testing.T) { // first, try to dispute right before the window end. // We expect this to fail "normally" (fail to disprove). - rt.SetEpoch(windowEnd-1) + rt.SetEpoch(windowEnd - 1) actor.disputeWindowPoSt(rt, nextDl, 0, []*miner.SectorOnChainInfo{sector}, nil) // Now set the epoch at the window end. We expect a different error. @@ -2412,7 +2398,7 @@ func TestWindowPost(t *testing.T) { // Now try to dispute. params := miner.DisputeWindowedPoStParams{ - Deadline: dlIdx, + Deadline: dlIdx, PoStIndex: 0, } @@ -3349,6 +3335,38 @@ func TestTerminateSectors(t *testing.T) { actor.terminateSectors(rt, sectors, expectedFee) actor.checkState(rt) }) + + t.Run("cannot terminate a sector when the challenge window is open", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + rt.SetEpoch(abi.ChainEpoch(1)) + sectorInfo := actor.commitAndProveSectors(rt, 1, defaultSectorExpiration, nil) + sector := sectorInfo[0] + + st := getState(rt) + dlIdx, pIdx, err := st.FindSector(rt.AdtStore(), sector.SectorNumber) + require.NoError(t, err) + + // advance into the deadline, but not past it. + dlinfo := actor.deadline(rt) + for dlinfo.Index != dlIdx { + dlinfo = advanceDeadline(rt, actor, &cronConfig{}) + } + + params := &miner.TerminateSectorsParams{Terminations: []miner.TerminationDeclaration{{ + Deadline: dlIdx, + Partition: pIdx, + Sectors: bf(uint64(sector.SectorNumber)), + }}} + rt.SetCaller(actor.worker, builtin.AccountActorCodeID) + rt.ExpectValidateCallerAddr(append(actor.controlAddrs, actor.owner, actor.worker)...) + rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "cannot terminate sectors in immutable deadline", func() { + rt.Call(actor.a.TerminateSectors, params) + }) + + actor.checkState(rt) + }) + } func TestWithdrawBalance(t *testing.T) { @@ -5329,22 +5347,7 @@ func (h *actorHarness) disputeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, rt.SetCaller(h.worker, builtin.AccountActorCodeID) rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) - currentReward := reward.ThisEpochRewardReturn{ - ThisEpochBaselinePower: h.baselinePower, - ThisEpochRewardSmoothed: h.epochRewardSmooth, - } - rt.ExpectSend(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), ¤tReward, exitcode.Ok) - - networkPower := big.NewIntUnsigned(1 << 50) - rt.ExpectSend(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), - &power.CurrentTotalPowerReturn{ - RawBytePower: networkPower, - QualityAdjPower: networkPower, - PledgeCollateral: h.networkPledge, - QualityAdjPowerSmoothed: h.epochQAPowerSmooth, - }, - exitcode.Ok) - + expectQueryNetworkInfo(rt, h) challengeRand := abi.SealRandomness([]byte{10, 11, 12, 13}) // only sectors that are not skipped and not existing non-recovered faults will be verified From 5de41bd72f2724fab2ad4bca9be6b185d2e09288 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 12 Jan 2021 09:27:16 -0800 Subject: [PATCH 55/61] add note about partitions posted bitfield --- actors/builtin/miner/deadline_state.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 3ff87bef9..5a2a15952 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -43,6 +43,10 @@ type Deadline struct { // Partitions that have been proved by window PoSts so far during the // current challenge window. + // NOTE: This bitfield includes both partitions whose proofs + // were optimistically accepted and stored in + // OptimisticPoStSubmissions, and those whose proofs were + // verified on-chain. PartitionsPoSted bitfield.BitField // Partitions with sectors that terminated early. From e70433b58de4a599a0c145a09c428e7a059e8708 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 12 Jan 2021 09:27:49 -0800 Subject: [PATCH 56/61] assert additional deadline invariant --- actors/builtin/miner/deadline_state.go | 23 ++++++++++++++++++++--- actors/builtin/miner/testing.go | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 5a2a15952..dfba5cc3f 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -193,10 +193,27 @@ func (d *Deadline) PartitionsArray(store adt.Store) (*adt.Array, error) { return arr, nil } +func (d *Deadline) OptimisticProofsArray(store adt.Store) (*adt.Array, error) { + arr, err := adt.AsArray(store, d.OptimisticPoStSubmissions, DeadlineOptimisticPoStSubmissionsAmtBitwidth) + if err != nil { + return nil, xerrors.Errorf("failed to load proofs: %w", err) + } + return arr, nil +} + func (d *Deadline) PartitionsSnapshotArray(store adt.Store) (*adt.Array, error) { arr, err := adt.AsArray(store, d.PartitionsSnapshot, DeadlinePartitionsAmtBitwidth) if err != nil { - return nil, xerrors.Errorf("failed to load partitions: %w", err) + return nil, xerrors.Errorf("failed to load partitions snapshot: %w", err) + } + return arr, nil +} + + +func (d *Deadline) OptimisticProofsSnapshotArray(store adt.Store) (*adt.Array, error) { + arr, err := adt.AsArray(store, d.OptimisticPoStSubmissionsSnapshot, DeadlineOptimisticPoStSubmissionsAmtBitwidth) + if err != nil { + return nil, xerrors.Errorf("failed to load proofs snapshot: %w", err) } return arr, nil } @@ -1096,7 +1113,7 @@ func (dl *Deadline) RecordProvenSectors( // RecordPoStProofs records a set of optimistically accepted PoSt proofs // (usually one), associating them with the given partitions. func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitField, proofs []proof.PoStProof) error { - proofArr, err := adt.AsArray(store, dl.OptimisticPoStSubmissions, DeadlineOptimisticPoStSubmissionsAmtBitwidth) + proofArr, err := dl.OptimisticProofsArray(store) if err != nil { return xerrors.Errorf("failed to load proofs: %w", err) } @@ -1120,7 +1137,7 @@ func (dl *Deadline) RecordPoStProofs(store adt.Store, partitions bitfield.BitFie // associated partitions. This method takes the PoSt from the PoSt submissions // snapshot. func (dl *Deadline) TakePoStProofs(store adt.Store, idx uint64) (partitions bitfield.BitField, proofs []proof.PoStProof, err error) { - proofArr, err := adt.AsArray(store, dl.OptimisticPoStSubmissionsSnapshot, DeadlineOptimisticPoStSubmissionsAmtBitwidth) + proofArr, err := dl.OptimisticProofsSnapshotArray(store) if err != nil { return bitfield.New(), nil, xerrors.Errorf("failed to load proofs: %w", err) } diff --git a/actors/builtin/miner/testing.go b/actors/builtin/miner/testing.go index c067f4a99..5dc51d5be 100644 --- a/actors/builtin/miner/testing.go +++ b/actors/builtin/miner/testing.go @@ -226,6 +226,22 @@ func CheckDeadlineStateInvariants(deadline *Deadline, store adt.Store, quant Qua }) acc.RequireNoError(err, "error iterating partitions snapshot") + // Check that we don't have any proofs proving partitions that are not in the snapshot. + proofsSnapshot, err := deadline.OptimisticProofsSnapshotArray(store) + acc.RequireNoError(err, "error loading proofs snapshot") + var proof WindowedPoSt + err = proofsSnapshot.ForEach(&proof, func(_ int64) error { + err = proof.Partitions.ForEach(func(i uint64) error { + found, err := partitionsSnapshot.Get(i, &partition) + acc.RequireNoError(err, "error loading partition snapshot") + acc.Require(found, "failed to find partition for recorded proof in the snapshot") + return nil + }) + acc.RequireNoError(err, "error iterating proof partitions bitfield") + return nil + }) + acc.RequireNoError(err, "error iterating proofs snapshot") + // Check memoized sector and power values. live, err := bitfield.MultiMerge(allLiveSectors...) if err != nil { From 6bf5bb4bf1939e3b4653b21b54b0caf126210891 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 12 Jan 2021 09:44:18 -0800 Subject: [PATCH 57/61] remove stray comment --- actors/builtin/miner/miner_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index c5a8a4f00..d8bde3e6b 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -5390,7 +5390,6 @@ func (h *actorHarness) disputeWindowPoSt(rt *mock.Runtime, deadline *dline.Info, actorId, err := addr.IDFromAddress(h.receiver) require.NoError(h.t, err) - // if not all sectors are skipped proofInfos := make([]proof.SectorInfo, len(infos)) for i, ci := range infos { si := ci From 9bd5e2714c8eee6b051557f59737e309b86b9fcf Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 12 Jan 2021 17:26:28 -0800 Subject: [PATCH 58/61] address some more review comments --- actors/builtin/miner/deadline_state.go | 3 +++ actors/builtin/miner/miner_actor.go | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index dfba5cc3f..66669c4dc 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -1148,6 +1148,9 @@ func (dl *Deadline) TakePoStProofs(store adt.Store, idx uint64) (partitions bitf } else if !found { return bitfield.New(), nil, xc.ErrIllegalArgument.Wrapf("proof %d not found", idx) } + + // Delete the proof from the proofs array, leaving a hole. + // This will not affect concurrent attempts to refute other proofs. err = proofArr.Delete(idx) if err != nil { return bitfield.New(), nil, xerrors.Errorf("failed to delete proof %d: %w", idx, err) diff --git a/actors/builtin/miner/miner_actor.go b/actors/builtin/miner/miner_actor.go index 5f3e64176..d7b8c8867 100644 --- a/actors/builtin/miner/miner_actor.go +++ b/actors/builtin/miner/miner_actor.go @@ -509,7 +509,7 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams } info := getMinerInfo(rt, &st) - disputedPower := NewPowerPairZero() + penalisedPower := NewPowerPairZero() store := adt.AsStore(rt) // Check proof @@ -538,7 +538,9 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams // Load the partition info we need for the dispute. disputeInfo, err := dlCurrent.LoadPartitionsForDispute(store, partitions) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load partition info for dispute") - disputedPower = disputeInfo.DisputedPower + // This includes power that is no longer active (e.g., due to sector terminations). + // It must only be used for penalty calculations, not power adjustments. + penalisedPower = disputeInfo.DisputedPower // Load sectors for the dispute. sectors, err := LoadSectors(store, st.Sectors) @@ -577,13 +579,13 @@ func (a Actor) DisputeWindowedPoSt(rt Runtime, params *DisputeWindowedPoStParams penaltyBase := PledgePenaltyForInvalidWindowPoSt( epochReward.ThisEpochRewardSmoothed, pwrTotal.QualityAdjPowerSmoothed, - disputedPower.QA, + penalisedPower.QA, ) // Calculate the target reward. postProofType, err := info.SealProofType.RegisteredWindowPoStProof() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to determine post proof type") - rewardTarget := RewardForDisputedWindowPoSt(postProofType, disputedPower) + rewardTarget := RewardForDisputedWindowPoSt(postProofType, penalisedPower) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to compute reward for disputed window post") // Compute the target penalty by adding the From 9ca6d38bfc6615cd4148a067b480d387337925d9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 12 Jan 2021 18:33:29 -0800 Subject: [PATCH 59/61] test some additional dispute cases --- actors/builtin/miner/miner_test.go | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index d8bde3e6b..1166d28f9 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -2426,6 +2426,81 @@ func TestWindowPost(t *testing.T) { }) rt.Verify() }) + + t.Run("can't dispute up with an invalid deadline", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + params := miner.DisputeWindowedPoStParams{ + Deadline: 50, + PoStIndex: 0, + } + + rt.SetCaller(actor.worker, builtin.AccountActorCodeID) + rt.ExpectValidateCallerType(builtin.CallerTypesSignable...) + + rt.ExpectAbortContainsMessage(exitcode.ErrIllegalArgument, "invalid deadline", func() { + rt.Call(actor.a.DisputeWindowedPoSt, ¶ms) + }) + rt.Verify() + }) + + t.Run("can dispute test after proving period changes", func(t *testing.T) { + rt := builder.Build(t) + actor.constructAndVerify(rt) + + periodStart := actor.deadline(rt).NextPeriodStart() + + // go to the next deadline 0 + rt.SetEpoch(periodStart) + + // fill one partition in each mutable deadline. + numSectors := int(actor.partitionSize*(miner.WPoStPeriodDeadlines-2)) + + // creates a partition in every deadline except 0 and 47 + sectors := actor.commitAndProveSectors(rt, numSectors, defaultSectorExpiration, nil) + actor.t.Log("here") + + // prove every sector once to activate power. This + // simplifies the test a bit. + advanceAndSubmitPoSts(rt, actor, sectors...) + + // Make sure we're in the correct deadline. We should + // finish at deadline 2 because precommit takes some + // time. + dlinfo := actor.deadline(rt) + require.True(t, dlinfo.Index < 46, + "we need to be before the target deadline for this test to make sense") + + // Now challenge find the sectors in the last partition. + _, partition := actor.getDeadlineAndPartition(rt, 46, 0) + var targetSectors []*miner.SectorOnChainInfo + err := partition.Sectors.ForEach(func(i uint64) error { + for _, sector := range sectors { + if uint64(sector.SectorNumber) == i { + targetSectors = append(targetSectors, sector) + } + } + return nil + }) + require.NoError(t, err) + require.NotEmpty(t, targetSectors) + + pwr := miner.PowerForSectors(actor.sectorSize, targetSectors) + + // And challenge the last partition. + var result *poStDisputeResult + expectedFee := miner.PledgePenaltyForInvalidWindowPoSt(actor.epochRewardSmooth, actor.epochQAPowerSmooth, pwr.QA) + result = &poStDisputeResult{ + expectedPowerDelta: pwr.Neg(), + expectedPenalty: expectedFee, + expectedReward: big.Zero(), + expectedPledgeDelta: big.Zero(), + } + + targetDlInfo := miner.NewDeadlineInfo(periodStart, 46, rt.Epoch()) + actor.disputeWindowPoSt(rt, targetDlInfo, 0, targetSectors, result) + }) } func TestProveCommit(t *testing.T) { From d07f47f23d43b5ef508b6e05dbda714fe5efddf8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 14 Jan 2021 12:46:04 -0800 Subject: [PATCH 60/61] implement reward and add a base penalty * The reward ensures that, at a minimum, the gas fees for disputing a proof is covered. * The base penalty ensures that the dispute method cannot be used to "take an advance" on vesting rewards. --- actors/builtin/miner/miner_test.go | 4 ++-- actors/builtin/miner/monies.go | 10 +++++++++- actors/builtin/miner/policy.go | 5 ++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/actors/builtin/miner/miner_test.go b/actors/builtin/miner/miner_test.go index 1166d28f9..b9a2b5775 100644 --- a/actors/builtin/miner/miner_test.go +++ b/actors/builtin/miner/miner_test.go @@ -1928,7 +1928,7 @@ func TestWindowPost(t *testing.T) { result = &poStDisputeResult{ expectedPowerDelta: pwr.Neg(), expectedPenalty: expectedFee, - expectedReward: big.Zero(), + expectedReward: miner.BaseRewardForDisputedWindowPoSt, expectedPledgeDelta: big.Zero(), } } @@ -2494,7 +2494,7 @@ func TestWindowPost(t *testing.T) { result = &poStDisputeResult{ expectedPowerDelta: pwr.Neg(), expectedPenalty: expectedFee, - expectedReward: big.Zero(), + expectedReward: miner.BaseRewardForDisputedWindowPoSt, expectedPledgeDelta: big.Zero(), } diff --git a/actors/builtin/miner/monies.go b/actors/builtin/miner/monies.go index 25fd58540..7f61c2af1 100644 --- a/actors/builtin/miner/monies.go +++ b/actors/builtin/miner/monies.go @@ -62,6 +62,11 @@ const ConsensusFaultFactor = 5 var LockedRewardFactorNum = big.NewInt(75) var LockedRewardFactorDenom = big.NewInt(100) +// Base reward for successfully disputing a window posts proofs. +var BaseRewardForDisputedWindowPoSt = big.Mul(big.NewInt(4), big.NewInt(1e10)) // PARAM_SPEC +// Base penalty for a successful disputed window post proof. +var BasePenaltyForDisputedWindowPoSt = big.Mul(big.NewInt(20), big.NewInt(1e10)) // PARAM_SPEC + // The projected block reward a sector would earn over some period. // Also known as "BR(t)". // BR(t) = ProjectedRewardFraction(t) * SectorQualityAdjustedPower @@ -123,7 +128,10 @@ func PledgePenaltyForTermination(dayReward abi.TokenAmount, sectorAge abi.ChainE // The penalty for optimistically proving a sector with an invalid window PoSt. func PledgePenaltyForInvalidWindowPoSt(rewardEstimate, networkQAPowerEstimate smoothing.FilterEstimate, qaSectorPower abi.StoragePower) abi.TokenAmount { - return ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate, qaSectorPower, InvalidWindowPoStProjectionPeriod) + return big.Add( + ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate, qaSectorPower, InvalidWindowPoStProjectionPeriod), + BasePenaltyForDisputedWindowPoSt, + ) } // Computes the PreCommit deposit given sector qa weight and current network conditions. diff --git a/actors/builtin/miner/policy.go b/actors/builtin/miner/policy.go index 1c82a509d..cca3c1025 100644 --- a/actors/builtin/miner/policy.go +++ b/actors/builtin/miner/policy.go @@ -348,7 +348,6 @@ func RewardForConsensusSlashReport(elapsedEpoch abi.ChainEpoch, collateral abi.T // The reward given for successfully disputing a window post. func RewardForDisputedWindowPoSt(proofType abi.RegisteredPoStProof, disputedPower PowerPair) abi.TokenAmount { - // This is currently zero but may be raised at some point to - // ensure that disputing proofs rational. - return big.Zero() + // This is currently just the base. In the future, the fee may scale based on the disputed power. + return BaseRewardForDisputedWindowPoSt } From 09d2b5849e0ad9f2131aa97fe2e0cc7bee3f775c Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 14 Jan 2021 14:27:37 -0800 Subject: [PATCH 61/61] fix fee base --- actors/builtin/miner/monies.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actors/builtin/miner/monies.go b/actors/builtin/miner/monies.go index 7f61c2af1..1f00a668b 100644 --- a/actors/builtin/miner/monies.go +++ b/actors/builtin/miner/monies.go @@ -63,9 +63,9 @@ var LockedRewardFactorNum = big.NewInt(75) var LockedRewardFactorDenom = big.NewInt(100) // Base reward for successfully disputing a window posts proofs. -var BaseRewardForDisputedWindowPoSt = big.Mul(big.NewInt(4), big.NewInt(1e10)) // PARAM_SPEC +var BaseRewardForDisputedWindowPoSt = big.Mul(big.NewInt(4), builtin.TokenPrecision) // PARAM_SPEC // Base penalty for a successful disputed window post proof. -var BasePenaltyForDisputedWindowPoSt = big.Mul(big.NewInt(20), big.NewInt(1e10)) // PARAM_SPEC +var BasePenaltyForDisputedWindowPoSt = big.Mul(big.NewInt(20), builtin.TokenPrecision) // PARAM_SPEC // The projected block reward a sector would earn over some period. // Also known as "BR(t)".