commands

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2022 License: Apache-2.0, MIT Imports: 68 Imported by: 1

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AppHelpTemplate = `` /* 600-byte string literal not displayed */
View Source
var ChainCmd = &cli.Command{
	Name:  "chain",
	Usage: "Interact with filecoin blockchain",
	Subcommands: []*cli.Command{
		ChainHeadCmd,
		ChainGetBlock,
		ChainReadObjCmd,
		ChainStatObjCmd,
		ChainGetMsgCmd,
		ChainListCmd,
		ChainSetHeadCmd,
	},
}
View Source
var ChainGetBlock = &cli.Command{
	Name:      "getblock",
	Usage:     "Get a block and print its details",
	ArgsUsage: "[blockCid]",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.BoolFlag{
				Name:  "raw",
				Usage: "print just the raw block header",
			},
		}),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		if !cctx.Args().Present() {
			return fmt.Errorf("must pass cid of block to print")
		}

		bcid, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return err
		}

		blk, err := lapi.ChainGetBlock(ctx, bcid)
		if err != nil {
			return xerrors.Errorf("get block failed: %w", err)
		}

		if cctx.Bool("raw") {
			out, err := json.MarshalIndent(blk, "", "  ")
			if err != nil {
				return err
			}

			fmt.Println(string(out))
			return nil
		}

		msgs, err := lapi.ChainGetBlockMessages(ctx, bcid)
		if err != nil {
			return xerrors.Errorf("failed to get messages: %w", err)
		}

		pmsgs, err := lapi.ChainGetParentMessages(ctx, bcid)
		if err != nil {
			return xerrors.Errorf("failed to get parent messages: %w", err)
		}

		recpts, err := lapi.ChainGetParentReceipts(ctx, bcid)
		if err != nil {
			log.Warn(err)

		}

		cblock := struct {
			types.BlockHeader
			BlsMessages    []*types.Message
			SecpkMessages  []*types.SignedMessage
			ParentReceipts []*types.MessageReceipt
			ParentMessages []cid.Cid
		}{}

		cblock.BlockHeader = *blk
		cblock.BlsMessages = msgs.BlsMessages
		cblock.SecpkMessages = msgs.SecpkMessages
		cblock.ParentReceipts = recpts
		cblock.ParentMessages = apiMsgCids(pmsgs)

		out, err := json.MarshalIndent(cblock, "", "  ")
		if err != nil {
			return err
		}

		fmt.Println(string(out))
		return nil
	},
}
View Source
var ChainGetMsgCmd = &cli.Command{
	Name:      "getmessage",
	Usage:     "Get and print a message by its cid",
	ArgsUsage: "[messageCid]",
	Action: func(cctx *cli.Context) error {
		if !cctx.Args().Present() {
			return fmt.Errorf("must pass a cid of a message to get")
		}

		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		c, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return xerrors.Errorf("failed to parse cid input: %w", err)
		}

		mb, err := lapi.ChainReadObj(ctx, c)
		if err != nil {
			return xerrors.Errorf("failed to read object: %w", err)
		}

		var i interface{}
		m, err := types.DecodeMessage(mb)
		if err != nil {
			sm, err := types.DecodeSignedMessage(mb)
			if err != nil {
				return xerrors.Errorf("failed to decode object as a message: %w", err)
			}
			i = sm
		} else {
			i = m
		}

		enc, err := json.MarshalIndent(i, "", "  ")
		if err != nil {
			return err
		}

		fmt.Println(string(enc))
		return nil
	},
}
View Source
var ChainHeadCmd = &cli.Command{
	Name:  "head",
	Usage: "Print chain head",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		head, err := lapi.ChainHead(ctx)
		if err != nil {
			return err
		}

		for _, c := range head.Cids() {
			fmt.Println(c)
		}
		return nil
	},
}
View Source
var ChainListCmd = &cli.Command{
	Name:    "list",
	Aliases: []string{"love"},
	Usage:   "View a segment of the chain",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.Uint64Flag{Name: "height", DefaultText: "current head"},
			&cli.IntFlag{Name: "count", Value: 30},
			&cli.StringFlag{
				Name:  "format",
				Usage: "specify the format to print out tipsets",
				Value: "<height>: (<time>) <blocks>",
			},
			&cli.BoolFlag{
				Name:  "gas-stats",
				Usage: "view gas statistics for the chain",
			},
		}),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		var head *types.TipSet

		if cctx.IsSet("height") {
			head, err = lapi.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("height")), types.EmptyTSK)
		} else {
			head, err = lapi.ChainHead(ctx)
		}
		if err != nil {
			return err
		}

		count := cctx.Int("count")
		if count < 1 {
			return nil
		}

		tss := make([]*types.TipSet, 0, count)
		tss = append(tss, head)

		for i := 1; i < count; i++ {
			if head.Height() == 0 {
				break
			}

			head, err = lapi.ChainGetTipSet(ctx, head.Parents())
			if err != nil {
				return err
			}

			tss = append(tss, head)
		}

		if cctx.Bool("gas-stats") {
			otss := make([]*types.TipSet, 0, len(tss))
			for i := len(tss) - 1; i >= 0; i-- {
				otss = append(otss, tss[i])
			}
			tss = otss
			for i, ts := range tss {
				pbf := ts.Blocks()[0].ParentBaseFee
				fmt.Printf("%d: %d blocks (baseFee: %s -> maxFee: %s)\n", ts.Height(), len(ts.Blocks()), ts.Blocks()[0].ParentBaseFee, types.FIL(types.BigMul(pbf, types.NewInt(uint64(lotusbuild.BlockGasLimit)))))

				for _, b := range ts.Blocks() {
					msgs, err := lapi.ChainGetBlockMessages(ctx, b.Cid())
					if err != nil {
						return err
					}
					var limitSum int64
					psum := big.NewInt(0)
					for _, m := range msgs.BlsMessages {
						limitSum += m.GasLimit
						psum = big.Add(psum, m.GasPremium)
					}

					for _, m := range msgs.SecpkMessages {
						limitSum += m.Message.GasLimit
						psum = big.Add(psum, m.Message.GasPremium)
					}

					lenmsgs := len(msgs.BlsMessages) + len(msgs.SecpkMessages)

					avgpremium := big.Zero()
					if lenmsgs > 0 {
						avgpremium = big.Div(psum, big.NewInt(int64(lenmsgs)))
					}

					fmt.Printf("\t%s: \t%d msgs, gasLimit: %d / %d (%0.2f%%), avgPremium: %s\n", b.Miner, len(msgs.BlsMessages)+len(msgs.SecpkMessages), limitSum, lotusbuild.BlockGasLimit, 100*float64(limitSum)/float64(lotusbuild.BlockGasLimit), avgpremium)
				}
				if i < len(tss)-1 {
					msgs, err := lapi.ChainGetParentMessages(ctx, tss[i+1].Blocks()[0].Cid())
					if err != nil {
						return err
					}
					var limitSum int64
					for _, m := range msgs {
						limitSum += m.Message.GasLimit
					}

					recpts, err := lapi.ChainGetParentReceipts(ctx, tss[i+1].Blocks()[0].Cid())
					if err != nil {
						return err
					}

					var gasUsed int64
					for _, r := range recpts {
						gasUsed += r.GasUsed
					}

					gasEfficiency := 100 * float64(gasUsed) / float64(limitSum)
					gasCapacity := 100 * float64(limitSum) / float64(lotusbuild.BlockGasLimit)

					fmt.Printf("\ttipset: \t%d msgs, %d (%0.2f%%) / %d (%0.2f%%)\n", len(msgs), gasUsed, gasEfficiency, limitSum, gasCapacity)
				}
				fmt.Println()
			}
		} else {
			for i := len(tss) - 1; i >= 0; i-- {
				printTipSet(cctx.String("format"), tss[i])
			}
		}
		return nil
	},
}
View Source
var ChainReadObjCmd = &cli.Command{
	Name:      "read-obj",
	Usage:     "Read the raw bytes of an object",
	ArgsUsage: "[objectCid]",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		c, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return fmt.Errorf("failed to parse cid input: %s", err)
		}

		obj, err := lapi.ChainReadObj(ctx, c)
		if err != nil {
			return err
		}

		fmt.Printf("%x\n", obj)
		return nil
	},
}
View Source
var ChainSetHeadCmd = &cli.Command{
	Name:      "sethead",
	Usage:     "manually set the local nodes head tipset (Caution: normally only used for recovery)",
	ArgsUsage: "[tipsetkey]",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.BoolFlag{
				Name:  "genesis",
				Usage: "reset head to genesis",
			},
			&cli.Uint64Flag{
				Name:  "epoch",
				Usage: "reset head to given epoch",
			},
		}),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		var ts *types.TipSet

		if cctx.Bool("genesis") {
			ts, err = lapi.ChainGetGenesis(ctx)
		}
		if ts == nil && cctx.IsSet("epoch") {
			ts, err = lapi.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), types.EmptyTSK)
		}
		if ts == nil {
			ts, err = parseTipSet(ctx, lapi, cctx.Args().Slice())
		}
		if err != nil {
			return err
		}

		if ts == nil {
			return fmt.Errorf("must pass cids for tipset to set as head")
		}

		if err := lapi.ChainSetHead(ctx, ts.Key()); err != nil {
			return err
		}

		return nil
	},
}
View Source
var ChainStatObjCmd = &cli.Command{
	Name:      "stat-obj",
	Usage:     "Collect size and ipld link counts for objs",
	ArgsUsage: "[cid]",
	Description: `Collect object size and ipld link count for an object.

   When a base is provided it will be walked first, and all links visisted
   will be ignored when the passed in object is walked.
`,
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.StringFlag{
				Name:  "base",
				Usage: "ignore links found in this obj",
			},
		}),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		obj, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return fmt.Errorf("failed to parse cid input: %s", err)
		}

		base := cid.Undef
		if cctx.IsSet("base") {
			base, err = cid.Decode(cctx.String("base"))
			if err != nil {
				return err
			}
		}

		stats, err := lapi.ChainStatObj(ctx, obj, base)
		if err != nil {
			return err
		}

		fmt.Printf("Links: %d\n", stats.Links)
		fmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size)
		return nil
	},
}
View Source
var DaemonCmd = &cli.Command{
	Name:  "daemon",
	Usage: "Start a lily daemon process.",
	Description: `Starts lily in daemon mode with its own blockstore.
In daemon mode lily synchronizes with the filecoin network and runs jobs such
as walk and watch against its local blockstore. This gives better performance
than operating against an external blockstore but requires more disk space and
memory.

Before starting the daemon for the first time the blockstore must be initialized
and synchronized. Visor can use a copy of an already synchronized Lotus node
repository or can initialize its own empty one and import a snapshot to catch
up to the chain.

To initialize an empty lily blockstore repository and import an initial
snapshot of the chain:

  lily init --repo=<path> --import-snapshot=<url>

This will initialize a blockstore repository at <path> and import chain state
from the snapshot at <url>. The type of snapshot needed depends on the type
of jobs expected to be run by the daemon.

Watch jobs only require current actor state to be imported since incoming
tipsets will be used to keep actor states up to date. The lightweight and full
chain snapshots available at https://docs.filecoin.io/get-started/lotus/chain/
are suitable to initialize the daemon for watch jobs.

Historic walks will require full actor states for the range of heights covered
by the walk. These may be created from an existing Lotus node using the
export command, provided receipts are also included in the export. See the
Lotus documentation for full details.

Once the repository is initialized, start the daemon:

  lily daemon --repo=<path> --config=<path>/config.toml

Visor will connect to the filecoin network and begin synchronizing with the
chain. To check the synchronization status use 'lily sync status' or
'lily sync wait'.

Jobs may be started on the daemon at any time. A watch job will wait for the
daemon to become synchronized before extracting data and will pause if the
daemon falls out of sync. Start a watch using 'lily watch'.

A walk job will start immediately. Start a walk using 'lily walk'. A walk may
only be performed between heights that have been synchronized with the network.

Note that jobs are not persisted between restarts of the daemon. See
'lily help job' for more information on managing jobs being run by the daemon.
`,

	Flags: []cli.Flag{
		clientAPIFlag,
		&cli.StringFlag{
			Name:        "repo",
			Usage:       "Specify path where lily should store chain state.",
			EnvVars:     []string{"LILY_REPO"},
			Value:       "~/.lily",
			Destination: &daemonFlags.repo,
		},
		&cli.BoolFlag{
			Name:        "bootstrap",
			Usage:       "Specify whether to act as a bootstrapper, the initial point of contact for other lily daemons to find peers. If set to `false` lily will not sync to the network. This is useful for troubleshooting.",
			EnvVars:     []string{"LILY_BOOTSTRAP"},
			Value:       true,
			Destination: &daemonFlags.bootstrap,
		},
		&cli.StringFlag{
			Name:        "config",
			Usage:       "Specify path of config file to use.",
			EnvVars:     []string{"LILY_CONFIG"},
			Destination: &daemonFlags.config,
		},
		&cli.StringFlag{
			Name:        "genesis",
			Usage:       "Genesis file to use for first node run.",
			EnvVars:     []string{"LILY_GENESIS"},
			Destination: &daemonFlags.genesis,
		},
		&cli.UintFlag{
			Name:        "blockstore-cache-size",
			EnvVars:     []string{"LILY_BLOCKSTORE_CACHE_SIZE"},
			Value:       0,
			Destination: &cacheFlags.BlockstoreCacheSize,
		},
		&cli.UintFlag{
			Name:        "statestore-cache-size",
			EnvVars:     []string{"LILY_STATESTORE_CACHE_SIZE"},
			Value:       0,
			Destination: &cacheFlags.StatestoreCacheSize,
		},
	},
	Action: func(c *cli.Context) error {
		lotuslog.SetupLogLevels()

		if err := setupLogging(VisorLogFlags); err != nil {
			return xerrors.Errorf("setup logging: %w", err)
		}

		if err := setupMetrics(VisorMetricFlags); err != nil {
			return xerrors.Errorf("setup metrics: %w", err)
		}

		if err := setupTracing(VisorTracingFlags); err != nil {
			return xerrors.Errorf("setup tracing: %w", err)
		}

		ctx := context.Background()
		var err error
		daemonFlags.repo, err = homedir.Expand(daemonFlags.repo)
		if err != nil {
			log.Warnw("could not expand repo location", "error", err)
		} else {
			log.Infof("lily repo: %s", daemonFlags.repo)
		}

		r, err := repo.NewFS(daemonFlags.repo)
		if err != nil {
			return xerrors.Errorf("opening fs repo: %w", err)
		}

		if daemonFlags.config == "" {
			daemonFlags.config = filepath.Join(daemonFlags.repo, "config.toml")
		} else {
			daemonFlags.config, err = homedir.Expand(daemonFlags.config)
			if err != nil {
				log.Warnw("could not expand repo location", "error", err)
			} else {
				log.Infof("lily config: %s", daemonFlags.config)
			}
		}

		if err := config.EnsureExists(daemonFlags.config); err != nil {
			return xerrors.Errorf("ensuring config is present at %q: %w", daemonFlags.config, err)
		}
		r.SetConfigPath(daemonFlags.config)

		err = r.Init(repo.FullNode)
		if err != nil && err != repo.ErrRepoExists {
			return xerrors.Errorf("repo init error: %w", err)
		}

		if err := paramfetch.GetParams(lcli.ReqContext(c), lotusbuild.ParametersJSON(), lotusbuild.SrsJSON(), 0); err != nil {
			return xerrors.Errorf("fetching proof parameters: %w", err)
		}

		var genBytes []byte
		if c.String("genesis") != "" {
			genBytes, err = ioutil.ReadFile(daemonFlags.genesis)
			if err != nil {
				return xerrors.Errorf("reading genesis: %w", err)
			}
		} else {
			genBytes = lotusbuild.MaybeGenesis()
		}

		genesis := node.Options()
		if len(genBytes) > 0 {
			genesis = node.Override(new(lotusmodules.Genesis), lotusmodules.LoadGenesis(genBytes))
		}

		isBootstrapper := false
		shutdown := make(chan struct{})
		liteModeDeps := node.Options()
		var api lily.LilyAPI
		stop, err := node.New(ctx,

			LilyNodeAPIOption(&api),
			node.Override(new(*config.Conf), modules.LoadConf(daemonFlags.config)),
			node.Override(new(*events.Events), modules.NewEvents),
			node.Override(new(*schedule.Scheduler), schedule.NewSchedulerDaemon),
			node.Override(new(*storage.Catalog), modules.NewStorageCatalog),
			node.Override(new(*lutil.CacheConfig), modules.CacheConfig(cacheFlags.BlockstoreCacheSize, cacheFlags.StatestoreCacheSize)),

			node.Override(new(dtypes.Bootstrapper), isBootstrapper),
			node.Override(new(dtypes.ShutdownChan), shutdown),
			node.Base(),
			node.Repo(r),

			node.Override(new(dtypes.UniversalBlockstore), modules.NewCachingUniversalBlockstore),

			node.Override(new(*stmgr.StateManager), modules.StateManager),
			node.Override(new(stmgr.ExecMonitor), modules.NewBufferedExecMonitor),

			genesis,
			liteModeDeps,

			node.ApplyIf(func(s *node.Settings) bool { return c.IsSet("api") },
				node.Override(node.SetApiEndpointKey, func(lr repo.LockedRepo) error {
					apima, err := multiaddr.NewMultiaddr(clientAPIFlags.apiAddr)
					if err != nil {
						return err
					}
					return lr.SetAPIEndpoint(apima)
				})),
			node.ApplyIf(func(s *node.Settings) bool { return !daemonFlags.bootstrap },
				node.Unset(node.RunPeerMgrKey),
				node.Unset(new(*peermgr.PeerMgr)),
			),
		)
		if err != nil {
			return xerrors.Errorf("initializing node: %w", err)
		}

		endpoint, err := r.APIEndpoint()
		if err != nil {
			return xerrors.Errorf("getting api endpoint: %w", err)
		}

		maxAPIRequestSize := int64(0)
		return util.ServeRPC(api, stop, endpoint, shutdown, maxAPIRequestSize)
	},
}
View Source
var ExportChainCmd = &cli.Command{
	Name:        "export",
	Description: "Export chain from repo (requires node to be offline)",
	UsageText: `
Exported chains will include all block headers starting at height 'from' to the genesis block.
This means block headers are not filtered by the 'from' and 'to' flags.
Messages, Receipts, and StateRoots are filtered by the 'from' and 'to' flags.

Some examples:

    lily export --from=100 --to=200 --include-messages=true --include-receipts=true --include-stateroots=false
        - export blocks from 200 to 0.
        - export messages from 200 to 100.
        - export receipts from 200 to 100.
        - no stateroots exported.

    lily export --repo=~/.lily --from=0 --to=200 --include-messages=true --include-receipts=true --include-stateroots=true:
        - export blocks from 200 to 0
        - export messages from 200 to 0
        - export receipts from 200 to 0
        - export stateroots from 200 to 0

    lily export --repo=~/.lily --from=0 --to=200 --include-messages=false --include-receipts=false --include-stateroots=false:
        - export all blocks from 200 to 0
        - no messages exported
        - no receipts exported
        - no stateroots exported
`,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "repo",
			Usage:       "the repo to export chain from",
			Value:       "~/.lily",
			Destination: &chainExportFlags.repo,
		},
		&cli.Uint64Flag{
			Name:        "to",
			Usage:       "inclusive highest epoch to export",
			Required:    true,
			Destination: &chainExportFlags.to,
		},
		&cli.Uint64Flag{
			Name:        "from",
			Usage:       "inclusive lowest epoch to export",
			Required:    true,
			Destination: &chainExportFlags.from,
		},
		&cli.BoolFlag{
			Name:        "include-messages",
			Usage:       "exports messages if true",
			Value:       true,
			Destination: &chainExportFlags.includeMsgs,
		},
		&cli.BoolFlag{
			Name:        "include-receipts",
			Usage:       "exports receipts if true",
			Value:       true,
			Destination: &chainExportFlags.includeRcpt,
		},
		&cli.BoolFlag{
			Name:        "include-stateroots",
			Usage:       "exports stateroots if true",
			Value:       true,
			Destination: &chainExportFlags.includeStrt,
		},
		&cli.StringFlag{
			Name:        "out-file",
			Usage:       "file to export to",
			Value:       "chain_export.car",
			Destination: &chainExportFlags.outFile,
		},
		&cli.BoolFlag{
			Name:        "progress",
			Usage:       "set to show progress bar of export",
			Value:       true,
			Destination: &chainExportFlags.progress,
		},
	},
	Before: func(cctx *cli.Context) error {
		from, to := chainExportFlags.from, chainExportFlags.to
		if to < from {
			return xerrors.Errorf("value of --to (%d) should be >= --from (%d)", to, from)
		}

		return nil
	},
	Action: func(cctx *cli.Context) error {

		ctx := cctx.Context

		path, err := homedir.Expand(chainExportFlags.outFile)
		if err != nil {
			return err
		}
		outFile, err := os.Create(path)
		if err != nil {
			return err
		}
		defer func() {
			if err := outFile.Close(); err != nil {
				log.Errorw("failed to close out file", "error", err.Error())
			}
		}()

		cs, bs, closer, err := openChainAndBlockStores(ctx, chainExportFlags.repo)
		if err != nil {
			return err
		}
		defer closer()

		log.Info("loading export head...")

		exportHead, err := cs.GetTipsetByHeight(ctx, abi.ChainEpoch(chainExportFlags.to), cs.GetHeaviestTipSet(), true)
		if err != nil {
			return err
		}
		log.Infow("loaded export head", "tipset", exportHead.String())
		return export.NewChainExporter(exportHead, bs, outFile, export.ExportConfig{
			MinHeight:         chainExportFlags.from,
			IncludeMessages:   chainExportFlags.includeMsgs,
			IncludeReceipts:   chainExportFlags.includeRcpt,
			IncludeStateRoots: chainExportFlags.includeStrt,
			ShowProcess:       chainExportFlags.progress,
		}).Export(ctx)
	},
}
View Source
var GapCmd = &cli.Command{
	Name:  "gap",
	Usage: "Launch gap filling and finding jobs",
	Subcommands: []*cli.Command{
		GapFillCmd,
		GapFindCmd,
	},
}
View Source
var GapFillCmd = &cli.Command{
	Name:  "fill",
	Usage: "Fill gaps in the database",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "api",
			Usage:       "Address of lily api in multiaddr format.",
			EnvVars:     []string{"LILY_API"},
			Value:       "/ip4/127.0.0.1/tcp/1234",
			Destination: &gapFlags.apiAddr,
		},
		&cli.StringFlag{
			Name:        "api-token",
			Usage:       "Authentication token for lily api.",
			EnvVars:     []string{"LILY_API_TOKEN"},
			Value:       "",
			Destination: &gapFlags.apiToken,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Name of storage that results will be written to.",
			EnvVars:     []string{"LILY_STORAGE"},
			Value:       "",
			Destination: &gapFlags.storage,
		},
		&cli.StringFlag{
			Name:        "tasks",
			Usage:       "Comma separated list of tasks to fill. Each task is reported separately in the database. If empty all task will be filled.",
			EnvVars:     []string{"LILY_TASKS"},
			Value:       "",
			Destination: &gapFlags.tasks,
		},
		&cli.StringFlag{
			Name:        "name",
			Usage:       "Name of job for easy identification later.",
			EnvVars:     []string{"LILY_JOB_NAME"},
			Value:       "",
			Destination: &gapFlags.name,
		},
		&cli.Uint64Flag{
			Name:        "to",
			Usage:       "to epoch to search for gaps in",
			EnvVars:     []string{"LILY_TO"},
			Destination: &gapFlags.to,
			Required:    true,
		},
		&cli.Uint64Flag{
			Name:        "from",
			Usage:       "from epoch to search for gaps in",
			EnvVars:     []string{"LILY_FROM"},
			Destination: &gapFlags.from,
			Required:    true,
		},
	},
	Before: func(cctx *cli.Context) error {
		from, to := gapFlags.from, gapFlags.to
		if to < from {
			return xerrors.Errorf("value of --to (%d) should be >= --from (%d)", to, from)
		}

		return nil
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx, gapFlags.apiAddr, gapFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		var tasks []string
		if gapFlags.tasks == "" {
			tasks = indexer.AllTableTasks
		} else {
			tasks = strings.Split(gapFlags.tasks, ",")
		}

		fillName := fmt.Sprintf("fill_%d", time.Now().Unix())
		if gapFlags.name != "" {
			fillName = gapFlags.name
		}

		res, err := api.LilyGapFill(ctx, &lily.LilyGapFillConfig{
			RestartOnFailure:    false,
			RestartOnCompletion: false,
			RestartDelay:        0,
			Storage:             gapFlags.storage,
			Name:                fillName,
			Tasks:               tasks,
			To:                  gapFlags.to,
			From:                gapFlags.from,
		})
		if err != nil {
			return err
		}
		if err := printNewJob(os.Stdout, res); err != nil {
			return err
		}
		return nil
	},
}
View Source
var GapFindCmd = &cli.Command{
	Name:  "find",
	Usage: "find gaps in the database",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "api",
			Usage:       "Address of lily api in multiaddr format.",
			EnvVars:     []string{"LILY_API"},
			Value:       "/ip4/127.0.0.1/tcp/1234",
			Destination: &gapFlags.apiAddr,
		},
		&cli.StringFlag{
			Name:        "api-token",
			Usage:       "Authentication token for lily api.",
			EnvVars:     []string{"LILY_API_TOKEN"},
			Value:       "",
			Destination: &gapFlags.apiToken,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Name of storage that results will be written to.",
			Value:       "",
			Destination: &gapFlags.storage,
		},
		&cli.StringFlag{
			Name:        "name",
			Usage:       "Name of job for easy identification later.",
			Value:       "",
			Destination: &gapFlags.name,
		},
		&cli.StringFlag{
			Name:        "tasks",
			Usage:       "Comma separated list of tasks to fill. Each task is reported separately in the database. If empty all task will be filled.",
			Value:       "",
			Destination: &gapFlags.tasks,
		},
		&cli.Uint64Flag{
			Name:        "to",
			Usage:       "to epoch to search for gaps in",
			Destination: &gapFlags.to,
			Required:    true,
		},
		&cli.Uint64Flag{
			Name:        "from",
			Usage:       "from epoch to search for gaps in",
			Destination: &gapFlags.from,
			Required:    true,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx, gapFlags.apiAddr, gapFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		findName := fmt.Sprintf("find_%d", time.Now().Unix())
		if gapFlags.name != "" {
			findName = gapFlags.name
		}

		var tasks []string
		if gapFlags.tasks == "" {
			tasks = indexer.AllTableTasks
		} else {
			tasks = strings.Split(gapFlags.tasks, ",")
		}

		res, err := api.LilyGapFind(ctx, &lily.LilyGapFindConfig{
			RestartOnFailure:    false,
			RestartOnCompletion: false,
			RestartDelay:        0,
			Storage:             gapFlags.storage,
			Tasks:               tasks,
			Name:                findName,
			To:                  gapFlags.to,
			From:                gapFlags.from,
		})
		if err != nil {
			return err
		}
		if err := printNewJob(os.Stdout, res); err != nil {
			return err
		}
		return nil
	},
}
View Source
var HelpCmd = &cli.Command{
	Name:      "help",
	Aliases:   []string{"h"},
	Usage:     "Shows a list of commands or help for one command",
	ArgsUsage: "[command]",
	Subcommands: []*cli.Command{
		HelpModelsListCmd,
		HelpModelsDescribeCmd,
	},
	Action: func(c *cli.Context) error {
		args := c.Args()
		if args.Present() {
			return ShowCommandHelp(c, args.First())
		}

		_ = cli.ShowAppHelp(c)
		return nil
	},
}
View Source
var HelpModelsDescribeCmd = &cli.Command{
	Name: "models-describe",
	Action: func(cctx *cli.Context) error {
		if cctx.Args().First() == "" {
			return xerrors.Errorf("model name required, run `lily help models-list`, to see all available models")
		}
		mname := cctx.Args().First()
		if _, found := indexer.TableLookup[mname]; !found {
			return xerrors.Errorf("model %s doesn't exist", mname)
		}

		modelFields := indexer.TableFieldComments[mname]
		t := table.NewWriter()
		t.AppendHeader(table.Row{"Fields", "Description"})
		t.SortBy([]table.SortBy{
			{Name: "Fields", Mode: table.Asc}})
		t.SetCaption(indexer.TableComment[mname])
		for field, comment := range modelFields {
			t.AppendRow(table.Row{field, comment})
			t.AppendSeparator()
		}
		fmt.Println(t.Render())
		return nil
	},
}
View Source
var HelpModelsListCmd = &cli.Command{
	Name: "models-list",
	Action: func(cctx *cli.Context) error {
		t := table.NewWriter()
		t.AppendHeader(table.Row{"Model", "Description"})
		for _, m := range indexer.AllTableTasks {
			comment := indexer.TableComment[m]
			t.AppendRow(table.Row{m, comment})
			t.AppendSeparator()
		}
		fmt.Println(t.Render())
		return nil
	},
}
View Source
var IndexCmd = &cli.Command{
	Name:  "index",
	Usage: "Index the state of a tipset from the filecoin blockchain.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "tasks",
			Usage:       "Comma separated list of tasks to run. Each task is reported separately in the database.",
			EnvVars:     []string{"LILY_TASKS"},
			Destination: &indexFlags.tasks,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Name of storage that results will be written to.",
			EnvVars:     []string{"LILY_STORAGE"},
			Value:       "",
			Destination: &indexFlags.storage,
		},
		&cli.StringFlag{
			Name:        "api",
			Usage:       "Address of lily api in multiaddr format.",
			EnvVars:     []string{"LILY_API"},
			Value:       "/ip4/127.0.0.1/tcp/1234",
			Destination: &indexFlags.apiAddr,
		},
		&cli.StringFlag{
			Name:        "api-token",
			Usage:       "Authentication token for lily api.",
			EnvVars:     []string{"LILY_API_TOKEN"},
			Value:       "",
			Destination: &indexFlags.apiToken,
		},
		&cli.StringFlag{
			Name:        "name",
			Usage:       "Name of job for easy identification later.",
			EnvVars:     []string{"LILY_JOB_NAME"},
			Value:       "",
			Destination: &indexFlags.name,
		},
		&cli.DurationFlag{
			Name:        "window",
			Usage:       "Duration after which any indexing work not completed will be marked incomplete",
			EnvVars:     []string{"LILY_WINDOW"},
			Value:       builtin.EpochDurationSeconds * time.Second,
			Destination: &indexFlags.window,
		},
	},
	Subcommands: []*cli.Command{
		IndexTipSetCmd,
		IndexHeightCmd,
	},
}
View Source
var IndexHeightCmd = &cli.Command{
	Name:  "height",
	Usage: "Index the state of a tipset from the filecoin blockchain by height",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		indexName := fmt.Sprintf("index_%d", time.Now().Unix())
		if indexFlags.name != "" {
			indexName = indexFlags.name
		}

		var tsStr string
		if tsStr = cctx.Args().First(); tsStr == "" {
			return xerrors.Errorf("height argument required")
		}

		api, closer, err := GetAPI(ctx, indexFlags.apiAddr, indexFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		height, err := strconv.ParseInt(cctx.Args().First(), 10, 46)
		if err != nil {
			return err
		}
		ts, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(height), types.EmptyTSK)
		if err != nil {
			return err
		}

		if height != int64(ts.Height()) {
			log.Warnf("height (%d) is null round, indexing height %d", height, ts.Height())
		}

		tasks := strings.Split(indexFlags.tasks, ",")
		if indexFlags.tasks == "*" {
			tasks = indexer.AllTableTasks
		}

		cfg := &lily.LilyIndexConfig{
			TipSet:  ts.Key(),
			Name:    indexName,
			Tasks:   tasks,
			Storage: indexFlags.storage,
			Window:  indexFlags.window,
		}

		_, err = api.LilyIndex(ctx, cfg)
		if err != nil {
			return err
		}

		return nil
	},
}
View Source
var IndexTipSetCmd = &cli.Command{
	Name:  "tipset",
	Usage: "Index the state of a tipset from the filecoin blockchain by tipset key",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		indexName := fmt.Sprintf("index_%d", time.Now().Unix())
		if indexFlags.name != "" {
			indexName = indexFlags.name
		}

		var tsStr string
		if tsStr = cctx.Args().First(); tsStr == "" {
			return xerrors.Errorf("tipset argument required")
		}

		tsk, err := parseTipSetKey(tsStr)
		if err != nil {
			return xerrors.Errorf("failed to parse tipset key: %w", err)
		}

		tasks := strings.Split(indexFlags.tasks, ",")
		if indexFlags.tasks == "*" {
			tasks = indexer.AllTableTasks
		}

		cfg := &lily.LilyIndexConfig{
			TipSet:  tsk,
			Name:    indexName,
			Tasks:   tasks,
			Storage: indexFlags.storage,
			Window:  indexFlags.window,
		}

		api, closer, err := GetAPI(ctx, indexFlags.apiAddr, indexFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		_, err = api.LilyIndex(ctx, cfg)
		if err != nil {
			return err
		}

		return nil
	},
}
View Source
var InitCmd = &cli.Command{
	Name:  "init",
	Usage: "Initialise a lily repository.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "repo",
			Usage:       "Specify path where lily should store chain state.",
			EnvVars:     []string{"LILY_REPO"},
			Value:       "~/.lily",
			Destination: &initFlags.repo,
		},
		&cli.StringFlag{
			Name:        "config",
			Usage:       "Specify path of config file to use.",
			EnvVars:     []string{"LILY_CONFIG"},
			Destination: &initFlags.config,
		},
		&cli.StringFlag{
			Name:        "import-snapshot",
			Usage:       "Import chain state from a given chain export file or url.",
			EnvVars:     []string{"LILY_SNAPSHOT"},
			Destination: &initFlags.importSnapshot,
		},
	},
	Action: func(c *cli.Context) error {
		lotuslog.SetupLogLevels()
		ctx := context.Background()
		{
			dir, err := homedir.Expand(initFlags.repo)
			if err != nil {
				log.Warnw("could not expand repo location", "error", err)
			} else {
				log.Infof("lotus repo: %s", dir)
			}
		}

		r, err := repo.NewFS(initFlags.repo)
		if err != nil {
			return xerrors.Errorf("opening fs repo: %w", err)
		}

		if initFlags.config != "" {
			if err := config.EnsureExists(initFlags.config); err != nil {
				return xerrors.Errorf("ensuring config is present at %q: %w", initFlags.config, err)
			}
			r.SetConfigPath(initFlags.config)
		}

		err = r.Init(repo.FullNode)
		if err != nil && err != repo.ErrRepoExists {
			return xerrors.Errorf("repo init error: %w", err)
		}

		if err := paramfetch.GetParams(lcli.ReqContext(c), lotusbuild.ParametersJSON(), lotusbuild.SrsJSON(), 0); err != nil {
			return xerrors.Errorf("fetching proof parameters: %w", err)
		}

		if initFlags.importSnapshot != "" {
			if err := util.ImportChain(ctx, r, initFlags.importSnapshot, true); err != nil {
				return err
			}
		}

		return nil
	},
}
View Source
var JobCmd = &cli.Command{
	Name:  "job",
	Usage: "Manage jobs being run by the daemon.",
	Subcommands: []*cli.Command{
		JobStartCmd,
		JobStopCmd,
		JobWaitCmd,
		JobListCmd,
	},
}
View Source
var JobListCmd = &cli.Command{
	Name:  "list",
	Usage: "list all jobs and their status",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		jobs, err := api.LilyJobList(ctx)
		if err != nil {
			return err
		}
		prettyJobs, err := json.MarshalIndent(jobs, "", "\t")
		if err != nil {
			return err
		}
		if _, err := fmt.Fprintf(os.Stdout, "%s\n", prettyJobs); err != nil {
			return err
		}
		return nil
	},
}
View Source
var JobStartCmd = &cli.Command{
	Name:  "start",
	Usage: "start a job.",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.IntFlag{
				Name:        "id",
				Usage:       "Identifier of job to start",
				Required:    true,
				Destination: &jobControlFlags.ID,
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		return api.LilyJobStart(ctx, schedule.JobID(jobControlFlags.ID))
	},
}
View Source
var JobStopCmd = &cli.Command{
	Name:  "stop",
	Usage: "stop a job.",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.IntFlag{
				Name:        "id",
				Usage:       "Identifier of job to stop",
				Required:    true,
				Destination: &jobControlFlags.ID,
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		return api.LilyJobStop(ctx, schedule.JobID(jobControlFlags.ID))
	},
}
View Source
var JobWaitCmd = &cli.Command{
	Name:  "wait",
	Usage: "wait on a job to complete.",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.IntFlag{
				Name:        "id",
				Usage:       "Identifier of job to wait on",
				Required:    true,
				Destination: &jobControlFlags.ID,
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		res, err := api.LilyJobWait(ctx, schedule.JobID(jobControlFlags.ID))
		if err != nil {
			return err
		}
		prettyJob, err := json.MarshalIndent(res, "", "\t")
		if err != nil {
			return err
		}
		if _, err := fmt.Fprintf(os.Stdout, "%s\n", prettyJob); err != nil {
			return err
		}
		return nil
	},
}
View Source
var LogCmd = &cli.Command{
	Name:  "log",
	Usage: "Manage logging",
	Description: `
	Manage lily logging systems.

	Environment Variables:
	GOLOG_LOG_LEVEL - Default log level for all log systems
	GOLOG_LOG_FMT   - Change output log format (json, nocolor)
	GOLOG_FILE      - Write logs to file
	GOLOG_OUTPUT    - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr
`,
	Subcommands: []*cli.Command{
		LogList,
		LogSetLevel,
		LogSetLevelRegex,
	},
}
View Source
var LogList = &cli.Command{
	Name:  "list",
	Usage: "List log systems",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		systems, err := api.LogList(ctx)
		if err != nil {
			return err
		}

		sort.Strings(systems)

		for _, system := range systems {
			fmt.Println(system)
		}

		return nil
	},
}
View Source
var LogSetLevel = &cli.Command{
	Name:      "set-level",
	Usage:     "Set log level",
	ArgsUsage: "[level]",
	Description: `Set the log level for logging systems:

   The system flag can be specified multiple times.

   eg) log set-level --system chain --system chainxchg debug

   Available Levels:
   debug
   info
   warn
   error
`,
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.StringSliceFlag{
				Name:  "system",
				Usage: "limit to log system",
				Value: &cli.StringSlice{},
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		if !cctx.Args().Present() {
			return fmt.Errorf("level is required")
		}

		systems := cctx.StringSlice("system")
		if len(systems) == 0 {
			var err error
			systems, err = api.LogList(ctx)
			if err != nil {
				return err
			}
		}

		for _, system := range systems {
			if err := api.LogSetLevel(ctx, system, cctx.Args().First()); err != nil {
				return xerrors.Errorf("setting log level on %s: %v", system, err)
			}
		}

		return nil
	},
}
View Source
var LogSetLevelRegex = &cli.Command{
	Name:      "set-level-regex",
	Usage:     "Set log level via regular expression",
	ArgsUsage: "[level] [regex]",
	Description: `Set the log level for logging systems via a regular expression matching all logging systems:

   eg) log set-level-regex info 'lily/*'
   eg) log set-level-regex debug 'lily/tasks/*'

   Available Levels:
   debug
   info
   warn
   error
`,
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		if !cctx.Args().Present() {
			return fmt.Errorf("level and regular expression are required")
		}

		if cctx.Args().Len() != 2 {
			return fmt.Errorf("extactyl two arguments required [level] [regex]")
		}

		if cctx.Args().First() == "" {
			return fmt.Errorf("level is required")
		}

		if cctx.Args().Get(1) == "" {
			return fmt.Errorf("regex is required")
		}

		if _, err := regexp.Compile(cctx.Args().Get(1)); err != nil {
			return fmt.Errorf("regex does not complie: %w", err)
		}

		if err := api.LogSetLevelRegex(ctx, cctx.Args().Get(1), cctx.Args().First()); err != nil {
			return xerrors.Errorf("setting log level via regex: %w", err)
		}

		return nil
	},
}
View Source
var MigrateCmd = &cli.Command{
	Name:  "migrate",
	Usage: "Manage the schema version installed in a database.",
	Flags: flagSet(
		dbConnectFlags,
		[]cli.Flag{
			&cli.StringFlag{
				Name:  "to",
				Usage: "Migrate the schema to specific `VERSION`.",
				Value: "",
			},
			&cli.BoolFlag{
				Name:  "latest",
				Value: false,
				Usage: "Migrate the schema to the latest version.",
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		if err := setupLogging(VisorLogFlags); err != nil {
			return xerrors.Errorf("setup logging: %w", err)
		}

		ctx := cctx.Context

		db, err := storage.NewDatabase(ctx, VisorDBFlags.DB, VisorDBFlags.DBPoolSize, VisorDBFlags.Name, VisorDBFlags.DBSchema, false)
		if err != nil {
			return xerrors.Errorf("connect database: %w", err)
		}

		if cctx.IsSet("to") {
			targetVersion, err := model.ParseVersion(cctx.String("to"))
			if err != nil {
				return xerrors.Errorf("invalid schema version: %w", err)
			}

			return db.MigrateSchemaTo(ctx, targetVersion)
		}

		if cctx.Bool("latest") {
			return db.MigrateSchema(ctx)
		}

		dbVersion, latestVersion, err := db.GetSchemaVersions(ctx)
		if err != nil {
			return xerrors.Errorf("get schema versions: %w", err)
		}

		log.Infof("current database schema is version %s, latest is %s", dbVersion, latestVersion)

		if err := db.VerifyCurrentSchema(ctx); err != nil {
			return xerrors.Errorf("verify schema: %w", err)
		}

		log.Infof("database schema is supported by this version of visor")
		return nil
	},
}
View Source
var NetCmd = &cli.Command{
	Name:  "net",
	Usage: "Manage P2P Network",
	Subcommands: []*cli.Command{
		NetID,
		NetListen,
		NetPeers,
		NetReachability,
		NetScores,
	},
}
View Source
var NetID = &cli.Command{
	Name:  "id",
	Usage: "Get peer ID of libp2p node used by daemon",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return xerrors.Errorf("get api: %w", err)
		}
		defer closer()

		pid, err := lapi.ID(ctx)
		if err != nil {
			return xerrors.Errorf("get id: %w", err)
		}

		fmt.Println(pid)
		return nil
	},
}
View Source
var NetListen = &cli.Command{
	Name:  "listen",
	Usage: "List libp2p addresses daemon is listening on",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return xerrors.Errorf("get api: %w", err)
		}
		defer closer()

		addrs, err := lapi.NetAddrsListen(ctx)
		if err != nil {
			return err
		}

		for _, peer := range addrs.Addrs {
			fmt.Printf("%s/p2p/%s\n", peer, addrs.ID)
		}
		return nil
	},
}
View Source
var NetPeers = &cli.Command{
	Name:  "peers",
	Usage: "List peers daemon is connected to",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.BoolFlag{
				Name:    "agent",
				Aliases: []string{"a"},
				Usage:   "Print agent name",
			},
			&cli.BoolFlag{
				Name:    "extended",
				Aliases: []string{"x"},
				Usage:   "Print extended peer information in json",
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return xerrors.Errorf("get api: %w", err)
		}
		defer closer()

		peers, err := lapi.NetPeers(ctx)
		if err != nil {
			return err
		}

		sort.Slice(peers, func(i, j int) bool {
			return strings.Compare(string(peers[i].ID), string(peers[j].ID)) > 0
		})

		if cctx.Bool("extended") {

			seen := make(map[peer.ID]struct{})

			for _, peer := range peers {
				_, dup := seen[peer.ID]
				if dup {
					continue
				}
				seen[peer.ID] = struct{}{}

				info, err := lapi.NetPeerInfo(ctx, peer.ID)
				if err != nil {
					log.Warnf("error getting extended peer info: %s", err)
				} else {
					bytes, err := json.Marshal(&info)
					if err != nil {
						log.Warnf("error marshalling extended peer info: %s", err)
					} else {
						fmt.Println(string(bytes))
					}
				}
			}
		} else {
			w := tabwriter.NewWriter(os.Stdout, 4, 0, 1, ' ', 0)
			for _, peer := range peers {
				var agent string
				if cctx.Bool("agent") {
					agent, err = lapi.NetAgentVersion(ctx, peer.ID)
					if err != nil {
						log.Warnf("getting agent version: %s", err)
					}
				}
				fmt.Fprintf(w, "%s\t%s\t%s\n", peer.ID, peer.Addrs, agent)
			}
			w.Flush()

		}

		return nil
	},
}
View Source
var NetReachability = &cli.Command{
	Name:  "reachability",
	Usage: "Print information about reachability from the Internet",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return xerrors.Errorf("get api: %w", err)
		}
		defer closer()

		i, err := lapi.NetAutoNatStatus(ctx)
		if err != nil {
			return err
		}

		fmt.Println("AutoNAT status: ", i.Reachability.String())
		if i.PublicAddr != "" {
			fmt.Println("Public address: ", i.PublicAddr)
		}
		return nil
	},
}
View Source
var NetScores = &cli.Command{
	Name:  "scores",
	Usage: "List scores assigned to peers",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.BoolFlag{
				Name:    "extended",
				Aliases: []string{"x"},
				Usage:   "print extended peer scores in json",
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return xerrors.Errorf("get api: %w", err)
		}
		defer closer()

		scores, err := lapi.NetPubsubScores(ctx)
		if err != nil {
			return err
		}

		if cctx.Bool("extended") {
			enc := json.NewEncoder(os.Stdout)
			for _, peer := range scores {
				err := enc.Encode(peer)
				if err != nil {
					return err
				}
			}
		} else {
			w := tabwriter.NewWriter(os.Stdout, 4, 0, 1, ' ', 0)
			for _, peer := range scores {
				fmt.Fprintf(w, "%s\t%f\n", peer.ID, peer.Score.Score)
			}
			w.Flush()
		}

		return nil
	},
}
View Source
var StopCmd = &cli.Command{
	Name:  "stop",
	Usage: "Stop a running lily daemon",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		err = lapi.Shutdown(ctx)
		if err != nil {
			return err
		}

		return nil
	},
}
View Source
var SurveyCmd = &cli.Command{
	Name:  "survey",
	Usage: "Start a daemon job to survey the node and its environment.",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.StringFlag{
				Name:        "tasks",
				Usage:       "Comma separated list of survey tasks to run. Each task is reported separately in the database.",
				Value:       strings.Join([]string{network.PeerAgentsTask}, ","),
				Destination: &surveyFlags.tasks,
			},
			&cli.DurationFlag{
				Name:        "interval",
				Usage:       "Interval to wait between each survey",
				Value:       10 * time.Minute,
				Destination: &surveyFlags.interval,
			},
			&cli.StringFlag{
				Name:        "storage",
				Usage:       "Name of storage that results will be written to.",
				Value:       "",
				Destination: &surveyFlags.storage,
			},
			&cli.StringFlag{
				Name:        "name",
				Usage:       "Name of job for easy identification later.",
				Value:       "",
				Destination: &surveyFlags.name,
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		survName := fmt.Sprintf("survey_%d", time.Now().Unix())
		if surveyFlags.name != "" {
			survName = surveyFlags.name
		}

		api, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		cfg := &lily.LilySurveyConfig{
			Name:                survName,
			Tasks:               strings.Split(surveyFlags.tasks, ","),
			Interval:            surveyFlags.interval,
			RestartDelay:        0,
			RestartOnCompletion: false,
			RestartOnFailure:    true,
			Storage:             surveyFlags.storage,
		}

		res, err := api.LilySurvey(ctx, cfg)
		if err != nil {
			return err
		}
		if err := printNewJob(os.Stdout, res); err != nil {
			return err
		}
		return nil
	},
}
View Source
var SyncCmd = &cli.Command{
	Name:  "sync",
	Usage: "Inspect or interact with the chain syncer",
	Subcommands: []*cli.Command{
		SyncStatusCmd,
		SyncWaitCmd,
	},
}
View Source
var SyncStatusCmd = &cli.Command{
	Name:  "status",
	Usage: "Report sync status of a running lily daemon",
	Flags: flagSet(
		clientAPIFlagSet,
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		state, err := lapi.SyncState(ctx)
		if err != nil {
			return err
		}

		fmt.Println("sync status:")
		for _, ss := range state.ActiveSyncs {
			fmt.Printf("worker %d:\n", ss.WorkerID)
			var base, target []cid.Cid
			var heightDiff int64
			var theight abi.ChainEpoch
			if ss.Base != nil {
				base = ss.Base.Cids()
				heightDiff = int64(ss.Base.Height())
			}
			if ss.Target != nil {
				target = ss.Target.Cids()
				heightDiff = int64(ss.Target.Height()) - heightDiff
				theight = ss.Target.Height()
			} else {
				heightDiff = 0
			}
			fmt.Printf("\tBase:\t%s\n", base)
			fmt.Printf("\tTarget:\t%s (%d)\n", target, theight)
			fmt.Printf("\tHeight diff:\t%d\n", heightDiff)
			fmt.Printf("\tStage: %s\n", ss.Stage)
			fmt.Printf("\tHeight: %d\n", ss.Height)
			if ss.End.IsZero() {
				if !ss.Start.IsZero() {
					fmt.Printf("\tElapsed: %s\n", time.Since(ss.Start))
				}
			} else {
				fmt.Printf("\tElapsed: %s\n", ss.End.Sub(ss.Start))
			}
			if ss.Stage == api.StageSyncErrored {
				fmt.Printf("\tError: %s\n", ss.Message)
			}
		}
		return nil
	},
}
View Source
var SyncWaitCmd = &cli.Command{
	Name:  "wait",
	Usage: "Wait for sync to be complete",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.BoolFlag{
				Name:  "watch",
				Usage: "don't exit after node is synced",
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		return SyncWait(ctx, lapi, cctx.Bool("watch"))
	},
}
View Source
var WaitApiCmd = &cli.Command{
	Name:  "wait-api",
	Usage: "Wait for lily api to come online",
	Flags: flagSet(
		clientAPIFlagSet,
		[]cli.Flag{
			&cli.DurationFlag{
				Name:  "timeout",
				Usage: "Time to wait for API to become ready",
				Value: 30 * time.Second,
			},
		},
	),
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		var timeout <-chan time.Time

		if cctx.Duration("timeout") > 0 {
			timeout = time.NewTimer(cctx.Duration("timeout")).C
		}

		for {
			err := checkAPI(ctx, clientAPIFlags.apiAddr, clientAPIFlags.apiToken)
			if err == nil {
				return nil
			}
			log.Warnf("API not online yet... (%s)", err)

			select {
			case <-ctx.Done():
				return nil
			case <-timeout:
				return fmt.Errorf("timed out waiting for api to come online")
			case <-time.After(time.Second):
			}
		}
	},
}
View Source
var WalkCmd = &cli.Command{
	Name:  "walk",
	Usage: "Start a daemon job to walk a range of the filecoin blockchain.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "tasks",
			Usage:       "Comma separated list of tasks to run. Each task is reported separately in the database.",
			EnvVars:     []string{"LILY_TASKS"},
			Destination: &walkFlags.tasks,
		},
		&cli.DurationFlag{
			Name:        "window",
			Usage:       "Duration after which any indexing work not completed will be marked incomplete",
			EnvVars:     []string{"LILY_WINDOW"},
			Value:       builtin.EpochDurationSeconds * time.Second * 10,
			Destination: &walkFlags.window,
		},
		&cli.Int64Flag{
			Name:        "from",
			Usage:       "Limit actor and message processing to tipsets at or above `HEIGHT`",
			EnvVars:     []string{"LILY_FROM"},
			Destination: &walkFlags.from,
			Required:    true,
		},
		&cli.Int64Flag{
			Name:        "to",
			Usage:       "Limit actor and message processing to tipsets at or below `HEIGHT`",
			EnvVars:     []string{"LILY_TO"},
			Destination: &walkFlags.to,
			Required:    true,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Name of storage that results will be written to.",
			EnvVars:     []string{"LILY_STORAGE"},
			Value:       "",
			Destination: &walkFlags.storage,
		},
		&cli.StringFlag{
			Name:        "api",
			Usage:       "Address of lily api in multiaddr format.",
			EnvVars:     []string{"LILY_API"},
			Value:       "/ip4/127.0.0.1/tcp/1234",
			Destination: &walkFlags.apiAddr,
		},
		&cli.StringFlag{
			Name:        "api-token",
			Usage:       "Authentication token for lily api.",
			EnvVars:     []string{"LILY_API_TOKEN"},
			Value:       "",
			Destination: &walkFlags.apiToken,
		},
		&cli.StringFlag{
			Name:        "name",
			Usage:       "Name of job for easy identification later.",
			EnvVars:     []string{"LILY_JOB_NAME"},
			Value:       "",
			Destination: &walkFlags.name,
		},
		&cli.IntFlag{
			Name:        "workers",
			Usage:       "Sets the number of tipsets that may be simultaneous indexed while walking",
			EnvVars:     []string{"LILY_WALK_WORKERS"},
			Value:       1,
			Destination: &walkFlags.workers,
		},
	},
	Before: func(cctx *cli.Context) error {
		from, to := walkFlags.from, walkFlags.to
		if to < from {
			return xerrors.Errorf("value of --to (%d) should be >= --from (%d)", to, from)
		}

		return nil
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		walkName := fmt.Sprintf("walk_%d", time.Now().Unix())
		if walkFlags.name != "" {
			walkName = walkFlags.name
		}

		tasks := strings.Split(walkFlags.tasks, ",")
		if walkFlags.tasks == "*" {
			tasks = indexer.AllTableTasks
		}

		cfg := &lily.LilyWalkConfig{
			Name:                walkName,
			Tasks:               tasks,
			Window:              walkFlags.window,
			From:                walkFlags.from,
			To:                  walkFlags.to,
			RestartDelay:        0,
			RestartOnCompletion: false,
			RestartOnFailure:    false,
			Storage:             walkFlags.storage,
			Workers:             walkFlags.workers,
		}

		api, closer, err := GetAPI(ctx, walkFlags.apiAddr, walkFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		res, err := api.LilyWalk(ctx, cfg)
		if err != nil {
			return err
		}
		if err := printNewJob(os.Stdout, res); err != nil {
			return err
		}
		return nil
	},
}
View Source
var WatchCmd = &cli.Command{
	Name:  "watch",
	Usage: "Start a daemon job to watch the head of the filecoin blockchain.",
	Flags: []cli.Flag{
		&cli.IntFlag{
			Name:        "confidence",
			Usage:       "Sets the size of the cache used to hold tipsets for possible reversion before being committed to the database",
			EnvVars:     []string{"LILY_CONFIDENCE"},
			Value:       2,
			Destination: &watchFlags.confidence,
		},
		&cli.IntFlag{
			Name:        "workers",
			Usage:       "Sets the number of tipsets that may be simultaneous indexed while watching",
			EnvVars:     []string{"LILY_WATCH_WORKERS"},
			Value:       2,
			Destination: &watchFlags.workers,
		},
		&cli.IntFlag{
			Name:        "buffer-size",
			Usage:       "Set the number of tipsets the watcher will buffer while waiting for a worker to accept the work",
			EnvVars:     []string{"LILY_WATCH_BUFFER"},
			Value:       5,
			Destination: &watchFlags.bufferSize,
		},
		&cli.StringFlag{
			Name:        "tasks",
			Usage:       "Comma separated list of tasks to run. Each task is reported separately in the database.",
			EnvVars:     []string{"LILY_TASKS"},
			Destination: &watchFlags.tasks,
		},
		&cli.DurationFlag{
			Name:        "window",
			Usage:       "Duration after which any indexing work not completed will be marked incomplete",
			EnvVars:     []string{"LILY_WINDOW"},
			Value:       builtin.EpochDurationSeconds * time.Second,
			Destination: &watchFlags.window,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Name of storage that results will be written to.",
			EnvVars:     []string{"LILY_STORAGE"},
			Value:       "",
			Destination: &watchFlags.storage,
		},
		&cli.StringFlag{
			Name:        "api",
			Usage:       "Address of lily api in multiaddr format.",
			EnvVars:     []string{"LILY_API"},
			Value:       "/ip4/127.0.0.1/tcp/1234",
			Destination: &watchFlags.apiAddr,
		},
		&cli.StringFlag{
			Name:        "api-token",
			Usage:       "Authentication token for lily api.",
			EnvVars:     []string{"LILY_API_TOKEN"},
			Value:       "",
			Destination: &watchFlags.apiToken,
		},
		&cli.StringFlag{
			Name:        "name",
			Usage:       "Name of job for easy identification later.",
			EnvVars:     []string{"LILY_JOB_NAME"},
			Value:       "",
			Destination: &watchFlags.name,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		watchName := fmt.Sprintf("watch_%d", time.Now().Unix())
		if watchFlags.name != "" {
			watchName = watchFlags.name
		}

		tasks := strings.Split(watchFlags.tasks, ",")
		if watchFlags.tasks == "*" {
			tasks = indexer.AllTableTasks
		}

		cfg := &lily.LilyWatchConfig{
			Name:                watchName,
			Tasks:               tasks,
			Window:              watchFlags.window,
			Confidence:          watchFlags.confidence,
			RestartDelay:        0,
			RestartOnCompletion: false,
			RestartOnFailure:    true,
			Storage:             watchFlags.storage,
			Workers:             watchFlags.workers,
			BufferSize:          watchFlags.bufferSize,
		}

		api, closer, err := GetAPI(ctx, watchFlags.apiAddr, watchFlags.apiToken)
		if err != nil {
			return err
		}
		defer closer()

		res, err := api.LilyWatch(ctx, cfg)
		if err != nil {
			return err
		}
		if err := printNewJob(os.Stdout, res); err != nil {
			return err
		}

		return nil
	},
}

Functions

func GetAPI

func GetAPI(ctx context.Context, addrStr string, token string) (lily.LilyAPI, jsonrpc.ClientCloser, error)

func LilyNodeAPIOption

func LilyNodeAPIOption(out *lily.LilyAPI, fopts ...node.Option) node.Option

Lily Node settings for injection into lotus node.

func Metadata

func Metadata() map[string]interface{}

func NewSentinelNodeRPC

func NewSentinelNodeRPC(ctx context.Context, addr string, requestHeader http.Header) (lily.LilyAPI, jsonrpc.ClientCloser, error)

func ShowCommandHelp

func ShowCommandHelp(ctx *cli.Context, command string) error

func SyncWait

func SyncWait(ctx context.Context, lapi lily.LilyAPI, watch bool) error

Types

type VisorDBOpts

type VisorDBOpts struct {
	DB                string
	Name              string
	DBSchema          string
	DBPoolSize        int
	DBAllowUpsert     bool
	DBAllowMigrations bool
}
var VisorDBFlags VisorDBOpts

type VisorLogOpts

type VisorLogOpts struct {
	LogLevel      string
	LogLevelNamed string
}
var VisorLogFlags VisorLogOpts

type VisorMetricOpts

type VisorMetricOpts struct {
	PrometheusPort string
}
var VisorMetricFlags VisorMetricOpts

type VisorTracingOpts

type VisorTracingOpts struct {
	Enabled            bool
	ServiceName        string
	ProviderURL        string
	JaegerSamplerParam float64
}
var VisorTracingFlags VisorTracingOpts

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL