diff --git a/api/api_full.go b/api/api_full.go index 80edf385b..f22c322e4 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -108,6 +108,7 @@ type FullNode interface { StateMinerPeerID(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) StateMinerElectionPeriodStart(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) StateMinerSectorSize(context.Context, address.Address, *types.TipSet) (uint64, error) + StateMinerFaults(context.Context, address.Address, *types.TipSet) ([]uint64, error) StatePledgeCollateral(context.Context, *types.TipSet) (types.BigInt, error) StateWaitMsg(context.Context, cid.Cid) (*MsgWait, error) StateListMiners(context.Context, *types.TipSet) ([]address.Address, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 1a6e09421..b747b34a4 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -99,6 +99,7 @@ type FullNodeStruct struct { StateMinerPeerID func(ctx context.Context, m address.Address, ts *types.TipSet) (peer.ID, error) `perm:"read"` StateMinerElectionPeriodStart func(ctx context.Context, actor address.Address, ts *types.TipSet) (uint64, error) `perm:"read"` StateMinerSectorSize func(context.Context, address.Address, *types.TipSet) (uint64, error) `perm:"read"` + StateMinerFaults func(context.Context, address.Address, *types.TipSet) ([]uint64, error) `perm:"read"` StateCall func(context.Context, *types.Message, *types.TipSet) (*api.MethodCall, error) `perm:"read"` StateReplay func(context.Context, *types.TipSet, cid.Cid) (*api.ReplayResults, error) `perm:"read"` StateGetActor func(context.Context, address.Address, *types.TipSet) (*types.Actor, error) `perm:"read"` @@ -410,6 +411,10 @@ func (c *FullNodeStruct) StateMinerSectorSize(ctx context.Context, actor address return c.Internal.StateMinerSectorSize(ctx, actor, ts) } +func (c *FullNodeStruct) StateMinerFaults(ctx context.Context, actor address.Address, ts *types.TipSet) ([]uint64, error) { + return c.Internal.StateMinerFaults(ctx, actor, ts) +} + func (c *FullNodeStruct) StateCall(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.MethodCall, error) { return c.Internal.StateCall(ctx, msg, ts) } diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 1fa84b4d2..5480dc949 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -2,6 +2,8 @@ package stmgr import ( "context" + amt2 "github.com/filecoin-project/go-amt-ipld/v2" + "github.com/filecoin-project/lotus/chain/actors/aerrors" ffi "github.com/filecoin-project/filecoin-ffi" sectorbuilder "github.com/filecoin-project/go-sectorbuilder" @@ -253,6 +255,21 @@ func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, ma return mas.SlashedAt, nil } +func GetMinerFaults(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) ([]uint64, error) { + var mas actors.StorageMinerActorState + _, err := sm.LoadActorState(ctx, maddr, &mas, ts) + if err != nil { + return nil, xerrors.Errorf("(get ssize) failed to load miner actor state: %w", err) + } + + ss, lerr := amt2.LoadAMT(amt.WrapBlockstore(sm.cs.Blockstore()), mas.Sectors) + if lerr != nil { + return nil, aerrors.HandleExternalError(lerr, "could not load proving set node") + } + + return mas.FaultSet.All(2 * ss.Count) +} + func GetStorageDeal(ctx context.Context, sm *StateManager, dealId uint64, ts *types.TipSet) (*actors.OnChainDeal, error) { var state actors.StorageMarketState if _, err := sm.LoadActorState(ctx, actors.StorageMarketAddress, &state, ts); err != nil { diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 3ba0dfbf8..d8af4abbf 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -84,6 +84,10 @@ func (a *StateAPI) StateMinerSectorSize(ctx context.Context, actor address.Addre return stmgr.GetMinerSectorSize(ctx, a.StateManager, ts, actor) } +func (a *StateAPI) StateMinerFaults(ctx context.Context, addr address.Address, ts *types.TipSet) ([]uint64, error) { + return stmgr.GetMinerFaults(ctx, a.StateManager, ts, addr) +} + func (a *StateAPI) StatePledgeCollateral(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { param, err := actors.SerializeParams(&actors.PledgeCollateralParams{Size: types.NewInt(0)}) if err != nil { diff --git a/storage/fpost_run.go b/storage/fpost_run.go index 6dd3a8fe1..3c09b76b0 100644 --- a/storage/fpost_run.go +++ b/storage/fpost_run.go @@ -50,52 +50,85 @@ func (s *fpostScheduler) doPost(ctx context.Context, eps uint64, ts *types.TipSe }() } +func (s *fpostScheduler) declareFaults(ctx context.Context, fc uint64, params *actors.DeclareFaultsParams) error { + log.Warnf("DECLARING %d FAULTS", fc) + + enc, aerr := actors.SerializeParams(params) + if aerr != nil { + return xerrors.Errorf("could not serialize declare faults parameters: %w", aerr) + } + + msg := &types.Message{ + To: s.actor, + From: s.worker, + Method: actors.MAMethods.DeclareFaults, + Params: enc, + Value: types.NewInt(0), + GasLimit: types.NewInt(10000000), // i dont know help + GasPrice: types.NewInt(1), + } + + sm, err := s.api.MpoolPushMessage(ctx, msg) + if err != nil { + return xerrors.Errorf("pushing faults message to mpool: %w", err) + } + + rec, err := s.api.StateWaitMsg(ctx, sm.Cid()) + if err != nil { + return xerrors.Errorf("waiting for declare faults: %w", err) + } + + if rec.Receipt.ExitCode != 0 { + return xerrors.Errorf("declare faults exit %d", rec.Receipt.ExitCode) + } + + log.Infof("Faults declared successfully") + return nil +} + func (s *fpostScheduler) checkFaults(ctx context.Context, ssi sectorbuilder.SortedPublicSectorInfo) ([]uint64, error) { faults := s.sb.Scrub(ssi) - var faultIDs []uint64 + + declaredFaults := map[uint64]struct{}{} + + { + chainFaults, err := s.api.StateMinerFaults(ctx, s.actor, nil) + if err != nil { + return nil, xerrors.Errorf("checking on-chain faults: %w", err) + } + + for _, fault := range chainFaults { + declaredFaults[fault] = struct{}{} + } + } if len(faults) > 0 { params := &actors.DeclareFaultsParams{Faults: types.NewBitField()} for _, fault := range faults { - log.Warnf("fault detected: sector %d: %s", fault.SectorID, fault.Err) - faultIDs = append(faultIDs, fault.SectorID) + if _, ok := declaredFaults[fault.SectorID]; ok { + continue + } - // TODO: omit already declared (with finality in mind though..) + log.Warnf("new fault detected: sector %d: %s", fault.SectorID, fault.Err) + declaredFaults[fault.SectorID] = struct{}{} params.Faults.Set(fault.SectorID) } - log.Warnf("DECLARING %d FAULTS", len(faults)) - - enc, aerr := actors.SerializeParams(params) - if aerr != nil { - return nil, xerrors.Errorf("could not serialize declare faults parameters: %w", aerr) - } - - msg := &types.Message{ - To: s.actor, - From: s.worker, - Method: actors.MAMethods.DeclareFaults, - Params: enc, - Value: types.NewInt(0), - GasLimit: types.NewInt(10000000), // i dont know help - GasPrice: types.NewInt(1), - } - - sm, err := s.api.MpoolPushMessage(ctx, msg) + pc, err := params.Faults.Count() if err != nil { - return nil, xerrors.Errorf("pushing faults message to mpool: %w", err) + return nil, xerrors.Errorf("counting faults: %w", err) } + if pc > 0 { + if err := s.declareFaults(ctx, pc, params); err != nil { + return nil, err + } + } + } - rec, err := s.api.StateWaitMsg(ctx, sm.Cid()) - if err != nil { - return nil, xerrors.Errorf("waiting for declare faults: %w", err) - } - - if rec.Receipt.ExitCode != 0 { - return nil, xerrors.Errorf("declare faults exit %d", rec.Receipt.ExitCode) - } - log.Infof("Faults declared successfully") + faultIDs := make([]uint64, 0, len(declaredFaults)) + for fault := range declaredFaults { + faultIDs = append(faultIDs, fault) } return faultIDs, nil diff --git a/storage/miner.go b/storage/miner.go index 06eb2c47c..4772dcff6 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -50,6 +50,7 @@ type storageMinerApi interface { StateGetActor(ctx context.Context, actor address.Address, ts *types.TipSet) (*types.Actor, error) StateGetReceipt(context.Context, cid.Cid, *types.TipSet) (*types.MessageReceipt, error) StateMarketStorageDeal(context.Context, uint64, *types.TipSet) (*actors.OnChainDeal, error) + StateMinerFaults(context.Context, address.Address, *types.TipSet) ([]uint64, error) MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error)