From 859168015a7291142db65755c7193b07bde0e288 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 27 Jul 2020 19:51:30 -0400 Subject: [PATCH] Use specs actor's MinerNominalPowerMeetsConsensusMinimum --- api/api_full.go | 1 + chain/stmgr/utils.go | 16 +++++++ chain/sub/incoming.go | 97 ++++++++++++++++++++++++++----------------- chain/sync.go | 9 ++++ miner/miner.go | 23 ++-------- 5 files changed, 90 insertions(+), 56 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 2b14c62d8..974e67b4c 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -637,6 +637,7 @@ type MiningBaseInfo struct { SectorSize abi.SectorSize PrevBeaconEntry types.BeaconEntry BeaconEntries []types.BeaconEntry + HasMinPower bool } type BlockTemplate struct { diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index b5e47e2c9..49df52137 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -567,6 +567,11 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBe return nil, xerrors.Errorf("resolving worker address: %w", err) } + hmp, err := MinerHasMinPower(ctx, sm, maddr, lbts) + if err != nil { + return nil, xerrors.Errorf("determining if miner has min power failed: %w", err) + } + return &api.MiningBaseInfo{ MinerPower: mpow.QualityAdjPower, NetworkPower: tpow.QualityAdjPower, @@ -575,6 +580,7 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBe SectorSize: info.SectorSize, PrevBeaconEntry: *prev, BeaconEntries: entries, + HasMinPower: hmp, }, nil } @@ -659,3 +665,13 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me m := MethodsMap[act.Code][method] return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil } + +func MinerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { + var ps power.State + _, err := sm.LoadActorState(ctx, builtin.StoragePowerActorAddr, &ps, ts) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + return ps.MinerNominalPowerMeetsConsensusMinimum(sm.ChainStore().Store(ctx), addr) +} diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 875e2658f..b96f9ead2 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -191,14 +191,14 @@ func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub // if we can't find it, we check whether we are (near) synced in the chain. // if we are not synced we cannot validate the block and we must ignore it. // if we are synced and the miner is unknown, then the block is rejcected. - key, err := bv.getMinerWorkerKey(ctx, blk) + key, err := bv.checkPowerAndGetWorkerKey(ctx, blk.Header) if err != nil { if bv.isChainNearSynced() { - log.Warnf("received block message from unknown miner over pubsub; rejecting message") + log.Warnf("received block from unknown miner or miner that doesn't meet min power over pubsub; rejecting message") recordFailure("unknown_miner") return pubsub.ValidationReject } else { - log.Warnf("cannot validate block message; unknown miner in unsynced chain") + log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain") return pubsub.ValidationIgnore } } @@ -283,60 +283,83 @@ func (bv *BlockValidator) validateMsgMeta(ctx context.Context, msg *types.BlockM return nil } -func (bv *BlockValidator) getMinerWorkerKey(ctx context.Context, msg *types.BlockMsg) (address.Address, error) { - addr := msg.Header.Miner +func (bv *BlockValidator) checkPowerAndGetWorkerKey(ctx context.Context, bh *types.BlockHeader) (address.Address, error) { + addr := bh.Miner bv.mx.Lock() key, ok := bv.keycache[addr.String()] bv.mx.Unlock() - if ok { - return key, nil + if !ok { + // TODO I have a feeling all this can be simplified by cleverer DI to use the API + ts := bv.chain.GetHeaviestTipSet() + st, _, err := bv.stmgr.TipSetState(ctx, ts) + if err != nil { + return address.Undef, err + } + buf := bufbstore.NewBufferedBstore(bv.chain.Blockstore()) + cst := cbor.NewCborStore(buf) + state, err := state.LoadStateTree(cst, st) + if err != nil { + return address.Undef, err + } + act, err := state.GetActor(addr) + if err != nil { + return address.Undef, err + } + + blk, err := bv.chain.Blockstore().Get(act.Head) + if err != nil { + return address.Undef, err + } + aso := blk.RawData() + + var mst miner.State + err = mst.UnmarshalCBOR(bytes.NewReader(aso)) + if err != nil { + return address.Undef, err + } + + info, err := mst.GetInfo(adt.WrapStore(ctx, cst)) + if err != nil { + return address.Undef, err + } + + worker := info.Worker + key, err = bv.stmgr.ResolveToKeyAddress(ctx, worker, ts) + if err != nil { + return address.Undef, err + } + + bv.mx.Lock() + bv.keycache[addr.String()] = key + bv.mx.Unlock() } - // TODO I have a feeling all this can be simplified by cleverer DI to use the API - ts := bv.chain.GetHeaviestTipSet() - st, _, err := bv.stmgr.TipSetState(ctx, ts) - if err != nil { - return address.Undef, err - } - buf := bufbstore.NewBufferedBstore(bv.chain.Blockstore()) - cst := cbor.NewCborStore(buf) - state, err := state.LoadStateTree(cst, st) - if err != nil { - return address.Undef, err - } - act, err := state.GetActor(addr) + // we check that the miner met the minimum power at the lookback tipset + + baseTs, err := bv.stmgr.ChainStore().LoadTipSet(types.NewTipSetKey(bh.Parents...)) if err != nil { + log.Warnf("failed to load parent tipset of incoming block") return address.Undef, err } - blk, err := bv.chain.Blockstore().Get(act.Head) - if err != nil { - return address.Undef, err - } - aso := blk.RawData() - - var mst miner.State - err = mst.UnmarshalCBOR(bytes.NewReader(aso)) + lbts, err := stmgr.GetLookbackTipSetForRound(ctx, bv.stmgr, baseTs, bh.Height) if err != nil { + log.Warnf("failed to load lookback tipset for incoming block") return address.Undef, err } - info, err := mst.GetInfo(adt.WrapStore(ctx, cst)) + hmp, err := stmgr.MinerHasMinPower(ctx, bv.stmgr, bh.Miner, lbts) if err != nil { + log.Warnf("failed to determine if incoming block's miner has minimum power") return address.Undef, err } - worker := info.Worker - key, err = bv.stmgr.ResolveToKeyAddress(ctx, worker, ts) - if err != nil { - return address.Undef, err + if !hmp { + log.Warnf("incoming block's miner does not have minimum power") + return address.Undef, xerrors.New("incoming block's miner does not have minimum power") } - bv.mx.Lock() - bv.keycache[addr.String()] = key - bv.mx.Unlock() - return key, nil } diff --git a/chain/sync.go b/chain/sync.go index 22edd4060..4555a980c 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -739,6 +739,15 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) (er return xerrors.Errorf("block is not claiming to be a winner") } + hp, err := stmgr.MinerHasMinPower(ctx, syncer.sm, h.Miner, lbts) + if err != nil { + return xerrors.Errorf("determining if miner has min power failed: %w", err) + } + + if !hp { + return xerrors.New("block's miner does not meet minimum power threshold") + } + rBeacon := *prevBeacon if len(h.BeaconEntries) != 0 { rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1] diff --git a/miner/miner.go b/miner/miner.go index cc5c85b5a..75edf82d3 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -14,7 +14,6 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/power" "github.com/filecoin-project/specs-actors/actors/crypto" lru "github.com/hashicorp/golang-lru" @@ -269,15 +268,6 @@ func (m *Miner) GetBestMiningCandidate(ctx context.Context) (*MiningBase, error) return m.lastWork, nil } -func (m *Miner) hasPower(ctx context.Context, addr address.Address, ts *types.TipSet) (bool, error) { - mpower, err := m.api.StateMinerPower(ctx, addr, ts.Key()) - if err != nil { - return false, err - } - - return mpower.MinerPower.QualityAdjPower.GreaterThanEqual(power.ConsensusMinerMinPower), nil -} - // mineOne attempts to mine a single block, and does so synchronously, if and // only if we are eligible to mine. // @@ -301,6 +291,10 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, if mbi == nil { return nil, nil } + if !mbi.HasMinPower { + // slashed or just have no power yet + return nil, nil + } tMBI := build.Clock.Now() @@ -309,15 +303,6 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, tDrand := build.Clock.Now() bvals := mbi.BeaconEntries - hasPower, err := m.hasPower(ctx, m.address, base.TipSet) - if err != nil { - return nil, xerrors.Errorf("checking if miner is slashed: %w", err) - } - if !hasPower { - // slashed or just have no power yet - return nil, nil - } - tPowercheck := build.Clock.Now() log.Infof("Time delta between now and our mining base: %ds (nulls: %d)", uint64(build.Clock.Now().Unix())-base.TipSet.MinTimestamp(), base.NullRounds)