commands

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2024 License: Apache-2.0, MIT Imports: 81 Imported by: 1

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ChainActorCodesCmd = &cli.Command{
	Name:  "actor-codes",
	Usage: "Print actor codes and names.",
	Flags: []cli.Flag{configFlag, storageFlag},
	Action: func(_ *cli.Context) error {
		manifests := manifest.GetBuiltinActorsKeys(actorstypes.Version(actorVersions[len(actorVersions)-1]))
		t := table.NewWriter()
		t.AppendHeader(table.Row{"name", "family", "code"})

		// values that may be accessed if user wants to persist to Storage
		var results common.ActorCodeList
		var strg model.Storage
		var ctx context.Context

		if chainActorFlags.storage != "" {
			results = common.ActorCodeList{}

			cfg, err := config.FromFile(chainActorFlags.config)
			if err != nil {
				return err
			}

			md := storage.Metadata{
				JobName: chainActorFlags.storage,
			}

			ctx = context.Background()

			sc, err := storage.NewCatalog(cfg.Storage)
			if err != nil {
				return err
			}
			strg, err = sc.Connect(ctx, chainActorFlags.storage, md)
			if err != nil {
				return err
			}
		}

		for _, a := range manifests {
			av := make(map[actorstypes.Version]cid.Cid)
			for _, v := range actorVersions {
				code, ok := actors.GetActorCodeID(actorstypes.Version(v), a)
				if !ok {
					continue
				}
				av[actorstypes.Version(v)] = code
				name, family, err := util.ActorNameAndFamilyFromCode(av[actorstypes.Version(v)])
				if err != nil {
					return err
				}
				t.AppendRow(table.Row{name, family, code})
				results = append(results, &common.ActorCode{
					CID:  code.String(),
					Code: name,
				})

				if chainActorFlags.storage != "" {
					err := strg.PersistBatch(ctx, results)
					if err != nil {
						return err
					}
				}
			}
		}

		fmt.Println(t.RenderCSV())
		return nil
	},
}
View Source
var ChainActorMethodsCmd = &cli.Command{
	Name:  "actor-methods",
	Usage: "Print actor method numbers and their human readable names.",
	Flags: []cli.Flag{configFlag, storageFlag},
	Action: func(_ *cli.Context) error {
		manifests := manifest.GetBuiltinActorsKeys(actorstypes.Version(actorVersions[len(actorVersions)-1]))
		t := table.NewWriter()
		t.AppendHeader(table.Row{"actor_family", "method_name", "method_number"})

		// values that may be accessed if user wants to persist to Storage
		var results common.ActorMethodList
		var strg model.Storage
		var ctx context.Context

		if chainActorFlags.persist {
			cfg, err := config.FromFile(chainActorFlags.config)
			if err != nil {
				return err
			}

			md := storage.Metadata{
				JobName: chainActorFlags.storage,
			}

			ctx = context.Background()

			sc, err := storage.NewCatalog(cfg.Storage)
			if err != nil {
				return err
			}
			strg, err = sc.Connect(ctx, chainActorFlags.storage, md)
			if err != nil {
				return err
			}
		}

		for _, a := range manifests {
			av := make(map[actorstypes.Version]cid.Cid)
			for _, v := range actorVersions {
				code, ok := actors.GetActorCodeID(actorstypes.Version(v), a)
				if !ok {
					continue
				}
				av[actorstypes.Version(v)] = code
			}

			var err error
			if results, err = printActorMethods(a); err != nil {
				return err
			}

			for _, result := range results {
				t.AppendRow(table.Row{result.Family, result.Method, result.MethodName})
				t.AppendSeparator()
			}

			if chainActorFlags.persist {
				err := strg.PersistBatch(ctx, results)
				if err != nil {
					return err
				}
			}
		}
		fmt.Println(t.RenderCSV())
		return nil
	},
}
View Source
var ChainCmd = &cli.Command{
	Name:  "chain",
	Usage: "Interact with filecoin blockchain",
	Subcommands: []*cli.Command{
		ChainHeadCmd,
		ChainGetBlock,
		ChainReadObjCmd,
		ChainStatObjCmd,
		ChainGetMsgCmd,
		ChainListCmd,
		ChainSetHeadCmd,
		ChainActorCodesCmd,
		ChainActorMethodsCmd,
		ChainStateInspect,
		ChainStateCompute,
		ChainStateComputeRange,
		ChainPruneCmd,
	},
}
View Source
var ChainGetBlock = &cli.Command{
	Name:      "getblock",
	Usage:     "Get a block and print its details",
	ArgsUsage: "[blockCid]",
	Flags: []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)
		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 fmt.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 fmt.Errorf("failed to get messages: %w", err)
		}

		pmsgs, err := lapi.ChainGetParentMessages(ctx, bcid)
		if err != nil {
			return fmt.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)
		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: %w", err)
		}

		mb, err := lapi.ChainReadObj(ctx, c)
		if err != nil {
			return fmt.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 fmt.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",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		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: []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)
		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 ChainPruneCmd = &cli.Command{
	Name:  "prune",
	Usage: "splitstore gc",
	Subcommands: []*cli.Command{
		chainPruneColdCmd,
		chainPruneHotGCCmd,
		chainPruneHotMovingGCCmd,
	},
}
View Source
var ChainReadObjCmd = &cli.Command{
	Name:      "read-obj",
	Usage:     "Read the raw bytes of an object",
	ArgsUsage: "[objectCid]",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		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: []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)
		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")
		}

		err = lapi.ChainSetHead(ctx, ts.Key())

		return err
	},
}
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: []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)
		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 ChainStateCompute = &cli.Command{
	Name:  "state-compute",
	Usage: "Generates the state at epoch `N`",
	Flags: []cli.Flag{
		&cli.Uint64Flag{
			Name:     "epoch",
			Aliases:  []string{"e"},
			Required: true,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		head, err := lapi.ChainHead(ctx)
		if err != nil {
			return err
		}
		ts, err := lapi.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), head.Key())
		if err != nil {
			return err
		}

		_, err = lapi.StateCompute(ctx, ts.Key())
		return err

	},
}
View Source
var ChainStateComputeRange = &cli.Command{
	Name:  "state-compute-range",
	Usage: "Generates the state from epoch `FROM` to epoch `TO`",
	Flags: []cli.Flag{
		&cli.Uint64Flag{
			Name:     "from",
			Required: true,
		},
		&cli.Uint64Flag{
			Name:     "to",
			Required: true,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		head, err := lapi.ChainHead(ctx)
		if err != nil {
			return err
		}
		bar := pb.StartNew(int(cctx.Uint64("to") - cctx.Uint64("from")))
		bar.ShowTimeLeft = true
		bar.ShowPercent = true
		bar.Units = pb.U_NO
		for i := cctx.Int64("from"); i <= cctx.Int64("to"); i++ {
			ts, err := lapi.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(i), head.Key())
			if err != nil {
				return err
			}

			_, err = lapi.StateCompute(ctx, ts.Key())
			if err != nil {
				return err
			}
			bar.Add(1)
		}
		bar.Finish()
		return nil

	},
}
View Source
var ChainStateInspect = &cli.Command{
	Name:  "state-inspect",
	Usage: "Returns details about each epoch's state in the local datastore",
	Flags: []cli.Flag{
		&cli.Uint64Flag{
			Name:    "limit",
			Aliases: []string{"l"},
			Value:   100,
			Usage:   "Limit traversal of statetree when searching for oldest state by `N` heights starting from most recent",
		},
		&cli.BoolFlag{
			Name:    "verbose",
			Aliases: []string{"v"},
			Usage:   "Include detailed information about the completeness of state for all traversed height(s) starting from most recent",
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		report, err := lapi.FindOldestState(ctx, cctx.Int64("limit"))
		if err != nil {
			return err
		}
		sort.Slice(report, func(i, j int) bool {
			return report[i].Height > report[j].Height
		})

		out, err := marshalReport(report, cctx.Bool("verbose"))
		if err != nil {
			return err
		}
		fmt.Println(string(out))
		return nil
	},
}
View Source
var ClientAPIFlag = &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: &ClientAPIFlags.APIAddr,
}
View Source
var ClientAPIFlagSet = []cli.Flag{
	ClientAPIFlag,
	ClientTokenFlag,
}

