lotus/cmd/lotus-storage-miner/init.go

614 lines
16 KiB
Go
Raw Normal View History

2019-07-19 09:24:11 +00:00
package main
import (
2019-12-07 14:12:10 +00:00
"bytes"
"context"
2019-10-03 00:02:06 +00:00
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
2019-11-16 06:47:04 +00:00
"os"
2019-11-25 04:45:13 +00:00
"path/filepath"
"strconv"
2019-11-16 06:47:04 +00:00
"github.com/filecoin-project/go-address"
cborutil "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-fil-markets/storagemarket"
deals "github.com/filecoin-project/go-fil-markets/storagemarket/impl"
2020-01-02 19:08:49 +00:00
paramfetch "github.com/filecoin-project/go-paramfetch"
"github.com/filecoin-project/go-sectorbuilder"
2019-11-16 06:47:04 +00:00
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/namespace"
badger "github.com/ipfs/go-ds-badger2"
2019-11-16 06:47:04 +00:00
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/mitchellh/go-homedir"
"golang.org/x/xerrors"
"gopkg.in/urfave/cli.v2"
2019-07-19 09:24:11 +00:00
lapi "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/genesis"
"github.com/filecoin-project/lotus/markets/utils"
2019-11-25 04:45:13 +00:00
"github.com/filecoin-project/lotus/miner"
"github.com/filecoin-project/lotus/node/config"
2019-11-25 04:45:13 +00:00
"github.com/filecoin-project/lotus/node/modules"
"github.com/filecoin-project/lotus/node/modules/dtypes"
"github.com/filecoin-project/lotus/node/repo"
2019-11-25 04:45:13 +00:00
"github.com/filecoin-project/lotus/storage"
"github.com/filecoin-project/lotus/storage/sealing"
2019-07-19 09:24:11 +00:00
)
var initCmd = &cli.Command{
Name: "init",
Usage: "Initialize a lotus storage miner repo",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "actor",
Usage: "specify the address of an already created miner actor",
},
&cli.BoolFlag{
2019-09-26 20:57:20 +00:00
Name: "genesis-miner",
Usage: "enable genesis mining (DON'T USE ON BOOTSTRAPPED NETWORK)",
Hidden: true,
},
&cli.BoolFlag{
Name: "create-worker-key",
Usage: "create separate worker key",
},
&cli.StringFlag{
2019-09-26 20:57:20 +00:00
Name: "worker",
Aliases: []string{"w"},
2019-09-26 20:57:20 +00:00
Usage: "worker key to use (overrides --create-worker-key)",
},
&cli.StringFlag{
2019-09-26 20:57:20 +00:00
Name: "owner",
Aliases: []string{"o"},
2019-09-26 20:57:20 +00:00
Usage: "owner key to use",
},
&cli.Uint64Flag{
Name: "sector-size",
Usage: "specify sector size to use",
Value: build.SectorSizes[0],
},
2019-11-25 04:45:13 +00:00
&cli.StringFlag{
Name: "pre-sealed-sectors",
Usage: "specify set of presealed sectors for starting as a genesis miner",
},
&cli.StringFlag{
Name: "pre-sealed-metadata",
Usage: "specify the metadata file for the presealed sectors",
},
2019-12-04 16:53:32 +00:00
&cli.BoolFlag{
Name: "nosync",
Usage: "don't check full-node sync status",
},
&cli.BoolFlag{
Name: "symlink-imported-sectors",
Usage: "attempt to symlink to presealed sectors instead of copying them into place",
},
},
2019-07-19 09:24:11 +00:00
Action: func(cctx *cli.Context) error {
log.Info("Initializing lotus storage miner")
2019-12-04 19:44:15 +00:00
ssize := cctx.Uint64("sector-size")
symlink := cctx.Bool("symlink-imported-sectors")
if symlink {
log.Info("will attempt to symlink to imported sectors")
}
2019-10-02 17:20:30 +00:00
log.Info("Checking proof parameters")
2020-01-30 22:50:24 +00:00
if err := paramfetch.GetParams(build.ParametersJson(), ssize); err != nil {
2019-10-02 17:20:30 +00:00
return xerrors.Errorf("fetching proof parameters: %w", err)
}
2019-12-04 00:25:18 +00:00
log.Info("Trying to connect to full node RPC")
api, closer, err := lcli.GetFullNodeAPI(cctx) // TODO: consider storing full node address in config
if err != nil {
return err
}
defer closer()
ctx := lcli.ReqContext(cctx)
log.Info("Checking full node sync status")
2019-12-04 16:53:32 +00:00
if !cctx.Bool("genesis-miner") && !cctx.Bool("nosync") {
2019-12-04 00:25:18 +00:00
if err := lcli.SyncWait(ctx, api); err != nil {
return xerrors.Errorf("sync wait: %w", err)
}
}
2019-10-02 20:29:40 +00:00
log.Info("Checking if repo exists")
repoPath := cctx.String(FlagStorageRepo)
r, err := repo.NewFS(repoPath)
2019-07-19 09:24:11 +00:00
if err != nil {
return err
}
2019-07-23 21:54:54 +00:00
ok, err := r.Exists()
if err != nil {
return err
}
if ok {
2019-07-23 21:50:52 +00:00
return xerrors.Errorf("repo at '%s' is already initialized", cctx.String(FlagStorageRepo))
2019-07-19 09:24:11 +00:00
}
log.Info("Checking full node version")
v, err := api.Version(ctx)
if err != nil {
return err
}
if !v.APIVersion.EqMajorMinor(build.APIVersion) {
return xerrors.Errorf("Remote API version didn't match (local %s, remote %s)", build.APIVersion, v.APIVersion)
}
log.Info("Initializing repo")
2019-11-12 17:59:38 +00:00
if err := r.Init(repo.StorageMiner); err != nil {
2019-07-19 09:24:11 +00:00
return err
}
2019-11-25 04:45:13 +00:00
if pssb := cctx.String("pre-sealed-sectors"); pssb != "" {
2019-12-02 22:10:22 +00:00
pssb, err := homedir.Expand(pssb)
if err != nil {
return err
}
2019-11-25 04:45:13 +00:00
log.Infof("moving pre-sealed-sectors from %s into newly created storage miner repo", pssb)
lr, err := r.Lock(repo.StorageMiner)
if err != nil {
return err
}
mds, err := lr.Datastore("/metadata")
if err != nil {
2019-11-25 04:45:13 +00:00
return err
}
bopts := badger.DefaultOptions
bopts.ReadOnly = true
oldmds, err := badger.NewDatastore(filepath.Join(pssb, "badger"), &bopts)
2019-11-30 23:17:50 +00:00
if err != nil {
return err
}
oldsb, err := sectorbuilder.New(&sectorbuilder.Config{
2019-12-04 19:44:15 +00:00
SectorSize: ssize,
WorkerThreads: 2,
Paths: sectorbuilder.SimplePath(pssb),
}, namespace.Wrap(oldmds, datastore.NewKey("/sectorbuilder")))
if err != nil {
return xerrors.Errorf("failed to open up preseal sectorbuilder: %w", err)
}
2019-11-30 23:17:50 +00:00
nsb, err := sectorbuilder.New(&sectorbuilder.Config{
2019-12-04 19:44:15 +00:00
SectorSize: ssize,
WorkerThreads: 2,
Paths: sectorbuilder.SimplePath(lr.Path()),
}, namespace.Wrap(mds, datastore.NewKey("/sectorbuilder")))
if err != nil {
return xerrors.Errorf("failed to open up sectorbuilder: %w", err)
}
2019-11-30 23:17:50 +00:00
if err := nsb.ImportFrom(oldsb, symlink); err != nil {
return err
}
if err := lr.Close(); err != nil {
return xerrors.Errorf("unlocking repo after preseal migration: %w", err)
}
2019-11-25 04:45:13 +00:00
}
2019-11-16 06:47:04 +00:00
if err := storageMinerInit(ctx, cctx, api, r); err != nil {
log.Errorf("Failed to initialize lotus-storage-miner: %+v", err)
2019-11-16 06:47:04 +00:00
path, err := homedir.Expand(repoPath)
if err != nil {
return err
}
2019-11-16 06:47:04 +00:00
log.Infof("Cleaning up %s after attempt...", path)
if err := os.RemoveAll(path); err != nil {
log.Errorf("Failed to clean up failed storage repo: %s", err)
}
return xerrors.Errorf("Storage-miner init failed")
2019-07-25 12:50:34 +00:00
}
// TODO: Point to setting storage price, maybe do it interactively or something
log.Info("Storage miner successfully created, you can now start it with 'lotus-storage-miner run'")
2019-07-25 12:50:34 +00:00
return nil
},
}
2019-07-25 12:50:34 +00:00
func migratePreSealMeta(ctx context.Context, api lapi.FullNode, metadata string, maddr address.Address, mds dtypes.MetadataDS) error {
metadata, err := homedir.Expand(metadata)
2019-11-30 09:25:31 +00:00
if err != nil {
return xerrors.Errorf("expanding preseal dir: %w", err)
}
b, err := ioutil.ReadFile(metadata)
if err != nil {
return xerrors.Errorf("reading preseal metadata: %w", err)
}
preseals := map[string]genesis.GenesisMiner{}
if err := json.Unmarshal(b, &preseals); err != nil {
return xerrors.Errorf("unmarshaling preseal metadata: %w", err)
}
meta, ok := preseals[maddr.String()]
if !ok {
return xerrors.New("got wrong preseal info")
}
for _, sector := range meta.Sectors {
sectorKey := datastore.NewKey(sealing.SectorStorePrefix).ChildString(fmt.Sprint(sector.SectorID))
dealID, err := findMarketDealID(ctx, api, sector.Deal)
if err != nil {
return xerrors.Errorf("finding storage deal for pre-sealed sector %d: %w", sector.SectorID, err)
}
info := &sealing.SectorInfo{
State: lapi.Proving,
SectorID: sector.SectorID,
Pieces: []sealing.Piece{
{
DealID: dealID,
Size: meta.SectorSize,
CommP: sector.CommD[:],
},
},
CommD: sector.CommD[:],
CommR: sector.CommR[:],
Proof: nil,
Ticket: sealing.SealTicket{},
PreCommitMessage: nil,
Seed: sealing.SealSeed{},
CommitMessage: nil,
}
b, err := cborutil.Dump(info)
if err != nil {
return err
}
if err := mds.Put(sectorKey, b); err != nil {
return err
}
2019-12-07 14:12:10 +00:00
proposalCid, err := sector.Deal.Cid()
if err != nil {
return err
}
dealKey := datastore.NewKey(deals.ProviderDsPrefix).ChildString(proposalCid.String())
proposal, err := utils.ToSharedStorageDealProposal(&sector.Deal)
if err != nil {
return err
}
deal := &deals.MinerDeal{
MinerDeal: storagemarket.MinerDeal{
Proposal: *proposal,
ProposalCid: proposalCid,
State: lapi.DealComplete,
Ref: proposalCid, // TODO: This is super wrong, but there
// are no params for CommP CIDs, we can't recover unixfs cid easily,
// and this isn't even used after the deal enters Complete state
DealID: dealID,
SectorID: sector.SectorID,
},
}
b, err = cborutil.Dump(deal)
if err != nil {
return err
}
if err := mds.Put(dealKey, b); err != nil {
return err
}
}
return nil
}
2019-12-07 14:12:10 +00:00
func findMarketDealID(ctx context.Context, api lapi.FullNode, deal actors.StorageDealProposal) (uint64, error) {
// TODO: find a better way
// (this is only used by genesis miners)
deals, err := api.StateMarketDeals(ctx, types.EmptyTSK)
if err != nil {
return 0, xerrors.Errorf("getting market deals: %w", err)
}
for k, v := range deals {
2019-12-07 14:12:10 +00:00
if bytes.Equal(v.PieceRef, deal.PieceRef) {
return strconv.ParseUint(k, 10, 64)
}
}
return 0, xerrors.New("deal not found")
}
func storageMinerInit(ctx context.Context, cctx *cli.Context, api lapi.FullNode, r repo.Repo) error {
2019-11-16 06:47:04 +00:00
lr, err := r.Lock(repo.StorageMiner)
if err != nil {
return err
}
defer lr.Close()
log.Info("Initializing libp2p identity")
p2pSk, err := makeHostKey(lr)
if err != nil {
2019-11-15 03:19:16 +00:00
return xerrors.Errorf("make host key: %w", err)
}
peerid, err := peer.IDFromPrivateKey(p2pSk)
if err != nil {
2019-11-15 03:19:16 +00:00
return xerrors.Errorf("peer ID from private key: %w", err)
}
2019-07-25 12:50:34 +00:00
mds, err := lr.Datastore("/metadata")
if err != nil {
return err
}
var addr address.Address
if act := cctx.String("actor"); act != "" {
a, err := address.NewFromString(act)
2019-07-25 12:50:34 +00:00
if err != nil {
2019-11-15 03:19:16 +00:00
return xerrors.Errorf("failed parsing actor flag value (%q): %w", act, err)
2019-07-25 12:50:34 +00:00
}
2019-11-25 04:45:13 +00:00
if cctx.Bool("genesis-miner") {
if err := mds.Put(datastore.NewKey("miner-address"), a.Bytes()); err != nil {
return err
}
c, err := lr.Config()
if err != nil {
return err
}
cfg, ok := c.(*config.StorageMiner)
if !ok {
return xerrors.Errorf("invalid config from repo, got: %T", c)
}
scfg := sectorbuilder.SimplePath(lr.Path())
if len(cfg.SectorBuilder.Storage) > 0 {
scfg = cfg.SectorBuilder.Storage
}
sbcfg, err := modules.SectorBuilderConfig(scfg, 2, false, false)(mds, api)
2019-11-25 04:45:13 +00:00
if err != nil {
return xerrors.Errorf("getting genesis miner sector builder config: %w", err)
}
sb, err := sectorbuilder.New(sbcfg, mds)
if err != nil {
return xerrors.Errorf("failed to set up sectorbuilder for genesis mining: %w", err)
}
epp := storage.NewElectionPoStProver(sb)
m := miner.NewMiner(api, epp)
{
if err := m.Register(a); err != nil {
return xerrors.Errorf("failed to start up genesis miner: %w", err)
}
defer func() {
if err := m.Unregister(ctx, a); err != nil {
log.Error("failed to shut down storage miner: ", err)
}
}()
if err := configureStorageMiner(ctx, api, a, peerid); err != nil {
return xerrors.Errorf("failed to configure storage miner: %w", err)
}
}
return nil
}
if pssb := cctx.String("pre-sealed-metadata"); pssb != "" {
pssb, err := homedir.Expand(pssb)
if err != nil {
return err
}
log.Infof("Importing pre-sealed sector metadata for %s", a)
if err := migratePreSealMeta(ctx, api, pssb, a, mds); err != nil {
return xerrors.Errorf("migrating presealed sector metadata: %w", err)
}
2019-12-02 12:51:16 +00:00
}
if err := configureStorageMiner(ctx, api, a, peerid); err != nil {
return xerrors.Errorf("failed to configure storage miner: %w", err)
}
addr = a
} else {
a, err := createStorageMiner(ctx, api, peerid, cctx)
if err != nil {
2019-07-25 12:50:34 +00:00
return err
}
addr = a
}
2019-07-25 12:50:34 +00:00
log.Infof("Created new storage miner: %s", addr)
2019-11-25 04:45:13 +00:00
if err := mds.Put(datastore.NewKey("miner-address"), addr.Bytes()); err != nil {
return err
}
return nil
}
2019-07-25 12:50:34 +00:00
2019-10-03 00:02:06 +00:00
func makeHostKey(lr repo.LockedRepo) (crypto.PrivKey, error) {
pk, _, err := crypto.GenerateEd25519Key(rand.Reader)
if err != nil {
return nil, err
}
ks, err := lr.KeyStore()
if err != nil {
return nil, err
}
kbytes, err := pk.Bytes()
if err != nil {
return nil, err
}
if err := ks.Put("libp2p-host", types.KeyInfo{
Type: "libp2p-host",
PrivateKey: kbytes,
}); err != nil {
return nil, err
}
return pk, nil
}
func configureStorageMiner(ctx context.Context, api lapi.FullNode, addr address.Address, peerid peer.ID) error {
// This really just needs to be an api call at this point...
recp, err := api.StateCall(ctx, &types.Message{
To: addr,
From: addr,
Method: actors.MAMethods.GetWorkerAddr,
}, types.EmptyTSK)
if err != nil {
return xerrors.Errorf("failed to get worker address: %w", err)
}
if recp.ExitCode != 0 {
return xerrors.Errorf("getWorkerAddr returned exit code %d", recp.ExitCode)
}
waddr, err := address.NewFromBytes(recp.Return)
if err != nil {
return xerrors.Errorf("getWorkerAddr returned bad address: %w", err)
}
enc, err := actors.SerializeParams(&actors.UpdatePeerIDParams{PeerID: peerid})
if err != nil {
return err
}
msg := &types.Message{
To: addr,
From: waddr,
Method: actors.MAMethods.UpdatePeerID,
Params: enc,
2019-08-16 22:10:34 +00:00
Value: types.NewInt(0),
GasPrice: types.NewInt(0),
2019-10-17 00:43:38 +00:00
GasLimit: types.NewInt(100000000),
}
2019-09-17 08:15:26 +00:00
smsg, err := api.MpoolPushMessage(ctx, msg)
if err != nil {
return err
}
2019-08-16 22:10:34 +00:00
log.Info("Waiting for message: ", smsg.Cid())
ret, err := api.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return err
}
if ret.Receipt.ExitCode != 0 {
return xerrors.Errorf("update peer id message failed with exit code %d", ret.Receipt.ExitCode)
}
return nil
}
2019-07-25 12:50:34 +00:00
func createStorageMiner(ctx context.Context, api lapi.FullNode, peerid peer.ID, cctx *cli.Context) (addr address.Address, err error) {
log.Info("Creating StorageMarket.CreateStorageMiner message")
2019-07-25 12:50:34 +00:00
var owner address.Address
if cctx.String("owner") != "" {
owner, err = address.NewFromString(cctx.String("owner"))
} else {
owner, err = api.WalletDefaultAddress(ctx)
}
if err != nil {
return address.Undef, err
}
2019-07-25 12:50:34 +00:00
ssize := cctx.Uint64("sector-size")
worker := owner
if cctx.String("worker") != "" {
worker, err = address.NewFromString(cctx.String("worker"))
} else if cctx.Bool("create-worker-key") { // TODO: Do we need to force this if owner is Secpk?
worker, err = api.WalletNew(ctx, types.KTBLS)
}
// TODO: Transfer some initial funds to worker
if err != nil {
return address.Undef, err
}
2019-07-25 12:50:34 +00:00
collateral, err := api.StatePledgeCollateral(ctx, types.EmptyTSK)
if err != nil {
return address.Undef, err
}
2019-07-25 12:50:34 +00:00
params, err := actors.SerializeParams(&actors.CreateStorageMinerParams{
Owner: owner,
Worker: worker,
SectorSize: ssize,
PeerID: peerid,
})
if err != nil {
return address.Undef, err
}
2019-07-25 12:50:34 +00:00
2019-09-17 08:15:26 +00:00
createStorageMinerMsg := &types.Message{
2019-10-19 08:06:41 +00:00
To: actors.StoragePowerAddress,
From: owner,
Value: types.BigAdd(collateral, types.BigDiv(collateral, types.NewInt(100))),
2019-07-25 12:50:34 +00:00
Method: actors.SPAMethods.CreateStorageMiner,
Params: params,
2019-10-17 00:43:38 +00:00
GasLimit: types.NewInt(10000000),
GasPrice: types.NewInt(0),
}
2019-07-25 12:50:34 +00:00
2019-09-17 08:15:26 +00:00
signed, err := api.MpoolPushMessage(ctx, createStorageMinerMsg)
if err != nil {
return address.Undef, err
}
2019-09-17 08:15:26 +00:00
log.Infof("Pushed StorageMarket.CreateStorageMiner, %s to Mpool", signed.Cid())
log.Infof("Waiting for confirmation")
mw, err := api.StateWaitMsg(ctx, signed.Cid())
if err != nil {
return address.Undef, err
}
if mw.Receipt.ExitCode != 0 {
return address.Undef, xerrors.Errorf("create storage miner failed: exit code %d", mw.Receipt.ExitCode)
}
addr, err = address.NewFromBytes(mw.Receipt.Return)
if err != nil {
return address.Undef, err
}
log.Infof("New storage miners address is: %s", addr)
return addr, nil
2019-07-19 09:24:11 +00:00
}