diff --git a/chain/actors/actor_miner.go b/chain/actors/actor_miner.go index a55010669..e5e8b3b1e 100644 --- a/chain/actors/actor_miner.go +++ b/chain/actors/actor_miner.go @@ -467,14 +467,21 @@ func (sma StorageMinerActor) SubmitFallbackPoSt(act *types.Actor, vmctx types.VM return nil, aerrors.HandleExternalError(lerr, "could not load proving set node") } - faults, nerr := self.FaultSet.AllMap() + ss, lerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Sectors) + if lerr != nil { + return nil, aerrors.HandleExternalError(lerr, "could not load proving set node") + } + + faults, nerr := self.FaultSet.AllMap(2 * ss.Count) if nerr != nil { return nil, aerrors.Absorb(err, 5, "RLE+ invalid") } + activeFaults := uint64(0) var sectorInfos []ffi.PublicSectorInfo if err := pss.ForEach(func(id uint64, v *cbg.Deferred) error { if faults[id] { + activeFaults++ return nil } @@ -512,7 +519,7 @@ func (sma StorageMinerActor) SubmitFallbackPoSt(act *types.Actor, vmctx types.VM } if ok, lerr := sectorbuilder.VerifyFallbackPost(vmctx.Context(), mi.SectorSize, - sectorbuilder.NewSortedPublicSectorInfo(sectorInfos), seed[:], params.Proof, candidates, proverID, len(faults)); !ok || lerr != nil { + sectorbuilder.NewSortedPublicSectorInfo(sectorInfos), seed[:], params.Proof, candidates, proverID, activeFaults); !ok || lerr != nil { if lerr != nil { // TODO: study PoST errors return nil, aerrors.Absorb(lerr, 4, "PoST error") @@ -523,7 +530,7 @@ func (sma StorageMinerActor) SubmitFallbackPoSt(act *types.Actor, vmctx types.VM } // Post submission is successful! - if err := onSuccessfulPoSt(self, vmctx); err != nil { + if err := onSuccessfulPoSt(self, vmctx, activeFaults); err != nil { return nil, err } @@ -834,6 +841,20 @@ func (sma StorageMinerActor) DeclareFaults(act *types.Actor, vmctx types.VMConte return nil, aerrors.Absorb(err, 1, "failed to merge bitfields") } + ss, nerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Sectors) + if nerr != nil { + return nil, aerrors.HandleExternalError(nerr, "failed to load sector set") + } + + cf, nerr := nfaults.Count() + if nerr != nil { + return nil, aerrors.Absorb(nerr, 2, "could not decode RLE+") + } + + if cf > 2*ss.Count { + return nil, aerrors.Newf(3, "too many declared faults: %d > %d", cf, 2*ss.Count) + } + self.FaultSet = nfaults self.LastFaultSubmission = vmctx.BlockHeight() @@ -909,7 +930,41 @@ func (sma StorageMinerActor) SubmitElectionPoSt(act *types.Actor, vmctx types.VM return nil, aerrors.New(1, "slashed miners can't perform election PoSt") } - if err := onSuccessfulPoSt(self, vmctx); err != nil { + pss, nerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.ProvingSet) + if nerr != nil { + return nil, aerrors.HandleExternalError(nerr, "failed to load proving set") + } + + ss, nerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Sectors) + if nerr != nil { + return nil, aerrors.HandleExternalError(nerr, "failed to load proving set") + } + + faults, nerr := self.FaultSet.AllMap(2 * ss.Count) + if nerr != nil { + return nil, aerrors.Absorb(nerr, 1, "invalid bitfield (fatal?)") + } + + activeFaults := uint64(0) + for f := range faults { + if f > amt.MaxIndex { + continue + } + + var comms [][]byte + err := pss.Get(f, &comms) + if err != nil { + var notfound *amt.ErrNotFound + if !xerrors.As(err, ¬found) { + return nil, aerrors.HandleExternalError(err, "failed to find sector in sector set") + } + continue + } + + activeFaults++ + } + + if err := onSuccessfulPoSt(self, vmctx, activeFaults); err != nil { // TODO return nil, err } @@ -924,7 +979,7 @@ func (sma StorageMinerActor) SubmitElectionPoSt(act *types.Actor, vmctx types.VM return nil, nil } -func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext) aerrors.ActorError { +func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext, activeFaults uint64) aerrors.ActorError { // TODO: some sector upkeep stuff that is very haphazard and unclear in the spec var mi MinerInfo @@ -937,7 +992,12 @@ func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext) aerro return aerrors.HandleExternalError(nerr, "failed to load proving set") } - faults, nerr := self.FaultSet.All() + ss, nerr := amt.LoadAMT(types.WrapStorage(vmctx.Storage()), self.Sectors) + if nerr != nil { + return aerrors.HandleExternalError(nerr, "failed to load sector set") + } + + faults, nerr := self.FaultSet.All(2 * ss.Count) if nerr != nil { return aerrors.Absorb(nerr, 1, "invalid bitfield (fatal?)") } @@ -945,7 +1005,7 @@ func onSuccessfulPoSt(self *StorageMinerActorState, vmctx types.VMContext) aerro self.FaultSet = types.NewBitField() oldPower := self.Power - newPower := types.BigMul(types.NewInt(pss.Count-uint64(len(faults))), types.NewInt(mi.SectorSize)) + newPower := types.BigMul(types.NewInt(pss.Count-activeFaults), types.NewInt(mi.SectorSize)) // If below the minimum size requirement, miners have zero power if newPower.LessThan(types.NewInt(build.MinimumMinerPower)) { diff --git a/chain/actors/actor_miner_test.go b/chain/actors/actor_miner_test.go index a522fe349..f76509373 100644 --- a/chain/actors/actor_miner_test.go +++ b/chain/actors/actor_miner_test.go @@ -3,18 +3,22 @@ package actors_test import ( "bytes" "context" + "math" "math/rand" "testing" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-sectorbuilder" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/rlepluslazy" hamt "github.com/ipfs/go-hamt-ipld" blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/stretchr/testify/assert" cbg "github.com/whyrusleeping/cbor-gen" ) @@ -50,23 +54,37 @@ func TestMinerCommitSectors(t *testing.T) { addSectorToMiner(h, t, minerAddr, worker, client, 1) assertSectorIDs(h, t, minerAddr, []uint64{1}) + } +type badRuns struct { + done bool +} + +func (br *badRuns) HasNext() bool { + return !br.done +} + +func (br *badRuns) NextRun() (rlepluslazy.Run, error) { + br.done = true + return rlepluslazy.Run{true, math.MaxInt64}, nil +} + +var _ rlepluslazy.RunIterator = (*badRuns)(nil) + func TestMinerSubmitBadFault(t *testing.T) { + oldSS, oldMin := build.SectorSizes, build.MinimumMinerPower + build.SectorSizes, build.MinimumMinerPower = []uint64{1024}, 1024 + defer func() { + build.SectorSizes, build.MinimumMinerPower = oldSS, oldMin + }() + var worker, client address.Address var minerAddr address.Address opts := []HarnessOpt{ HarnessAddr(&worker, 1000000), HarnessAddr(&client, 1000000), - HarnessActor(&minerAddr, &worker, actors.StorageMinerCodeCid, - func() cbg.CBORMarshaler { - return &actors.StorageMinerConstructorParams{ - Owner: worker, - Worker: worker, - SectorSize: 1024, - PeerID: "fakepeerid", - } - }), + HarnessAddMiner(&minerAddr, &worker), } h := NewHarness(t, opts...) @@ -92,18 +110,52 @@ func TestMinerSubmitBadFault(t *testing.T) { ret, _ = h.Invoke(t, actors.NetworkAddress, minerAddr, actors.MAMethods.SubmitElectionPoSt, nil) ApplyOK(t, ret) - assertSectorIDs(h, t, minerAddr, []uint64{1}) + st, err := getMinerState(context.TODO(), h.vm.StateTree(), h.bs, minerAddr) + assert.NoError(t, err) + expectedPower := st.Power + if types.BigCmp(expectedPower, types.NewInt(1024)) != 0 { + t.Errorf("Expected power of 1024, got %s", expectedPower) + } + badnum := uint64(0) badnum-- bf = types.NewBitField() bf.Set(badnum) + bf.Set(badnum - 1) ret, _ = h.Invoke(t, worker, minerAddr, actors.MAMethods.DeclareFaults, &actors.DeclareFaultsParams{bf}) ApplyOK(t, ret) ret, _ = h.Invoke(t, actors.NetworkAddress, minerAddr, actors.MAMethods.SubmitElectionPoSt, nil) + ApplyOK(t, ret) + assertSectorIDs(h, t, minerAddr, []uint64{1}) + + st, err = getMinerState(context.TODO(), h.vm.StateTree(), h.bs, minerAddr) + assert.NoError(t, err) + currentPower := st.Power + if types.BigCmp(expectedPower, currentPower) != 0 { + t.Errorf("power changed and shouldn't have: %s != %s", expectedPower, currentPower) + } + + bf.Set(badnum - 2) + ret, _ = h.Invoke(t, worker, minerAddr, actors.MAMethods.DeclareFaults, &actors.DeclareFaultsParams{bf}) + if ret.ExitCode != 3 { + t.Errorf("expected exit code 3, got %d: %+v", ret.ExitCode, ret.ActorErr) + } + assertSectorIDs(h, t, minerAddr, []uint64{1}) + + rle, err := rlepluslazy.EncodeRuns(&badRuns{}, []byte{}) + assert.NoError(t, err) + + bf, err = types.NewBitFieldFromBytes(rle) + assert.NoError(t, err) + ret, _ = h.Invoke(t, worker, minerAddr, actors.MAMethods.DeclareFaults, &actors.DeclareFaultsParams{bf}) + if ret.ExitCode != 3 { + t.Errorf("expected exit code 3, got %d: %+v", ret.ExitCode, ret.ActorErr) + } + assertSectorIDs(h, t, minerAddr, []uint64{1}) bf = types.NewBitField() bf.Set(1) @@ -175,7 +227,7 @@ func assertSectorIDs(h *Harness, t *testing.T, maddr address.Address, ids []uint } } -func getMinerSectorSet(ctx context.Context, st types.StateTree, bs blockstore.Blockstore, maddr address.Address) ([]*api.ChainSectorInfo, error) { +func getMinerState(ctx context.Context, st types.StateTree, bs blockstore.Blockstore, maddr address.Address) (*actors.StorageMinerActorState, error) { mact, err := st.GetActor(maddr) if err != nil { return nil, err @@ -187,6 +239,14 @@ func getMinerSectorSet(ctx context.Context, st types.StateTree, bs blockstore.Bl if err := cst.Get(ctx, mact.Head, &mstate); err != nil { return nil, err } + return &mstate, nil +} + +func getMinerSectorSet(ctx context.Context, st types.StateTree, bs blockstore.Blockstore, maddr address.Address) ([]*api.ChainSectorInfo, error) { + mstate, err := getMinerState(ctx, st, bs, maddr) + if err != nil { + return nil, err + } return stmgr.LoadSectorsFromSet(ctx, bs, mstate.Sectors) } diff --git a/chain/actors/actor_storagepower.go b/chain/actors/actor_storagepower.go index 09a3eb30a..d3c2ad674 100644 --- a/chain/actors/actor_storagepower.go +++ b/chain/actors/actor_storagepower.go @@ -67,7 +67,7 @@ type CreateStorageMinerParams struct { func (spa StoragePowerActor) CreateStorageMiner(act *types.Actor, vmctx types.VMContext, params *CreateStorageMinerParams) ([]byte, ActorError) { if !build.SupportedSectorSize(params.SectorSize) { - return nil, aerrors.New(1, "Unsupported sector size") + return nil, aerrors.Newf(1, "Unsupported sector size: %d", params.SectorSize) } var self StoragePowerState diff --git a/chain/actors/harness2_test.go b/chain/actors/harness2_test.go index d5d34795b..2739d0cc8 100644 --- a/chain/actors/harness2_test.go +++ b/chain/actors/harness2_test.go @@ -115,6 +115,32 @@ func HarnessActor(actor *address.Address, creator *address.Address, code cid.Cid } +func HarnessAddMiner(addr *address.Address, creator *address.Address) HarnessOpt { + return func(t testing.TB, h *Harness) error { + if h.Stage != HarnessPostInit { + return nil + } + if !addr.Empty() { + return xerrors.New("actor address should be empty") + } + ret, _ := h.InvokeWithValue(t, *creator, actors.StoragePowerAddress, + actors.SPAMethods.CreateStorageMiner, types.NewInt(3000), &actors.StorageMinerConstructorParams{ + Owner: *creator, + Worker: *creator, + SectorSize: 1024, + PeerID: "fakepeerid", + }) + + if ret.ExitCode != 0 { + return xerrors.Errorf("creating actor: %w", ret.ActorErr) + } + var err error + *addr, err = address.NewFromBytes(ret.Return) + return err + + } +} + func HarnessCtx(ctx context.Context) HarnessOpt { return func(t testing.TB, h *Harness) error { h.ctx = ctx @@ -177,7 +203,7 @@ func NewHarness(t *testing.T, options ...HarnessOpt) *Harness { for _, opt := range options { err := opt(t, h) if err != nil { - t.Fatalf("Applying options: %v", err) + t.Fatalf("Applying options: %+v", err) } } diff --git a/chain/types/bitfield.go b/chain/types/bitfield.go index d959e4f84..81031085b 100644 --- a/chain/types/bitfield.go +++ b/chain/types/bitfield.go @@ -1,6 +1,7 @@ package types import ( + "errors" "fmt" "io" @@ -9,6 +10,8 @@ import ( "golang.org/x/xerrors" ) +var ErrBitFieldTooMany = errors.New("to many items in RLE") + type BitField struct { rle rlepluslazy.RLE @@ -16,14 +19,23 @@ type BitField struct { } func NewBitField() BitField { - rle, err := rlepluslazy.FromBuf([]byte{}) + bf, err := NewBitFieldFromBytes([]byte{}) if err != nil { - panic(err) + panic(fmt.Sprintf("creating empty rle: %+v", err)) } - return BitField{ - rle: rle, - bits: make(map[uint64]struct{}), + return bf +} + +func NewBitFieldFromBytes(rle []byte) (BitField, error) { + bf := BitField{} + rlep, err := rlepluslazy.FromBuf(rle) + if err != nil { + return BitField{}, xerrors.Errorf("could not decode rle+: %w", err) } + bf.rle = rlep + bf.bits = make(map[uint64]struct{}) + return bf, nil + } func BitFieldFromSet(setBits []uint64) BitField { @@ -106,7 +118,14 @@ func (bf BitField) Count() (uint64, error) { } // All returns all set bits -func (bf BitField) All() ([]uint64, error) { +func (bf BitField) All(max uint64) ([]uint64, error) { + c, err := bf.Count() + if err != nil { + return nil, xerrors.Errorf("count errror: %w", err) + } + if c > max { + return nil, xerrors.Errorf("expected %d, got %d: %w", max, c, ErrBitFieldTooMany) + } runs, err := bf.sum() if err != nil { @@ -121,7 +140,14 @@ func (bf BitField) All() ([]uint64, error) { return res, nil } -func (bf BitField) AllMap() (map[uint64]bool, error) { +func (bf BitField) AllMap(max uint64) (map[uint64]bool, error) { + c, err := bf.Count() + if err != nil { + return nil, xerrors.Errorf("count errror: %w", err) + } + if c > max { + return nil, xerrors.Errorf("expected %d, got %d: %w", max, c, ErrBitFieldTooMany) + } runs, err := bf.sum() if err != nil { diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index 23fdab0f7..6b3f5c679 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -216,7 +216,7 @@ func IsTicketWinner(partialTicket []byte, ssizeI uint64, snum uint64, totpow Big return lhs.Cmp(rhs) < 0 } -func ElectionPostChallengeCount(sectors uint64, faults int) uint64 { +func ElectionPostChallengeCount(sectors uint64, faults uint64) uint64 { return sectorbuilder.ElectionPostChallengeCount(sectors, faults) } diff --git a/go.mod b/go.mod index 3283f456b..42b472943 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/filecoin-project/go-cbor-util v0.0.0-20191219014500-08c40a1e63a2 github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 github.com/filecoin-project/go-paramfetch v0.0.0-20200102181131-b20d579f2878 - github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107152336-0cbb2c483013 + github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107220006-3361d30ea5ab github.com/filecoin-project/go-statestore v0.0.0-20200102200712-1f63c701c1e5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-ole/go-ole v1.2.4 // indirect diff --git a/go.sum b/go.sum index 9fe0eb6e1..3c8f189b2 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMX github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= github.com/filecoin-project/go-paramfetch v0.0.0-20200102181131-b20d579f2878 h1:YicJT9xhPzZ1SBGiJFNUCkfwqK/G9vFyY1ytKBSjNJA= github.com/filecoin-project/go-paramfetch v0.0.0-20200102181131-b20d579f2878/go.mod h1:40kI2Gv16mwcRsHptI3OAV4nlOEU7wVDc4RgMylNFjU= -github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107152336-0cbb2c483013 h1:OGpRq3HRxyrxZJtbNKCOsb5YTmc+RBLLwdAgwZfkRnY= -github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107152336-0cbb2c483013/go.mod h1:3OZ4E3B2OuwhJjtxR4r7hPU9bCfB+A+hm4alLEsaeDc= +github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107220006-3361d30ea5ab h1:bsrBNO1LwnhOLxPEXlSPal/WuY61mLJUCHYyD0NayHg= +github.com/filecoin-project/go-sectorbuilder v0.0.0-20200107220006-3361d30ea5ab/go.mod h1:3OZ4E3B2OuwhJjtxR4r7hPU9bCfB+A+hm4alLEsaeDc= github.com/filecoin-project/go-statestore v0.0.0-20200102200712-1f63c701c1e5 h1:NZXq90YlfakSmB2/84dGr0AVmKYFA97+yyViBIgTFbk= github.com/filecoin-project/go-statestore v0.0.0-20200102200712-1f63c701c1e5/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=