diff --git a/cli/cmd.go b/cli/cmd.go index 71524d787..79023917b 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -69,6 +69,7 @@ var CommonCommands = []*cli.Command{ var Commands = []*cli.Command{ WithCategory("basic", sendCmd), WithCategory("basic", walletCmd), + WithCategory("basic", infoCmd), WithCategory("basic", clientCmd), WithCategory("basic", multisigCmd), WithCategory("basic", filplusCmd), diff --git a/cli/info.go b/cli/info.go new file mode 100644 index 000000000..0d036875a --- /dev/null +++ b/cli/info.go @@ -0,0 +1,230 @@ +package cli + +import ( + "context" + "fmt" + "math" + "os" + "sort" + "strings" + "text/tabwriter" + "time" + + "github.com/dustin/go-humanize" + "github.com/fatih/color" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +var infoCmd = &cli.Command{ + Name: "info", + Usage: "Print node info", + Action: infoCmdAct, +} + +func infoCmdAct(cctx *cli.Context) error { + fullapi, acloser, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer acloser() + ctx := ReqContext(cctx) + + network, err := fullapi.StateGetNetworkParams(ctx) + if err != nil { + return err + } + + fmt.Printf("Network: %s\n", network.NetworkName) + fmt.Print("Chain: ") + err = SyncBasefeeCheck(ctx, fullapi) + if err != nil { + return err + } + + status, err := fullapi.NodeStatus(ctx, true) + if err != nil { + return err + } + + fmt.Printf(" [epoch %s]\n", color.MagentaString(("%d"), status.SyncStatus.Epoch)) + fmt.Printf("Peers to: [publish messages %d] [publish blocks %d]\n", status.PeerStatus.PeersToPublishMsgs, status.PeerStatus.PeersToPublishBlocks) + + //Chain health calculated as percentage: amount of blocks in last finality / very healthy amount of blocks in a finality (900 epochs * 5 blocks per tipset) + health := (100 * (900 * status.ChainStatus.BlocksPerTipsetLastFinality) / (900 * 5)) + switch { + case health > 85: + fmt.Printf("Chain health: %.f%% [%s]\n", health, color.GreenString("healthy")) + case health < 85: + fmt.Printf("Chain health: %.f%% [%s]\n", health, color.RedString("unhealthy")) + } + + fmt.Println() + + addr, err := fullapi.WalletDefaultAddress(ctx) + if err == nil { + fmt.Printf("Default address: \n") + balance, err := fullapi.WalletBalance(ctx, addr) + if err != nil { + return err + } + fmt.Printf(" %s [%s]\n", addr.String(), types.FIL(balance).Short()) + } else { + fmt.Printf("Default address: address not set\n") + } + fmt.Println() + + addrs, err := fullapi.WalletList(ctx) + if err != nil { + return err + } + + totalBalance := big.Zero() + for _, addr := range addrs { + totbal, err := fullapi.WalletBalance(ctx, addr) + if err != nil { + return err + } + totalBalance = big.Add(totalBalance, totbal) + } + + switch { + case len(addrs) <= 1: + fmt.Printf("Wallet: %v address\n", len(addrs)) + case len(addrs) > 1: + fmt.Printf("Wallet: %v addresses\n", len(addrs)) + } + fmt.Printf(" Total balance: %s\n", types.FIL(totalBalance).Short()) + + mbLockedSum := big.Zero() + mbAvailableSum := big.Zero() + for _, addr := range addrs { + mbal, err := fullapi.StateMarketBalance(ctx, addr, types.EmptyTSK) + if err != nil { + if strings.Contains(err.Error(), "actor not found") { + continue + } else { + return err + } + } + mbLockedSum = big.Add(mbLockedSum, mbal.Locked) + mbAvailableSum = big.Add(mbAvailableSum, mbal.Escrow) + } + + fmt.Printf(" Market locked: %s\n", types.FIL(mbLockedSum).Short()) + fmt.Printf(" Market available: %s\n", types.FIL(mbAvailableSum).Short()) + + fmt.Println() + + chs, err := fullapi.PaychList(ctx) + if err != nil { + return err + } + + switch { + case len(chs) <= 1: + fmt.Printf("Payment Channels: %v channel\n", len(chs)) + case len(chs) > 1: + fmt.Printf("Payment Channels: %v channels\n", len(chs)) + } + fmt.Println() + + localDeals, err := fullapi.ClientListDeals(ctx) + if err != nil { + return err + } + + var totalSize uint64 + byState := map[storagemarket.StorageDealStatus][]uint64{} + for _, deal := range localDeals { + totalSize += deal.Size + byState[deal.State] = append(byState[deal.State], deal.Size) + } + + fmt.Printf("Deals: %d, %s\n", len(localDeals), types.SizeStr(types.NewInt(totalSize))) + + type stateStat struct { + state storagemarket.StorageDealStatus + count int + bytes uint64 + } + + stateStats := make([]stateStat, 0, len(byState)) + for state, deals := range byState { + if state == storagemarket.StorageDealActive { + state = math.MaxUint64 // for sort + } + + st := stateStat{ + state: state, + count: len(deals), + } + for _, b := range deals { + st.bytes += b + } + + stateStats = append(stateStats, st) + } + + sort.Slice(stateStats, func(i, j int) bool { + return int64(stateStats[i].state) < int64(stateStats[j].state) + }) + + for _, st := range stateStats { + if st.state == math.MaxUint64 { + st.state = storagemarket.StorageDealActive + } + fmt.Printf(" %s: %d deals, %s\n", storagemarket.DealStates[st.state], st.count, types.SizeStr(types.NewInt(st.bytes))) + } + + fmt.Println() + + tw := tabwriter.NewWriter(os.Stdout, 6, 6, 2, ' ', 0) + + s, err := fullapi.NetBandwidthStats(ctx) + if err != nil { + return err + } + + fmt.Printf("Bandwidth:\n") + fmt.Fprintf(tw, "\tTotalIn\tTotalOut\tRateIn\tRateOut\n") + fmt.Fprintf(tw, "\t%s\t%s\t%s/s\t%s/s\n", humanize.Bytes(uint64(s.TotalIn)), humanize.Bytes(uint64(s.TotalOut)), humanize.Bytes(uint64(s.RateIn)), humanize.Bytes(uint64(s.RateOut))) + return tw.Flush() + +} + +func SyncBasefeeCheck(ctx context.Context, fullapi v1api.FullNode) error { + head, err := fullapi.ChainHead(ctx) + if err != nil { + return err + } + + switch { + case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*3/2): // within 1.5 epochs + fmt.Printf("[%s]", color.GreenString("sync ok")) + case time.Now().Unix()-int64(head.MinTimestamp()) < int64(build.BlockDelaySecs*5): // within 5 epochs + fmt.Printf("[%s]", color.YellowString("sync slow (%s behind)", time.Now().Sub(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second))) + default: + fmt.Printf("[%s]", color.RedString("sync behind! (%s behind)", time.Now().Sub(time.Unix(int64(head.MinTimestamp()), 0)).Truncate(time.Second))) + } + basefee := head.MinTicketBlock().ParentBaseFee + gasCol := []color.Attribute{color.FgBlue} + switch { + case basefee.GreaterThan(big.NewInt(7000_000_000)): // 7 nFIL + gasCol = []color.Attribute{color.BgRed, color.FgBlack} + case basefee.GreaterThan(big.NewInt(3000_000_000)): // 3 nFIL + gasCol = []color.Attribute{color.FgRed} + case basefee.GreaterThan(big.NewInt(750_000_000)): // 750 uFIL + gasCol = []color.Attribute{color.FgYellow} + case basefee.GreaterThan(big.NewInt(100_000_000)): // 100 uFIL + gasCol = []color.Attribute{color.FgGreen} + } + fmt.Printf(" [basefee %s]", color.New(gasCol...).Sprint(types.FIL(basefee).Short())) + return nil +} diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 5cb6b42aa..15178d8e0 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -18,6 +18,7 @@ COMMANDS: BASIC: send Send funds between accounts wallet Manage wallet + info Print node info client Make deals, store data, retrieve data msig Interact with a multisig wallet filplus Interact with the verified registry actor used by Filplus @@ -398,6 +399,22 @@ OPTIONS: ``` +## lotus info +``` +NAME: + lotus info - Print node info + +USAGE: + lotus info [command options] [arguments...] + +CATEGORY: + BASIC + +OPTIONS: + --help, -h show help (default: false) + +``` + ## lotus client ``` NAME: