2019-06-25 11:42:17 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
2019-07-09 12:08:43 +00:00
|
|
|
"context"
|
2019-12-09 12:36:56 +00:00
|
|
|
"fmt"
|
2019-07-23 18:49:09 +00:00
|
|
|
"net/http"
|
2020-10-08 12:15:09 +00:00
|
|
|
"net/url"
|
2019-07-09 12:08:43 +00:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2019-12-09 12:36:56 +00:00
|
|
|
"strings"
|
2019-07-09 12:08:43 +00:00
|
|
|
"syscall"
|
|
|
|
|
2020-01-08 19:10:57 +00:00
|
|
|
logging "github.com/ipfs/go-log/v2"
|
2019-11-21 16:10:04 +00:00
|
|
|
"github.com/mitchellh/go-homedir"
|
2019-12-09 12:36:56 +00:00
|
|
|
"github.com/multiformats/go-multiaddr"
|
2020-08-25 23:46:31 +00:00
|
|
|
manet "github.com/multiformats/go-multiaddr/net"
|
2020-06-02 18:12:53 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2020-06-05 22:59:01 +00:00
|
|
|
"golang.org/x/xerrors"
|
2019-07-10 17:28:49 +00:00
|
|
|
|
2020-05-20 17:43:22 +00:00
|
|
|
"github.com/filecoin-project/go-jsonrpc"
|
|
|
|
|
2019-10-18 04:47:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/api/client"
|
|
|
|
"github.com/filecoin-project/lotus/node/repo"
|
2019-06-25 11:42:17 +00:00
|
|
|
)
|
|
|
|
|
2019-07-23 18:49:09 +00:00
|
|
|
var log = logging.Logger("cli")
|
|
|
|
|
2019-07-09 12:08:43 +00:00
|
|
|
const (
|
2020-08-16 02:20:32 +00:00
|
|
|
metadataTraceContext = "traceContext"
|
2019-07-09 12:08:43 +00:00
|
|
|
)
|
|
|
|
|
2020-02-25 23:17:15 +00:00
|
|
|
// custom CLI error
|
|
|
|
|
|
|
|
type ErrCmdFailed struct {
|
|
|
|
msg string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ErrCmdFailed) Error() string {
|
|
|
|
return e.msg
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewCliError(s string) error {
|
|
|
|
return &ErrCmdFailed{s}
|
|
|
|
}
|
|
|
|
|
2019-07-09 12:08:43 +00:00
|
|
|
// ApiConnector returns API instance
|
2019-07-24 00:09:34 +00:00
|
|
|
type ApiConnector func() api.FullNode
|
2019-07-08 19:07:16 +00:00
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
type APIInfo struct {
|
2020-10-08 12:15:09 +00:00
|
|
|
Addr string
|
2019-12-09 12:36:56 +00:00
|
|
|
Token []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a APIInfo) DialArgs() (string, error) {
|
2020-10-08 12:15:09 +00:00
|
|
|
ma, err := multiaddr.NewMultiaddr(a.Addr)
|
|
|
|
if err == nil {
|
|
|
|
_, addr, err := manet.DialArgs(ma)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-12-09 12:36:56 +00:00
|
|
|
|
2020-10-08 12:15:09 +00:00
|
|
|
return "ws://" + addr + "/rpc/v0", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = url.Parse(a.Addr)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return a.Addr + "/rpc/v0", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a APIInfo) Host() (string, error) {
|
|
|
|
ma, err := multiaddr.NewMultiaddr(a.Addr)
|
|
|
|
if err == nil {
|
|
|
|
_, addr, err := manet.DialArgs(ma)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return addr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
spec, err := url.Parse(a.Addr)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return spec.Host, nil
|
2019-12-09 12:36:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a APIInfo) AuthHeader() http.Header {
|
|
|
|
if len(a.Token) != 0 {
|
|
|
|
headers := http.Header{}
|
|
|
|
headers.Add("Authorization", "Bearer "+string(a.Token))
|
|
|
|
return headers
|
|
|
|
}
|
|
|
|
log.Warn("API Token not set and requested, capabilities might be limited.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-13 20:37:09 +00:00
|
|
|
// The flag passed on the command line with the listen address of the API
|
|
|
|
// server (only used by the tests)
|
|
|
|
func flagForAPI(t repo.RepoType) string {
|
|
|
|
switch t {
|
|
|
|
case repo.FullNode:
|
2020-10-08 12:15:09 +00:00
|
|
|
return "api-url"
|
2020-08-13 20:37:09 +00:00
|
|
|
case repo.StorageMiner:
|
2020-10-08 12:15:09 +00:00
|
|
|
return "miner-api-url"
|
2020-08-30 18:28:58 +00:00
|
|
|
case repo.Worker:
|
2020-10-08 12:15:09 +00:00
|
|
|
return "worker-api-url"
|
2020-08-13 20:37:09 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown repo type: %v", t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
func flagForRepo(t repo.RepoType) string {
|
|
|
|
switch t {
|
|
|
|
case repo.FullNode:
|
|
|
|
return "repo"
|
|
|
|
case repo.StorageMiner:
|
2020-07-08 10:38:59 +00:00
|
|
|
return "miner-repo"
|
2020-08-30 18:28:58 +00:00
|
|
|
case repo.Worker:
|
|
|
|
return "worker-repo"
|
2019-12-09 12:36:56 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown repo type: %v", t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func envForRepo(t repo.RepoType) string {
|
|
|
|
switch t {
|
|
|
|
case repo.FullNode:
|
|
|
|
return "FULLNODE_API_INFO"
|
|
|
|
case repo.StorageMiner:
|
2020-07-09 03:04:45 +00:00
|
|
|
return "MINER_API_INFO"
|
2020-08-30 18:28:58 +00:00
|
|
|
case repo.Worker:
|
|
|
|
return "WORKER_API_INFO"
|
2019-12-09 12:36:56 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown repo type: %v", t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-10 12:18:09 +00:00
|
|
|
// TODO remove after deprecation period
|
|
|
|
func envForRepoDeprecation(t repo.RepoType) string {
|
|
|
|
switch t {
|
|
|
|
case repo.FullNode:
|
|
|
|
return "FULLNODE_API_INFO"
|
|
|
|
case repo.StorageMiner:
|
|
|
|
return "STORAGE_API_INFO"
|
2020-08-30 18:28:58 +00:00
|
|
|
case repo.Worker:
|
|
|
|
return "WORKER_API_INFO"
|
2020-07-10 12:18:09 +00:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown repo type: %v", t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) {
|
2020-08-13 20:37:09 +00:00
|
|
|
// Check if there was a flag passed with the listen address of the API
|
|
|
|
// server (only used by the tests)
|
|
|
|
apiFlag := flagForAPI(t)
|
|
|
|
if ctx.IsSet(apiFlag) {
|
|
|
|
strma := ctx.String(apiFlag)
|
|
|
|
strma = strings.TrimSpace(strma)
|
|
|
|
|
2020-10-08 12:15:09 +00:00
|
|
|
return APIInfo{Addr: strma}, nil
|
2020-08-13 20:37:09 +00:00
|
|
|
}
|
|
|
|
|
2020-07-10 12:18:09 +00:00
|
|
|
envKey := envForRepo(t)
|
|
|
|
env, ok := os.LookupEnv(envKey)
|
|
|
|
if !ok {
|
|
|
|
// TODO remove after deprecation period
|
|
|
|
envKey = envForRepoDeprecation(t)
|
|
|
|
env, ok = os.LookupEnv(envKey)
|
|
|
|
if ok {
|
|
|
|
log.Warnf("Use deprecation env(%s) value, please use env(%s) instead.", envKey, envForRepo(t))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ok {
|
2019-12-09 12:36:56 +00:00
|
|
|
sp := strings.SplitN(env, ":", 2)
|
|
|
|
if len(sp) != 2 {
|
2020-07-10 12:18:09 +00:00
|
|
|
log.Warnf("invalid env(%s) value, missing token or address", envKey)
|
2019-12-09 12:36:56 +00:00
|
|
|
} else {
|
|
|
|
return APIInfo{
|
2020-10-08 12:15:09 +00:00
|
|
|
Addr: sp[1],
|
2019-12-09 12:36:56 +00:00
|
|
|
Token: []byte(sp[0]),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
repoFlag := flagForRepo(t)
|
|
|
|
|
2019-11-21 16:10:04 +00:00
|
|
|
p, err := homedir.Expand(ctx.String(repoFlag))
|
2019-07-10 17:28:49 +00:00
|
|
|
if err != nil {
|
2020-08-16 02:20:32 +00:00
|
|
|
return APIInfo{}, xerrors.Errorf("could not expand home dir (%s): %w", repoFlag, err)
|
2019-11-21 16:10:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
r, err := repo.NewFS(p)
|
|
|
|
if err != nil {
|
2019-12-09 12:36:56 +00:00
|
|
|
return APIInfo{}, xerrors.Errorf("could not open repo at path: %s; %w", p, err)
|
2019-07-10 17:28:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ma, err := r.APIEndpoint()
|
|
|
|
if err != nil {
|
2020-03-24 06:54:39 +00:00
|
|
|
return APIInfo{}, xerrors.Errorf("could not get api endpoint: %w", err)
|
2019-07-10 17:28:49 +00:00
|
|
|
}
|
2019-12-09 12:36:56 +00:00
|
|
|
|
|
|
|
token, err := r.APIToken()
|
2019-11-21 16:10:04 +00:00
|
|
|
if err != nil {
|
2019-12-09 12:36:56 +00:00
|
|
|
log.Warnf("Couldn't load CLI token, capabilities may be limited: %v", err)
|
2019-11-21 16:10:04 +00:00
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
return APIInfo{
|
2020-10-08 12:15:09 +00:00
|
|
|
Addr: ma.String(),
|
2019-12-09 12:36:56 +00:00
|
|
|
Token: token,
|
|
|
|
}, nil
|
2019-11-21 16:10:04 +00:00
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
func GetRawAPI(ctx *cli.Context, t repo.RepoType) (string, http.Header, error) {
|
|
|
|
ainfo, err := GetAPIInfo(ctx, t)
|
2019-07-11 11:52:07 +00:00
|
|
|
if err != nil {
|
2019-12-09 12:36:56 +00:00
|
|
|
return "", nil, xerrors.Errorf("could not get API info: %w", err)
|
2019-07-11 11:52:07 +00:00
|
|
|
}
|
2019-11-21 16:10:04 +00:00
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
addr, err := ainfo.DialArgs()
|
2019-07-23 18:49:09 +00:00
|
|
|
if err != nil {
|
2019-12-09 12:36:56 +00:00
|
|
|
return "", nil, xerrors.Errorf("could not get DialArgs: %w", err)
|
2019-07-23 18:49:09 +00:00
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
return addr, ainfo.AuthHeader(), nil
|
2019-07-27 12:18:36 +00:00
|
|
|
}
|
|
|
|
|
2019-10-03 18:12:30 +00:00
|
|
|
func GetAPI(ctx *cli.Context) (api.Common, jsonrpc.ClientCloser, error) {
|
2019-12-09 12:36:56 +00:00
|
|
|
ti, ok := ctx.App.Metadata["repoType"]
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("unknown repo type, are you sure you want to use GetAPI?")
|
|
|
|
ti = repo.FullNode
|
|
|
|
}
|
|
|
|
t, ok := ti.(repo.RepoType)
|
|
|
|
if !ok {
|
|
|
|
log.Errorf("repoType type does not match the type of repo.RepoType")
|
2019-08-02 16:18:44 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 12:14:43 +00:00
|
|
|
if tn, ok := ctx.App.Metadata["testnode-storage"]; ok {
|
|
|
|
return tn.(api.StorageMiner), func() {}, nil
|
|
|
|
}
|
|
|
|
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
|
|
|
|
return tn.(api.FullNode), func() {}, nil
|
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
addr, headers, err := GetRawAPI(ctx, t)
|
2019-08-02 16:18:44 +00:00
|
|
|
if err != nil {
|
2019-10-03 18:12:30 +00:00
|
|
|
return nil, nil, err
|
2019-08-02 16:18:44 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 03:22:40 +00:00
|
|
|
return client.NewCommonRPC(ctx.Context, addr, headers)
|
2019-08-02 16:18:44 +00:00
|
|
|
}
|
|
|
|
|
2019-10-03 18:12:30 +00:00
|
|
|
func GetFullNodeAPI(ctx *cli.Context) (api.FullNode, jsonrpc.ClientCloser, error) {
|
2020-10-05 12:14:43 +00:00
|
|
|
if tn, ok := ctx.App.Metadata["testnode-full"]; ok {
|
|
|
|
return tn.(api.FullNode), func() {}, nil
|
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
addr, headers, err := GetRawAPI(ctx, repo.FullNode)
|
2019-07-27 12:18:36 +00:00
|
|
|
if err != nil {
|
2019-10-03 18:12:30 +00:00
|
|
|
return nil, nil, err
|
2019-07-27 12:18:36 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 03:22:40 +00:00
|
|
|
return client.NewFullNodeRPC(ctx.Context, addr, headers)
|
2019-07-27 12:18:36 +00:00
|
|
|
}
|
|
|
|
|
2020-08-17 15:34:56 +00:00
|
|
|
func GetStorageMinerAPI(ctx *cli.Context, opts ...jsonrpc.Option) (api.StorageMiner, jsonrpc.ClientCloser, error) {
|
2020-10-05 12:14:43 +00:00
|
|
|
if tn, ok := ctx.App.Metadata["testnode-storage"]; ok {
|
|
|
|
return tn.(api.StorageMiner), func() {}, nil
|
|
|
|
}
|
|
|
|
|
2019-12-09 12:36:56 +00:00
|
|
|
addr, headers, err := GetRawAPI(ctx, repo.StorageMiner)
|
2019-07-27 12:18:36 +00:00
|
|
|
if err != nil {
|
2019-10-03 18:12:30 +00:00
|
|
|
return nil, nil, err
|
2019-07-27 12:18:36 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 03:22:40 +00:00
|
|
|
return client.NewStorageMinerRPC(ctx.Context, addr, headers, opts...)
|
2019-07-09 12:08:43 +00:00
|
|
|
}
|
|
|
|
|
2020-08-30 18:28:58 +00:00
|
|
|
func GetWorkerAPI(ctx *cli.Context) (api.WorkerAPI, jsonrpc.ClientCloser, error) {
|
|
|
|
addr, headers, err := GetRawAPI(ctx, repo.Worker)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return client.NewWorkerRPC(ctx.Context, addr, headers)
|
|
|
|
}
|
|
|
|
|
2019-09-17 18:36:06 +00:00
|
|
|
func DaemonContext(cctx *cli.Context) context.Context {
|
2020-08-16 02:20:32 +00:00
|
|
|
if mtCtx, ok := cctx.App.Metadata[metadataTraceContext]; ok {
|
2019-09-17 18:36:06 +00:00
|
|
|
return mtCtx.(context.Context)
|
|
|
|
}
|
|
|
|
|
|
|
|
return context.Background()
|
|
|
|
}
|
|
|
|
|
2019-07-18 23:16:23 +00:00
|
|
|
// ReqContext returns context for cli execution. Calling it for the first time
|
2019-07-09 12:08:43 +00:00
|
|
|
// installs SIGTERM handler that will close returned context.
|
|
|
|
// Not safe for concurrent execution.
|
2019-07-18 23:16:23 +00:00
|
|
|
func ReqContext(cctx *cli.Context) context.Context {
|
2019-09-17 18:36:06 +00:00
|
|
|
tCtx := DaemonContext(cctx)
|
2019-07-25 20:19:43 +00:00
|
|
|
|
|
|
|
ctx, done := context.WithCancel(tCtx)
|
2019-07-09 12:08:43 +00:00
|
|
|
sigChan := make(chan os.Signal, 2)
|
|
|
|
go func() {
|
|
|
|
<-sigChan
|
|
|
|
done()
|
|
|
|
}()
|
2020-03-09 22:03:33 +00:00
|
|
|
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP)
|
2019-07-09 12:08:43 +00:00
|
|
|
|
|
|
|
return ctx
|
2019-07-08 19:07:16 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 12:29:24 +00:00
|
|
|
var CommonCommands = []*cli.Command{
|
|
|
|
netCmd,
|
2020-05-13 11:08:59 +00:00
|
|
|
authCmd,
|
2020-03-23 12:29:24 +00:00
|
|
|
logCmd,
|
2020-04-27 22:26:46 +00:00
|
|
|
waitApiCmd,
|
2020-05-13 11:08:59 +00:00
|
|
|
fetchParamCmd,
|
2020-08-17 09:48:12 +00:00
|
|
|
pprofCmd,
|
2020-08-04 18:57:40 +00:00
|
|
|
VersionCmd,
|
2020-03-23 12:29:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-25 11:42:17 +00:00
|
|
|
var Commands = []*cli.Command{
|
2020-07-21 17:19:47 +00:00
|
|
|
WithCategory("basic", sendCmd),
|
|
|
|
WithCategory("basic", walletCmd),
|
|
|
|
WithCategory("basic", clientCmd),
|
|
|
|
WithCategory("basic", multisigCmd),
|
|
|
|
WithCategory("basic", paychCmd),
|
|
|
|
WithCategory("developer", authCmd),
|
|
|
|
WithCategory("developer", mpoolCmd),
|
|
|
|
WithCategory("developer", stateCmd),
|
|
|
|
WithCategory("developer", chainCmd),
|
|
|
|
WithCategory("developer", logCmd),
|
|
|
|
WithCategory("developer", waitApiCmd),
|
|
|
|
WithCategory("developer", fetchParamCmd),
|
|
|
|
WithCategory("network", netCmd),
|
|
|
|
WithCategory("network", syncCmd),
|
2020-08-17 09:48:12 +00:00
|
|
|
pprofCmd,
|
2020-08-04 18:57:40 +00:00
|
|
|
VersionCmd,
|
2020-05-13 11:08:59 +00:00
|
|
|
}
|
|
|
|
|
2020-07-21 17:19:47 +00:00
|
|
|
func WithCategory(cat string, cmd *cli.Command) *cli.Command {
|
2020-05-13 11:08:59 +00:00
|
|
|
cmd.Category = cat
|
|
|
|
return cmd
|
2019-06-28 09:03:28 +00:00
|
|
|
}
|