diff --git a/cmd/lotus-storage-miner/init.go b/cmd/lotus-storage-miner/init.go index 71098e725..f67160d65 100644 --- a/cmd/lotus-storage-miner/init.go +++ b/cmd/lotus-storage-miner/init.go @@ -121,6 +121,7 @@ var initCmd = &cli.Command{ }, Subcommands: []*cli.Command{ restoreCmd, + serviceCmd, }, Action: func(cctx *cli.Context) error { log.Info("Initializing lotus miner") diff --git a/cmd/lotus-storage-miner/init_restore.go b/cmd/lotus-storage-miner/init_restore.go index c609fd2a4..d31c3788d 100644 --- a/cmd/lotus-storage-miner/init_restore.go +++ b/cmd/lotus-storage-miner/init_restore.go @@ -22,6 +22,7 @@ import ( lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/extern/sector-storage/stores" @@ -30,6 +31,224 @@ import ( "github.com/filecoin-project/lotus/node/repo" ) +// ctx context.Context, api lapi.FullNode, addr address.Address, peerid peer.ID +func restore(ctx context.Context, cctx *cli.Context, manageConfig func(*config.StorageMiner) error, after func(api lapi.FullNode, addr address.Address, peerid peer.ID, mi miner.MinerInfo) error) error { + if cctx.Args().Len() != 1 { + return xerrors.Errorf("expected 1 argument") + } + + log.Info("Trying to connect to full node RPC") + + api, closer, err := lcli.GetFullNodeAPIV1(cctx) // TODO: consider storing full node address in config + if err != nil { + return err + } + defer closer() + + log.Info("Checking full node version") + + v, err := api.Version(ctx) + if err != nil { + return err + } + + //TODO(anteva): bump api version? + if !v.APIVersion.EqMajorMinor(lapi.FullAPIVersion0) { + return xerrors.Errorf("Remote API version didn't match (expected %s, remote %s)", lapi.FullAPIVersion0, v.APIVersion) + } + + if !cctx.Bool("nosync") { + if err := lcli.SyncWait(ctx, &v0api.WrapperV1Full{FullNode: api}, false); err != nil { + return xerrors.Errorf("sync wait: %w", err) + } + } + + bf, err := homedir.Expand(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("expand backup file path: %w", err) + } + + st, err := os.Stat(bf) + if err != nil { + return xerrors.Errorf("stat backup file (%s): %w", bf, err) + } + + f, err := os.Open(bf) + if err != nil { + return xerrors.Errorf("opening backup file: %w", err) + } + defer f.Close() // nolint:errcheck + + log.Info("Checking if repo exists") + + repoPath := cctx.String(FlagMinerRepo) + r, err := repo.NewFS(repoPath) + if err != nil { + return err + } + + ok, err := r.Exists() + if err != nil { + return err + } + if ok { + return xerrors.Errorf("repo at '%s' is already initialized", cctx.String(FlagMinerRepo)) + } + + log.Info("Initializing repo") + + if err := r.Init(repo.StorageMiner); err != nil { + return err + } + + lr, err := r.Lock(repo.StorageMiner) + if err != nil { + return err + } + defer lr.Close() //nolint:errcheck + + if cctx.IsSet("config") { + log.Info("Restoring config") + + cf, err := homedir.Expand(cctx.String("config")) + if err != nil { + return xerrors.Errorf("expanding config path: %w", err) + } + + _, err = os.Stat(cf) + if err != nil { + return xerrors.Errorf("stat config file (%s): %w", cf, err) + } + + var cerr error + err = lr.SetConfig(func(raw interface{}) { + rcfg, ok := raw.(*config.StorageMiner) + if !ok { + cerr = xerrors.New("expected miner config") + return + } + + ff, err := config.FromFile(cf, rcfg) + if err != nil { + cerr = xerrors.Errorf("loading config: %w", err) + return + } + + *rcfg = *ff.(*config.StorageMiner) + if manageConfig != nil { + cerr = manageConfig(rcfg) + } + }) + if cerr != nil { + return cerr + } + if err != nil { + return xerrors.Errorf("setting config: %w", err) + } + + } else { + log.Warn("--config NOT SET, WILL USE DEFAULT VALUES") + } + + if cctx.IsSet("storage-config") { + log.Info("Restoring storage path config") + + cf, err := homedir.Expand(cctx.String("storage-config")) + if err != nil { + return xerrors.Errorf("expanding storage config path: %w", err) + } + + cfb, err := ioutil.ReadFile(cf) + if err != nil { + return xerrors.Errorf("reading storage config: %w", err) + } + + var cerr error + err = lr.SetStorage(func(scfg *stores.StorageConfig) { + cerr = json.Unmarshal(cfb, scfg) + }) + if cerr != nil { + return xerrors.Errorf("unmarshalling storage config: %w", cerr) + } + if err != nil { + return xerrors.Errorf("setting storage config: %w", err) + } + } else { + log.Warn("--storage-config NOT SET. NO SECTOR PATHS WILL BE CONFIGURED") + } + + log.Info("Restoring metadata backup") + + mds, err := lr.Datastore(context.TODO(), "/metadata") + if err != nil { + return err + } + + bar := pb.New64(st.Size()) + br := bar.NewProxyReader(f) + bar.ShowTimeLeft = true + bar.ShowPercent = true + bar.ShowSpeed = true + bar.Units = pb.U_BYTES + + bar.Start() + err = backupds.RestoreInto(br, mds) + bar.Finish() + + if err != nil { + return xerrors.Errorf("restoring metadata: %w", err) + } + + log.Info("Checking actor metadata") + + abytes, err := mds.Get(datastore.NewKey("miner-address")) + if err != nil { + return xerrors.Errorf("getting actor address from metadata datastore: %w", err) + } + + maddr, err := address.NewFromBytes(abytes) + if err != nil { + return xerrors.Errorf("parsing actor address: %w", err) + } + + log.Info("ACTOR ADDRESS: ", maddr.String()) + + mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + log.Info("SECTOR SIZE: ", units.BytesSize(float64(mi.SectorSize))) + + wk, err := api.StateAccountKey(ctx, mi.Worker, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("resolving worker key: %w", err) + } + + has, err := api.WalletHas(ctx, wk) + if err != nil { + return xerrors.Errorf("checking worker address: %w", err) + } + + if !has { + return xerrors.Errorf("worker address %s for miner actor %s not present in full node wallet", mi.Worker, maddr) + } + + log.Info("Initializing libp2p identity") + + p2pSk, err := makeHostKey(lr) + if err != nil { + return xerrors.Errorf("make host key: %w", err) + } + + peerid, err := peer.IDFromPrivateKey(p2pSk) + if err != nil { + return xerrors.Errorf("peer ID from private key: %w", err) + } + + return after(api, maddr, peerid, mi) +} + var restoreCmd = &cli.Command{ Name: "restore", Usage: "Initialize a lotus miner repo from a backup", @@ -49,231 +268,24 @@ var restoreCmd = &cli.Command{ }, ArgsUsage: "[backupFile]", Action: func(cctx *cli.Context) error { - log.Info("Initializing lotus miner using a backup") - if cctx.Args().Len() != 1 { - return xerrors.Errorf("expected 1 argument") - } - ctx := lcli.ReqContext(cctx) + log.Info("Initializing lotus miner using a backup") - log.Info("Trying to connect to full node RPC") + if err := restore(ctx, cctx, nil, func(api lapi.FullNode, maddr address.Address, peerid peer.ID, mi miner.MinerInfo) error { + log.Info("Checking proof parameters") - if err := checkV1ApiSupport(ctx, cctx); err != nil { - return err - } - - api, closer, err := lcli.GetFullNodeAPIV1(cctx) // TODO: consider storing full node address in config - if err != nil { - return err - } - defer closer() - - log.Info("Checking full node version") - - v, err := api.Version(ctx) - if err != nil { - return err - } - - if !v.APIVersion.EqMajorMinor(lapi.FullAPIVersion1) { - return xerrors.Errorf("Remote API version didn't match (expected %s, remote %s)", lapi.FullAPIVersion1, v.APIVersion) - } - - if !cctx.Bool("nosync") { - if err := lcli.SyncWait(ctx, &v0api.WrapperV1Full{FullNode: api}, false); err != nil { - return xerrors.Errorf("sync wait: %w", err) - } - } - - bf, err := homedir.Expand(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("expand backup file path: %w", err) - } - - st, err := os.Stat(bf) - if err != nil { - return xerrors.Errorf("stat backup file (%s): %w", bf, err) - } - - f, err := os.Open(bf) - if err != nil { - return xerrors.Errorf("opening backup file: %w", err) - } - defer f.Close() // nolint:errcheck - - log.Info("Checking if repo exists") - - repoPath := cctx.String(FlagMinerRepo) - r, err := repo.NewFS(repoPath) - if err != nil { - return err - } - - ok, err := r.Exists() - if err != nil { - return err - } - if ok { - return xerrors.Errorf("repo at '%s' is already initialized", cctx.String(FlagMinerRepo)) - } - - log.Info("Initializing repo") - - if err := r.Init(repo.StorageMiner); err != nil { - return err - } - - lr, err := r.Lock(repo.StorageMiner) - if err != nil { - return err - } - defer lr.Close() //nolint:errcheck - - if cctx.IsSet("config") { - log.Info("Restoring config") - - cf, err := homedir.Expand(cctx.String("config")) - if err != nil { - return xerrors.Errorf("expanding config path: %w", err) + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(mi.SectorSize)); err != nil { + return xerrors.Errorf("fetching proof parameters: %w", err) } - _, err = os.Stat(cf) - if err != nil { - return xerrors.Errorf("stat config file (%s): %w", cf, err) + log.Info("Configuring miner actor") + + if err := configureStorageMiner(ctx, api, maddr, peerid, big.Zero()); err != nil { + return err } - var cerr error - err = lr.SetConfig(func(raw interface{}) { - rcfg, ok := raw.(*config.StorageMiner) - if !ok { - cerr = xerrors.New("expected miner config") - return - } - - ff, err := config.FromFile(cf, rcfg) - if err != nil { - cerr = xerrors.Errorf("loading config: %w", err) - return - } - - *rcfg = *ff.(*config.StorageMiner) - }) - if cerr != nil { - return cerr - } - if err != nil { - return xerrors.Errorf("setting config: %w", err) - } - - } else { - log.Warn("--config NOT SET, WILL USE DEFAULT VALUES") - } - - if cctx.IsSet("storage-config") { - log.Info("Restoring storage path config") - - cf, err := homedir.Expand(cctx.String("storage-config")) - if err != nil { - return xerrors.Errorf("expanding storage config path: %w", err) - } - - cfb, err := ioutil.ReadFile(cf) - if err != nil { - return xerrors.Errorf("reading storage config: %w", err) - } - - var cerr error - err = lr.SetStorage(func(scfg *stores.StorageConfig) { - cerr = json.Unmarshal(cfb, scfg) - }) - if cerr != nil { - return xerrors.Errorf("unmarshalling storage config: %w", cerr) - } - if err != nil { - return xerrors.Errorf("setting storage config: %w", err) - } - } else { - log.Warn("--storage-config NOT SET. NO SECTOR PATHS WILL BE CONFIGURED") - } - - log.Info("Restoring metadata backup") - - mds, err := lr.Datastore(context.TODO(), "/metadata") - if err != nil { - return err - } - - bar := pb.New64(st.Size()) - br := bar.NewProxyReader(f) - bar.ShowTimeLeft = true - bar.ShowPercent = true - bar.ShowSpeed = true - bar.Units = pb.U_BYTES - - bar.Start() - err = backupds.RestoreInto(br, mds) - bar.Finish() - - if err != nil { - return xerrors.Errorf("restoring metadata: %w", err) - } - - log.Info("Checking actor metadata") - - abytes, err := mds.Get(datastore.NewKey("miner-address")) - if err != nil { - return xerrors.Errorf("getting actor address from metadata datastore: %w", err) - } - - maddr, err := address.NewFromBytes(abytes) - if err != nil { - return xerrors.Errorf("parsing actor address: %w", err) - } - - log.Info("ACTOR ADDRESS: ", maddr.String()) - - mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - log.Info("SECTOR SIZE: ", units.BytesSize(float64(mi.SectorSize))) - - wk, err := api.StateAccountKey(ctx, mi.Worker, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("resolving worker key: %w", err) - } - - has, err := api.WalletHas(ctx, wk) - if err != nil { - return xerrors.Errorf("checking worker address: %w", err) - } - - if !has { - return xerrors.Errorf("worker address %s for miner actor %s not present in full node wallet", mi.Worker, maddr) - } - - log.Info("Checking proof parameters") - - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(mi.SectorSize)); err != nil { - return xerrors.Errorf("fetching proof parameters: %w", err) - } - - log.Info("Initializing libp2p identity") - - p2pSk, err := makeHostKey(lr) - if err != nil { - return xerrors.Errorf("make host key: %w", err) - } - - peerid, err := peer.IDFromPrivateKey(p2pSk) - if err != nil { - return xerrors.Errorf("peer ID from private key: %w", err) - } - - log.Info("Configuring miner actor") - - if err := configureStorageMiner(ctx, api, maddr, peerid, big.Zero()); err != nil { + return nil + }); err != nil { return err } diff --git a/cmd/lotus-storage-miner/init_service.go b/cmd/lotus-storage-miner/init_service.go new file mode 100644 index 000000000..99bd7ef84 --- /dev/null +++ b/cmd/lotus-storage-miner/init_service.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "strings" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/client" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + lcli "github.com/filecoin-project/lotus/cli" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/node/config" + "github.com/libp2p/go-libp2p-core/peer" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +func checkApiInfo(ctx context.Context, ai string) (string, error) { + ai = strings.TrimPrefix(strings.TrimSpace(ai), "MINER_API_INFO=") + info := cliutil.ParseApiInfo(ai) + addr, err := info.DialArgs("v0") + if err != nil { + return "", xerrors.Errorf("could not get DialArgs: %w", err) + } + + log.Infof("Checking api version of %s", addr) + + api, closer, err := client.NewStorageMinerRPCV0(ctx, addr, info.AuthHeader()) + if err != nil { + return "", err + } + defer closer() + + v, err := api.Version(ctx) + if err != nil { + return "", xerrors.Errorf("checking version: %w", err) + } + + //TODO(anteva): bump api version? + if !v.APIVersion.EqMajorMinor(lapi.MinerAPIVersion0) { + return "", xerrors.Errorf("remote service API version didn't match (expected %s, remote %s)", lapi.MinerAPIVersion0, v.APIVersion) + } + + return ai, nil +} + +var serviceCmd = &cli.Command{ + Name: "service", + Usage: "Initialize a lotus miner sub-service", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Usage: "config file (config.toml)", + Required: true, + }, + &cli.StringFlag{ + Name: "storage-config", + Usage: "storage paths config (storage.json)", + Required: true, + }, + &cli.BoolFlag{ + Name: "nosync", + Usage: "don't check full-node sync status", + }, + + &cli.BoolFlag{ + Name: "enable-market", + Usage: "enable market module", + }, + + &cli.StringFlag{ + Name: "api-sealer", + Usage: "sealer API info (lotus-miner auth api-info --perm=admin)", + }, + &cli.StringFlag{ + Name: "api-sector-index", + Usage: "sector Index API info (lotus-miner auth api-info --perm=admin)", + }, + }, + ArgsUsage: "[backupFile]", + Action: func(cctx *cli.Context) error { + ctx := lcli.ReqContext(cctx) + log.Info("Initializing lotus miner service") + + if !cctx.Bool("enable-market") { + return xerrors.Errorf("at least one module must be enabled") + } + + if !cctx.IsSet("api-sealer") { + return xerrors.Errorf("--api-sealer is required without the sealer module enabled") + } + if !cctx.IsSet("api-sector-index") { + return xerrors.Errorf("--api-sector-index is required without the sector storage module enabled") + } + + if err := restore(ctx, cctx, func(cfg *config.StorageMiner) error { + cfg.Subsystems.EnableStorageMarket = cctx.Bool("enable-market") + cfg.Subsystems.EnableMining = false + cfg.Subsystems.EnableSealing = false + cfg.Subsystems.EnableSectorStorage = false + + if !cfg.Subsystems.EnableSealing { + ai, err := checkApiInfo(ctx, cctx.String("api-sealer")) + if err != nil { + return xerrors.Errorf("checking sealer API: %w", err) + } + cfg.Subsystems.SealerApiInfo = ai + } + + if !cfg.Subsystems.EnableSectorStorage { + ai, err := checkApiInfo(ctx, cctx.String("api-sector-index")) + if err != nil { + return xerrors.Errorf("checking sector index API: %w", err) + } + cfg.Subsystems.SectorIndexApiInfo = ai + } + + return nil + }, func(api lapi.FullNode, maddr address.Address, peerid peer.ID, mi miner.MinerInfo) error { + if cctx.Bool("enable-market") { + log.Info("Configuring miner actor") + + if err := configureStorageMiner(ctx, api, maddr, peerid, big.Zero()); err != nil { + return err + } + } + + return nil + }); err != nil { + return err + } + + return nil + }, +}