2020-01-10 02:11:00 +00:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
2020-01-13 17:44:59 +00:00
|
|
|
"github.com/filecoin-project/lotus/lib/statemachine"
|
2020-01-10 02:11:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type SectorStart struct {
|
|
|
|
id uint64
|
|
|
|
pieces []Piece
|
|
|
|
}
|
|
|
|
type SectorRestart struct{}
|
|
|
|
|
|
|
|
type SectorFatalError struct{ error }
|
|
|
|
|
|
|
|
type SectorPacked struct{ pieces []Piece }
|
|
|
|
|
|
|
|
type SectorSealed struct {
|
|
|
|
commR []byte
|
|
|
|
commD []byte
|
|
|
|
ticket SealTicket
|
|
|
|
}
|
|
|
|
type SectorSealFailed struct{ error }
|
|
|
|
|
|
|
|
type SectorPreCommitFailed struct{ error }
|
|
|
|
type SectorPreCommitted struct {
|
|
|
|
message cid.Cid
|
|
|
|
}
|
|
|
|
|
|
|
|
type SectorSeedReady struct {
|
|
|
|
seed SealSeed
|
|
|
|
}
|
|
|
|
|
|
|
|
type SectorSealCommitFailed struct{ error }
|
|
|
|
type SectorCommitFailed struct{ error }
|
|
|
|
type SectorCommitted struct {
|
|
|
|
message cid.Cid
|
|
|
|
proof []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type SectorProving struct{}
|
|
|
|
|
|
|
|
type SectorFaultReported struct{ reportMsg cid.Cid }
|
|
|
|
type SectorFaultedFinal struct{}
|
|
|
|
|
|
|
|
type SectorForceState struct {
|
|
|
|
state api.SectorState
|
|
|
|
}
|
|
|
|
|
2020-01-13 17:44:59 +00:00
|
|
|
func (m *Miner) Plan(events []statemachine.Event, user interface{}) (interface{}, error) {
|
2020-01-10 02:11:00 +00:00
|
|
|
next, err := m.plan(events, user.(*SectorInfo))
|
|
|
|
if err != nil || next == nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-01-13 17:44:59 +00:00
|
|
|
return func(ctx statemachine.Context, si SectorInfo) error {
|
2020-01-10 02:11:00 +00:00
|
|
|
err := next(ctx, si)
|
|
|
|
if err != nil {
|
|
|
|
if err := ctx.Send(SectorFatalError{error: err}); err != nil {
|
|
|
|
return xerrors.Errorf("error while sending error: reporting %+v: %w", err, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-01-13 17:44:59 +00:00
|
|
|
func (m *Miner) plan(events []statemachine.Event, state *SectorInfo) (func(statemachine.Context, SectorInfo) error, error) {
|
2020-01-10 02:11:00 +00:00
|
|
|
/////
|
|
|
|
// First process all events
|
|
|
|
|
|
|
|
for _, event := range events {
|
|
|
|
if err, ok := event.User.(error); ok {
|
|
|
|
state.LastErr = fmt.Sprintf("%+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch event := event.User.(type) {
|
|
|
|
case SectorStart:
|
|
|
|
// TODO: check if state is clean
|
|
|
|
state.SectorID = event.id
|
|
|
|
state.Pieces = event.pieces
|
|
|
|
state.State = api.Packing
|
|
|
|
|
|
|
|
case SectorRestart:
|
|
|
|
// noop
|
|
|
|
case SectorFatalError:
|
|
|
|
log.Errorf("Fatal error on sector %d: %+v", state.SectorID, event.error)
|
|
|
|
// TODO: Do we want to mark the state as unrecoverable?
|
|
|
|
// I feel like this should be a softer error, where the user would
|
|
|
|
// be able to send a retry event of some kind
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
// // TODO: Incoming
|
|
|
|
// TODO: for those - look at dealIDs matching chain
|
|
|
|
|
|
|
|
// //
|
|
|
|
// Packing
|
|
|
|
|
|
|
|
case SectorPacked:
|
|
|
|
// TODO: assert state
|
|
|
|
state.Pieces = append(state.Pieces, event.pieces...)
|
|
|
|
state.State = api.Unsealed
|
|
|
|
|
|
|
|
// // Unsealed
|
|
|
|
|
|
|
|
case SectorSealFailed:
|
|
|
|
// TODO: try to find out the reason, maybe retry
|
|
|
|
state.State = api.SealFailed
|
|
|
|
|
|
|
|
case SectorSealed:
|
|
|
|
state.CommD = event.commD
|
|
|
|
state.CommR = event.commR
|
|
|
|
state.Ticket = event.ticket
|
|
|
|
state.State = api.PreCommitting
|
|
|
|
|
|
|
|
// // PreCommit
|
|
|
|
|
|
|
|
case SectorPreCommitFailed:
|
|
|
|
// TODO: try to find out the reason, maybe retry
|
|
|
|
state.State = api.PreCommitFailed
|
|
|
|
case SectorPreCommitted:
|
|
|
|
state.PreCommitMessage = &event.message
|
|
|
|
state.State = api.PreCommitted
|
|
|
|
|
|
|
|
case SectorSeedReady:
|
|
|
|
state.Seed = event.seed
|
|
|
|
state.State = api.Committing
|
|
|
|
|
|
|
|
// // Commit
|
|
|
|
|
|
|
|
case SectorSealCommitFailed:
|
|
|
|
// TODO: try to find out the reason, maybe retry
|
|
|
|
state.State = api.SealCommitFailed
|
|
|
|
case SectorCommitFailed:
|
|
|
|
// TODO: try to find out the reason, maybe retry
|
|
|
|
state.State = api.SealFailed
|
|
|
|
case SectorCommitted:
|
|
|
|
state.Proof = event.proof
|
2020-01-13 17:18:52 +00:00
|
|
|
state.CommitMessage = &event.message
|
2020-01-10 02:11:00 +00:00
|
|
|
state.State = api.CommitWait
|
|
|
|
case SectorProving:
|
|
|
|
state.State = api.Proving
|
|
|
|
|
|
|
|
case SectorFaultReported:
|
|
|
|
state.FaultReportMsg = &event.reportMsg
|
|
|
|
state.State = api.FaultReported
|
|
|
|
case SectorFaultedFinal:
|
|
|
|
state.State = api.FaultedFinal
|
|
|
|
|
|
|
|
// // Debug triggers
|
|
|
|
case SectorForceState:
|
|
|
|
state.State = event.state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/////
|
|
|
|
// Now decide what to do next
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
* Empty
|
|
|
|
| |
|
|
|
|
| v
|
|
|
|
*<- Packing <- incoming
|
|
|
|
| |
|
|
|
|
| v
|
|
|
|
*<- Unsealed <--> SealFailed
|
|
|
|
| |
|
|
|
|
| v
|
|
|
|
* PreCommitting <--> PreCommitFailed
|
|
|
|
| | ^
|
|
|
|
| v |
|
|
|
|
*<- PreCommitted ------/
|
|
|
|
| |||
|
|
|
|
| vvv v--> SealCommitFailed
|
|
|
|
*<- Committing
|
|
|
|
| | ^--> CommitFailed
|
|
|
|
| v ^
|
|
|
|
*<- CommitWait ---/
|
|
|
|
| |
|
|
|
|
| v
|
|
|
|
*<- Proving
|
|
|
|
|
|
|
|
|
v
|
|
|
|
FailedUnrecoverable
|
|
|
|
|
|
|
|
UndefinedSectorState <- ¯\_(ツ)_/¯
|
|
|
|
| ^
|
|
|
|
*---------------------/
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch state.State {
|
|
|
|
// Happy path
|
|
|
|
case api.Packing:
|
|
|
|
return m.handlePacking, nil
|
|
|
|
case api.Unsealed:
|
|
|
|
return m.handleUnsealed, nil
|
|
|
|
case api.PreCommitting:
|
|
|
|
return m.handlePreCommitting, nil
|
|
|
|
case api.PreCommitted:
|
|
|
|
return m.handlePreCommitted, nil
|
|
|
|
case api.Committing:
|
|
|
|
return m.handleCommitting, nil
|
|
|
|
case api.CommitWait:
|
|
|
|
return m.handleCommitWait, nil
|
|
|
|
case api.Proving:
|
|
|
|
// TODO: track sector health / expiration
|
|
|
|
log.Infof("Proving sector %d", state.SectorID)
|
|
|
|
|
|
|
|
// Handled failure modes
|
|
|
|
case api.SealFailed:
|
|
|
|
log.Warnf("sector %d entered unimplemented state 'SealFailed'", state.SectorID)
|
|
|
|
case api.PreCommitFailed:
|
|
|
|
log.Warnf("sector %d entered unimplemented state 'PreCommitFailed'", state.SectorID)
|
|
|
|
case api.SealCommitFailed:
|
|
|
|
log.Warnf("sector %d entered unimplemented state 'SealCommitFailed'", state.SectorID)
|
|
|
|
case api.CommitFailed:
|
|
|
|
log.Warnf("sector %d entered unimplemented state 'CommitFailed'", state.SectorID)
|
|
|
|
|
|
|
|
// Faults
|
|
|
|
case api.Faulty:
|
|
|
|
return m.handleFaulty, nil
|
|
|
|
case api.FaultReported:
|
|
|
|
return m.handleFaultReported, nil
|
|
|
|
|
|
|
|
// Fatal errors
|
|
|
|
case api.UndefinedSectorState:
|
|
|
|
log.Error("sector update with undefined state!")
|
|
|
|
case api.FailedUnrecoverable:
|
|
|
|
log.Errorf("sector %d failed unrecoverably", state.SectorID)
|
|
|
|
default:
|
|
|
|
log.Errorf("unexpected sector update state: %d", state.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) restartSectors(ctx context.Context) error {
|
|
|
|
trackedSectors, err := m.ListSectors()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("loading sector list: %+v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, sector := range trackedSectors {
|
|
|
|
if err := m.sectors.Send(sector.SectorID, SectorRestart{}); err != nil {
|
|
|
|
log.Errorf("restarting sector %d: %+v", sector.SectorID, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Grab on-chain sector set and diff with trackedSectors
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Miner) ForceSectorState(ctx context.Context, id uint64, state api.SectorState) error {
|
|
|
|
return m.sectors.Send(id, SectorForceState{state})
|
|
|
|
}
|