diff --git a/api/api_full.go b/api/api_full.go index 657e945ca..957529eaa 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -355,6 +355,8 @@ type FullNode interface { StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) // StateMinerAvailableBalance returns the portion of a miner's balance that can be withdrawn or spent StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) + // StateMinerSectorAllocated checks if a sector is allocated + StateMinerSectorAllocated(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (bool, error) // StateSectorPreCommitInfo returns the PreCommit info for the specified miner's sector StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) // StateSectorGetInfo returns the on-chain info for the specified miner's sector. Returns null in case the sector info isn't found diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 61863044e..edf119114 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -181,6 +181,7 @@ type FullNodeStruct struct { StateMinerPreCommitDepositForPower func(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) `perm:"read"` StateMinerInitialPledgeCollateral func(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) `perm:"read"` StateMinerAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"` + StateMinerSectorAllocated func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (bool, error) `perm:"read"` StateSectorPreCommitInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) `perm:"read"` StateSectorGetInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error) `perm:"read"` StateSectorExpiration func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorExpiration, error) `perm:"read"` @@ -863,6 +864,10 @@ func (c *FullNodeStruct) StateMinerAvailableBalance(ctx context.Context, maddr a return c.Internal.StateMinerAvailableBalance(ctx, maddr, tsk) } +func (c *FullNodeStruct) StateMinerSectorAllocated(ctx context.Context, maddr address.Address, s abi.SectorNumber, tsk types.TipSetKey) (bool, error) { + return c.Internal.StateMinerSectorAllocated(ctx, maddr, s, tsk) +} + func (c *FullNodeStruct) StateSectorPreCommitInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { return c.Internal.StateSectorPreCommitInfo(ctx, maddr, n, tsk) } diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index a936e14b8..3a5931c8b 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -45,8 +45,11 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorAddPiece{}, WaitDeals), on(SectorStartPacking{}, Packing), ), - Packing: planOne(on(SectorPacked{}, GetTicket)), - GetTicket: planOne(on(SectorTicket{}, PreCommit1)), + Packing: planOne(on(SectorPacked{}, GetTicket)), + GetTicket: planOne( + on(SectorTicket{}, PreCommit1), + on(SectorCommitFailed{}, CommitFailed), + ), PreCommit1: planOne( on(SectorPreCommit1{}, PreCommit2), on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), @@ -124,6 +127,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorRetryCommitWait{}, CommitWait), on(SectorDealsExpired{}, DealsExpired), on(SectorInvalidDealIDs{}, RecoverDealIDs), + on(SectorTicketExpired{}, Removing), ), FinalizeFailed: planOne( on(SectorRetryFinalize{}, FinalizeSector), diff --git a/extern/storage-sealing/fsm_events.go b/extern/storage-sealing/fsm_events.go index aec2beb0a..59f5e77e6 100644 --- a/extern/storage-sealing/fsm_events.go +++ b/extern/storage-sealing/fsm_events.go @@ -206,6 +206,11 @@ type SectorDealsExpired struct{ error } func (evt SectorDealsExpired) FormatError(xerrors.Printer) (next error) { return evt.error } func (evt SectorDealsExpired) apply(*SectorInfo) {} +type SectorTicketExpired struct{ error } + +func (evt SectorTicketExpired) FormatError(xerrors.Printer) (next error) { return evt.error } +func (evt SectorTicketExpired) apply(*SectorInfo) {} + type SectorCommitted struct { Proof []byte } diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 1ba53661a..d9953eee0 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -53,6 +53,7 @@ type SealingAPI interface { StateMinerWorkerAddress(ctx context.Context, maddr address.Address, tok TipSetToken) (address.Address, error) StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, TipSetToken) (big.Int, error) StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, TipSetToken) (big.Int, error) + StateMinerSectorAllocated(context.Context, address.Address, abi.SectorNumber, TipSetToken) (bool, error) StateMarketStorageDeal(context.Context, abi.DealID, TipSetToken) (market.DealProposal, error) StateNetworkVersion(ctx context.Context, tok TipSetToken) (network.Version, error) SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) diff --git a/extern/storage-sealing/states_failed.go b/extern/storage-sealing/states_failed.go index d22830253..b583701ae 100644 --- a/extern/storage-sealing/states_failed.go +++ b/extern/storage-sealing/states_failed.go @@ -170,7 +170,7 @@ func (m *Sealing) handleCommitFailed(ctx statemachine.Context, sector SectorInfo case *ErrExpiredTicket: return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("ticket expired error: %w", err)}) case *ErrBadTicket: - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad ticket: %w", err)}) + return ctx.Send(SectorTicketExpired{xerrors.Errorf("expired ticket: %w", err)}) case *ErrInvalidDeals: log.Warnf("invalid deals in sector %d: %v", sector.SectorNumber, err) return ctx.Send(SectorInvalidDealIDs{Return: RetCommitFailed}) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 7915660fa..415335f68 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -87,6 +87,21 @@ func (m *Sealing) getTicket(ctx statemachine.Context, sector SectorInfo) (abi.Se func (m *Sealing) handleGetTicket(ctx statemachine.Context, sector SectorInfo) error { ticketValue, ticketEpoch, err := m.getTicket(ctx, sector) if err != nil { + allocated, aerr := m.api.StateMinerSectorAllocated(ctx.Context(), m.maddr, sector.SectorNumber, nil) + if aerr == nil { + log.Errorf("error checking if sector is allocated: %+v", err) + } + + if allocated { + if sector.CommitMessage != nil { + // Some recovery paths with unfortunate timing lead here + return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector %s is committed but got into the GetTicket state", sector.SectorNumber)}) + } + + log.Errorf("Sector %s precommitted but expired", sector.SectorNumber) + return ctx.Send(SectorRemove{}) + } + return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("getting ticket failed: %w", err)}) } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 1c39b59da..2cdbf00e4 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -1096,6 +1096,25 @@ func (a *StateAPI) StateMinerAvailableBalance(ctx context.Context, maddr address return types.BigAdd(abal, vested), nil } +func (a *StateAPI) StateMinerSectorAllocated(ctx context.Context, maddr address.Address, s abi.SectorNumber, tsk types.TipSetKey) (bool, error) { + ts, err := a.Chain.GetTipSetFromKey(tsk) + if err != nil { + return false, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + + act, err := a.StateManager.LoadActor(ctx, maddr, ts) + if err != nil { + return false, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(a.StateManager.ChainStore().Store(ctx), act) + if err != nil { + return false, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + return mas.IsAllocated(s) +} + // StateVerifiedClientStatus returns the data cap for the given address. // Returns zero if there is no entry in the data cap table for the // address. diff --git a/storage/adapter_storage_miner.go b/storage/adapter_storage_miner.go index 380fb4471..8d74a897a 100644 --- a/storage/adapter_storage_miner.go +++ b/storage/adapter_storage_miner.go @@ -94,6 +94,15 @@ func (s SealingAPIAdapter) StateMinerDeadlines(ctx context.Context, maddr addres return s.delegate.StateMinerDeadlines(ctx, maddr, tsk) } +func (s SealingAPIAdapter) StateMinerSectorAllocated(ctx context.Context, maddr address.Address, sid abi.SectorNumber, tok sealing.TipSetToken) (bool, error) { + tsk, err := types.TipSetKeyFromBytes(tok) + if err != nil { + return false, xerrors.Errorf("failed to unmarshal TipSetToken to TipSetKey: %w", err) + } + + return s.delegate.StateMinerSectorAllocated(ctx, maddr, sid, tsk) +} + func (s SealingAPIAdapter) StateWaitMsg(ctx context.Context, mcid cid.Cid) (sealing.MsgLookup, error) { wmsg, err := s.delegate.StateWaitMsg(ctx, mcid, build.MessageConfidence) if err != nil { diff --git a/storage/miner.go b/storage/miner.go index 74a048c8e..b8985c1a5 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -83,6 +83,7 @@ type storageMinerApi interface { StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error) StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) + StateMinerSectorAllocated(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (bool, error) StateSearchMsg(context.Context, cid.Cid) (*api.MsgLookup, error) StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error) // TODO: removeme eventually StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error)