ClientAPIFlagSet are used by commands that act as clients of a daemon's API

View Source
var ClientAPIFlags struct {
	APIAddr  string
	APIToken string
}
View Source
var ClientTokenFlag = &cli.StringFlag{
	Name:        "api-token",
	Usage:       "Authentication token for lily api.",
	EnvVars:     []string{"LILY_API_TOKEN"},
	Value:       "",
	Destination: &ClientAPIFlags.APIToken,
}
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. Lily 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

Lily 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(LilyLogFlags); err != nil {
			return fmt.Errorf("setup logging: %w", err)
		}

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

		if err := setupTracing(LilyTracingFlags); err != nil {
			return fmt.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 fmt.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)
			}
		}

		ctx, _ = tag.New(ctx,
			tag.Insert(metrics.Version, version.String()),
		)
		stats.Record(ctx, metrics.LilyInfo.M(1))

		if err := config.EnsureExists(daemonFlags.config); err != nil {
			return fmt.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 fmt.Errorf("repo init error: %w", err)
		}

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

		var genBytes []byte
		if c.String("genesis") != "" {
			genBytes, err = os.ReadFile(daemonFlags.genesis)
			if err != nil {
				return fmt.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(*distributed.Catalog), modules.NewQueueCatalog),
			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(_ *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(_ *node.Settings) bool { return !daemonFlags.bootstrap },
				node.Unset(node.RunPeerMgrKey),
				node.Unset(new(*peermgr.PeerMgr)),
			),
		)
		if err != nil {
			return fmt.Errorf("initializing node: %w", err)
		}

		endpoint, err := r.APIEndpoint()
		if err != nil {
			return fmt.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(_ *cli.Context) error {
		from, to := chainExportFlags.from, chainExportFlags.to
		if to < from {
			return fmt.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 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,
		},
		&cli.IntFlag{
			Name:        "backfill-tipsetkey-range",
			Usage:       "Determine the extent of backfilling from the head.",
			EnvVars:     []string{"LILY_BACKFILL_TIPSETKEY_RANGE"},
			Value:       3600,
			Destination: &initFlags.backfillTipsetKeyRange,
		},
	},
	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 fmt.Errorf("opening fs repo: %w", err)
		}

		if initFlags.config != "" {
			if err := config.EnsureExists(initFlags.config); err != nil {
				return fmt.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 fmt.Errorf("repo init error: %w", err)
		}

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

		if initFlags.importSnapshot != "" {
			if err := util.ImportChain(ctx, r, initFlags.importSnapshot, true, initFlags.backfillTipsetKeyRange); 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",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx)
		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: []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)
		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 fmt.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
`,
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)

		api, closer, err := GetAPI(ctx)
		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 fmt.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(LilyLogFlags); err != nil {
			return fmt.Errorf("setup logging: %w", err)
		}

		ctx := cctx.Context

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

		if cctx.IsSet("to") {
			targetVersion, err := model.ParseVersion(cctx.String("to"))
			if err != nil {
				return fmt.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 fmt.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 fmt.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{
		NetConnect,
		NetDisconnect,
		NetID,
		NetListen,
		NetPeers,
		NetReachability,
		NetScores,
	},
}
View Source
var NetConnect = &cli.Command{
	Name:      "connect",
	Usage:     "Connect to a peer",
	ArgsUsage: "[peerMultiaddr]",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		pis, err := addrutil.ParseAddresses(ctx, cctx.Args().Slice())
		if err != nil {
			return err
		}

		for _, pi := range pis {
			fmt.Printf("connect %s: ", pi.ID.String())
			err := api.NetConnect(ctx, pi)
			if err != nil {
				fmt.Println("failure")
				return err
			}
			fmt.Println("success")
		}

		return nil
	},
}
View Source
var NetDisconnect = &cli.Command{
	Name:      "disconnect",
	Usage:     "Disconnect from a peer",
	ArgsUsage: "[peerID]",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		api, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		ids := cctx.Args().Slice()
		for _, id := range ids {
			pid, err := peer.Decode(id)
			if err != nil {
				fmt.Println("failure")
				return err
			}
			fmt.Printf("disconnect %s: ", pid.String())
			err = api.NetDisconnect(ctx, pid)
			if err != nil {
				fmt.Println("failure")
				return err
			}
			fmt.Println("success")
		}
		return nil
	},
}
View Source
var NetID = &cli.Command{
	Name:  "id",
	Usage: "Get peer ID of libp2p node used by daemon",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return fmt.Errorf("get api: %w", err)
		}
		defer closer()

		pid, err := lapi.ID(ctx)
		if err != nil {
			return fmt.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",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return fmt.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: []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)
		if err != nil {
			return fmt.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)
			}
			if err := w.Flush(); err != nil {
				return err
			}

		}

		return nil
	},
}
View Source
var NetReachability = &cli.Command{
	Name:  "reachability",
	Usage: "Print information about reachability from the Internet",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return fmt.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 len(i.PublicAddrs) > 0 {
			fmt.Println("Public address: ", i.PublicAddrs)
		}
		return nil
	},
}
View Source
var NetScores = &cli.Command{
	Name:  "scores",
	Usage: "List scores assigned to peers",
	Flags: []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)
		if err != nil {
			return fmt.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)
			}
			if err := w.Flush(); err != nil {
				return err
			}
		}

		return nil
	},
}
View Source
var ShedCmd = &cli.Command{
	Name:  "shed",
	Usage: "Various utilities to help with Lily development.",
	Subcommands: []*cli.Command{
		ShedModelsListCmd,
		ShedModelsDescribeCmd,
	},
}
View Source
var ShedModelsDescribeCmd = &cli.Command{
	Name: "models-describe",
	Action: func(cctx *cli.Context) error {
		if cctx.Args().First() == "" {
			return fmt.Errorf("model name required, run `lily help models-list`, to see all available models")
		}
		mname := cctx.Args().First()
		if _, found := tasktype.TableLookup[mname]; !found {
			return fmt.Errorf("model %s doesn't exist", mname)
		}

		modelFields := tasktype.TableFieldComments[mname]
		t := table.NewWriter()
		t.AppendHeader(table.Row{"Fields", "Description"})
		t.SortBy([]table.SortBy{
			{Name: "Fields", Mode: table.Asc}})
		t.SetCaption(tasktype.TableComment[mname])
		for field, comment := range modelFields {
			t.AppendRow(table.Row{field, comment})
			t.AppendSeparator()
		}
		fmt.Println(t.Render())
		return nil
	},
}
View Source
var ShedModelsListCmd = &cli.Command{
	Name: "models-list",
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "short",
			Usage:   "List only model names in a shell script-friendly output.",
			Aliases: []string{"s"},
		},
	},
	Action: func(cctx *cli.Context) error {
		if cctx.Bool("short") {
			fmt.Printf("%s\n", strings.Join(tasktype.AllTableTasks, " "))
			return nil
		}

		t := table.NewWriter()
		t.AppendHeader(table.Row{"Model", "Description"})
		for _, m := range tasktype.AllTableTasks {
			comment := tasktype.TableComment[m]
			t.AppendRow(table.Row{m, text.WrapSoft(comment, 80)})
			t.AppendSeparator()
		}
		fmt.Println(t.Render())
		return nil
	},
}
View Source
var StopCmd = &cli.Command{
	Name:  "stop",
	Usage: "Stop a running lily daemon",
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		err = lapi.Shutdown(ctx)
		if 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,
		SyncIncomingBlockCmd,
	},
}
View Source
var SyncIncomingBlockCmd = &cli.Command{
	Name:  "blocks",
	Usage: "Start to get incoming block",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:        "config",
			Usage:       "Specify path of config file to use.",
			EnvVars:     []string{"LILY_CONFIG"},
			Destination: &syncFlags.config,
		},
		&cli.StringFlag{
			Name:        "storage",
			Usage:       "Specify the storage to use, if persisting the displayed output.",
			Destination: &syncFlags.storage,
		},
		&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: &syncFlags.confidence,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

		ctx, cancel := SetupContextWithCancel(ctx)
		defer cancel()

		strg, err := SetupStorage(syncFlags.config, syncFlags.storage)
		if err != nil {
			return err
		}

		state := &SyncingState{
			UnsyncedBlockHeadersByEpoch: make(map[int64][]*blocks.UnsyncedBlockHeader),
			Confidence:                  int64(syncFlags.confidence),
			Storage:                     strg,
			MapMutex:                    sync.Mutex{},
			StorageMutex:                sync.Mutex{},
		}

		go detectOrphanBlocks(ctx, lapi, state)
		go getIncomingBlocks(ctx, lapi, state)

		<-ctx.Done()
		return nil
	},
}
View Source
var SyncStatusCmd = &cli.Command{
	Name:  "status",
	Usage: "Report sync status of a running lily daemon",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:     "output",
			Usage:    "Print only the current sync stage at the latest height. One of [text, json]",
			Aliases:  []string{"o"},
			Required: false,
		},
	},
	Action: func(cctx *cli.Context) error {
		ctx := lotuscli.ReqContext(cctx)
		lapi, closer, err := GetAPI(ctx)
		if err != nil {
			return err
		}
		defer closer()

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

		output := cctx.String("output")

		var max abi.ChainEpoch = -1
		maxStateSync := api.StageIdle
		for _, ss := range state.ActiveSyncs {
			if max < ss.Height && maxStateSync <= ss.Stage {
				max = ss.Height
				maxStateSync = ss.Stage
			}

			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
			}

			switch output {
			case "json":
				j, err := json.Marshal(SyncStatus{Stage: maxStateSync, Height: max})
				if err != nil {
					return err
				}
				fmt.Printf(string(j) + "\n")
			case "":
				fmt.Printf("worker %d:\n", ss.WorkerID)
				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))
				}
			case "text":
				fallthrough
			default:
				fmt.Printf("%s %d\n", maxStateSync, max)
			}

			if ss.Stage == api.StageSyncErrored && output != "json" {
				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: []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)
		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: []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)
			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):
			}
		}
	},
}

Functions

func FlagSet added in v0.10.0

func FlagSet(fs ...[]cli.Flag) []cli.Flag

func GetAPI

func GetAPI(ctx context.Context) (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 NewSentinelNodeRPC

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

func PrintNewJob added in v0.10.0

func PrintNewJob(w io.Writer, res *schedule.JobSubmitResult) error

func SetupContextWithCancel added in v0.17.2

func SetupContextWithCancel(ctx context.Context) (context.Context, context.CancelFunc)

func SetupStorage added in v0.17.2

func SetupStorage(configPath string, storageStr string) (strg model.Storage, err error)

func SyncWait

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

Types

type LilyDBOpts added in v0.10.0

type LilyDBOpts struct {
	DB                string
	Name              string
	DBSchema          string
	DBPoolSize        int
	DBAllowUpsert     bool
	DBAllowMigrations bool
}
var LilyDBFlags LilyDBOpts

type LilyLogOpts added in v0.10.0

type LilyLogOpts struct {
	LogLevel      string
	LogLevelNamed string
}
var LilyLogFlags LilyLogOpts

type LilyMetricOpts added in v0.10.0

type LilyMetricOpts struct {
	PrometheusPort string
	RedisAddr      string
	RedisUsername  string
	RedisPassword  string
	RedisDB        int
}
var LilyMetricFlags LilyMetricOpts

type LilyTracingOpts added in v0.10.0

type LilyTracingOpts struct {
	Enabled            bool
	ServiceName        string
	ProviderURL        string
	JaegerSamplerParam float64
}
var LilyTracingFlags LilyTracingOpts

type SyncStatus added in v0.11.0

type SyncStatus struct {
	Stage  api.SyncStateStage
	Height abi.ChainEpoch
}

type SyncingState added in v0.17.2

type SyncingState struct {
	UnsyncedBlockHeadersByEpoch map[int64][]*blocks.UnsyncedBlockHeader
	MapMutex                    sync.Mutex
	Confidence                  int64
	Storage                     model.Storage
	StorageMutex                sync.Mutex
}

func (*SyncingState) PersistBlocks added in v0.17.2

func (ss *SyncingState) PersistBlocks(ctx context.Context, blocks blocks.UnsyncedBlockHeaders) error

func (*SyncingState) SetBlockHeaderToMap added in v0.17.2

func (ss *SyncingState) SetBlockHeaderToMap(block *blocks.UnsyncedBlockHeader)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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