package main import ( "fmt" "os" "sort" "strconv" "text/tabwriter" "time" "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/urfave/cli/v2" "golang.org/x/xerrors" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) var sectorsCmd = &cli.Command{ Name: "sectors", Usage: "interact with sector store", Subcommands: []*cli.Command{ sectorsStatusCmd, sectorsListCmd, sectorsRefsCmd, sectorsUpdateCmd, sectorsPledgeCmd, sectorsRemoveCmd, sectorsMarkForUpgradeCmd, sectorsStartSealCmd, sectorsSealDelayCmd, sectorsCapacityCollateralCmd, }, } var sectorsPledgeCmd = &cli.Command{ Name: "pledge", Usage: "store random data in a sector", Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) return nodeApi.PledgeSector(ctx) }, } var sectorsStatusCmd = &cli.Command{ Name: "status", Usage: "Get the seal status of a sector by its number", ArgsUsage: "", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "log", Usage: "display event log", }, &cli.BoolFlag{ Name: "on-chain-info", Usage: "show sector on chain info", }, }, Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) if !cctx.Args().Present() { return fmt.Errorf("must specify sector number to get status of") } id, err := strconv.ParseUint(cctx.Args().First(), 10, 64) if err != nil { return err } onChainInfo := cctx.Bool("on-chain-info") status, err := nodeApi.SectorsStatus(ctx, abi.SectorNumber(id), onChainInfo) if err != nil { return err } fmt.Printf("SectorID:\t%d\n", status.SectorID) fmt.Printf("Status:\t\t%s\n", status.State) fmt.Printf("CIDcommD:\t%s\n", status.CommD) fmt.Printf("CIDcommR:\t%s\n", status.CommR) fmt.Printf("Ticket:\t\t%x\n", status.Ticket.Value) fmt.Printf("TicketH:\t%d\n", status.Ticket.Epoch) fmt.Printf("Seed:\t\t%x\n", status.Seed.Value) fmt.Printf("SeedH:\t\t%d\n", status.Seed.Epoch) fmt.Printf("Precommit:\t%s\n", status.PreCommitMsg) fmt.Printf("Commit:\t\t%s\n", status.CommitMsg) fmt.Printf("Proof:\t\t%x\n", status.Proof) fmt.Printf("Deals:\t\t%v\n", status.Deals) fmt.Printf("Retries:\t%d\n", status.Retries) if status.LastErr != "" { fmt.Printf("Last Error:\t\t%s\n", status.LastErr) } if onChainInfo { fmt.Printf("\nSector On Chain Info\n") fmt.Printf("SealProof:\t\t%x\n", status.SealProof) fmt.Printf("Activation:\t\t%v\n", status.Activation) fmt.Printf("Expiration:\t\t%v\n", status.Expiration) fmt.Printf("DealWeight:\t\t%v\n", status.DealWeight) fmt.Printf("VerifiedDealWeight:\t\t%v\n", status.VerifiedDealWeight) fmt.Printf("InitialPledge:\t\t%v\n", status.InitialPledge) fmt.Printf("\nExpiration Info\n") fmt.Printf("OnTime:\t\t%v\n", status.OnTime) fmt.Printf("Early:\t\t%v\n", status.Early) } if cctx.Bool("log") { fmt.Printf("--------\nEvent Log:\n") for i, l := range status.Log { fmt.Printf("%d.\t%s:\t[%s]\t%s\n", i, time.Unix(int64(l.Timestamp), 0), l.Kind, l.Message) if l.Trace != "" { fmt.Printf("\t%s\n", l.Trace) } } } return nil }, } var sectorsListCmd = &cli.Command{ Name: "list", Usage: "List sectors", Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() fullApi, closer2, err := lcli.GetFullNodeAPI(cctx) // TODO: consider storing full node address in config if err != nil { return err } defer closer2() ctx := lcli.ReqContext(cctx) list, err := nodeApi.SectorsList(ctx) if err != nil { return err } maddr, err := nodeApi.ActorAddress(ctx) if err != nil { return err } activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) if err != nil { return err } activeIDs := make(map[abi.SectorNumber]struct{}, len(activeSet)) for _, info := range activeSet { activeIDs[info.ID] = struct{}{} } sset, err := fullApi.StateMinerSectors(ctx, maddr, nil, true, types.EmptyTSK) if err != nil { return err } commitedIDs := make(map[abi.SectorNumber]struct{}, len(activeSet)) for _, info := range sset { commitedIDs[info.ID] = struct{}{} } sort.Slice(list, func(i, j int) bool { return list[i] < list[j] }) w := tabwriter.NewWriter(os.Stdout, 8, 4, 1, ' ', 0) for _, s := range list { st, err := nodeApi.SectorsStatus(ctx, s, false) if err != nil { fmt.Fprintf(w, "%d:\tError: %s\n", s, err) continue } _, inSSet := commitedIDs[s] _, inASet := activeIDs[s] fmt.Fprintf(w, "%d: %s\tsSet: %s\tactive: %s\ttktH: %d\tseedH: %d\tdeals: %v\t toUpgrade:%t\n", s, st.State, yesno(inSSet), yesno(inASet), st.Ticket.Epoch, st.Seed.Epoch, st.Deals, st.ToUpgrade, ) } return w.Flush() }, } var sectorsRefsCmd = &cli.Command{ Name: "refs", Usage: "List References to sectors", Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) refs, err := nodeApi.SectorsRefs(ctx) if err != nil { return err } for name, refs := range refs { fmt.Printf("Block %s:\n", name) for _, ref := range refs { fmt.Printf("\t%d+%d %d bytes\n", ref.SectorID, ref.Offset, ref.Size) } } return nil }, } var sectorsRemoveCmd = &cli.Command{ Name: "remove", Usage: "Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector)", ArgsUsage: "", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "really-do-it", Usage: "pass this flag if you know what you are doing", }, }, Action: func(cctx *cli.Context) error { if !cctx.Bool("really-do-it") { return xerrors.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing") } nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() != 1 { return xerrors.Errorf("must pass sector number") } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorRemove(ctx, abi.SectorNumber(id)) }, } var sectorsMarkForUpgradeCmd = &cli.Command{ Name: "mark-for-upgrade", Usage: "Mark a committed capacity sector for replacement by a sector with deals", ArgsUsage: "", Action: func(cctx *cli.Context) error { if cctx.Args().Len() != 1 { return lcli.ShowHelp(cctx, xerrors.Errorf("must pass sector number")) } nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorMarkForUpgrade(ctx, abi.SectorNumber(id)) }, } var sectorsStartSealCmd = &cli.Command{ Name: "seal", Usage: "Manually start sealing a sector (filling any unused space with junk)", ArgsUsage: "", Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() != 1 { return xerrors.Errorf("must pass sector number") } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorStartSealing(ctx, abi.SectorNumber(id)) }, } var sectorsSealDelayCmd = &cli.Command{ Name: "set-seal-delay", Usage: "Set the time, in minutes, that a new sector waits for deals before sealing starts", ArgsUsage: "", Action: func(cctx *cli.Context) error { nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() != 1 { return xerrors.Errorf("must pass duration in minutes") } hs, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { return xerrors.Errorf("could not parse sector number: %w", err) } delay := hs * uint64(time.Minute) return nodeApi.SectorSetSealDelay(ctx, time.Duration(delay)) }, } var sectorsCapacityCollateralCmd = &cli.Command{ Name: "get-cc-collateral", Usage: "Get the collateral required to pledge a committed capacity sector", Flags: []cli.Flag{ &cli.Uint64Flag{ Name: "expiration", Usage: "the epoch when the sector will expire", }, }, Action: func(cctx *cli.Context) error { mApi, mCloser, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer mCloser() nApi, nCloser, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err } defer nCloser() ctx := lcli.ReqContext(cctx) maddr, err := mApi.ActorAddress(ctx) if err != nil { return err } pci := miner.SectorPreCommitInfo{ Expiration: abi.ChainEpoch(cctx.Uint64("expiration")), } if pci.Expiration == 0 { pci.Expiration = miner.MaxSectorExpirationExtension } pc, err := nApi.StateMinerInitialPledgeCollateral(ctx, maddr, pci, types.EmptyTSK) if err != nil { return err } fmt.Printf("Estimated collateral: %s\n", types.FIL(pc)) return nil }, } var sectorsUpdateCmd = &cli.Command{ Name: "update-state", Usage: "ADVANCED: manually update the state of a sector, this may aid in error recovery", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "really-do-it", Usage: "pass this flag if you know what you are doing", }, }, Action: func(cctx *cli.Context) error { if !cctx.Bool("really-do-it") { return xerrors.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing") } nodeApi, closer, err := lcli.GetStorageMinerAPI(cctx) if err != nil { return err } defer closer() ctx := lcli.ReqContext(cctx) if cctx.Args().Len() < 2 { return xerrors.Errorf("must pass sector number and new state") } id, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) if err != nil { return xerrors.Errorf("could not parse sector number: %w", err) } return nodeApi.SectorsUpdate(ctx, abi.SectorNumber(id), api.SectorState(cctx.Args().Get(1))) }, } func yesno(b bool) string { if b { return "YES" } return "NO" }