diff --git a/cli/chain.go b/cli/chain.go index 0c25a881c6b..24d1c7414fe 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1,1711 +1,1290 @@ package cli import ( - "bytes" - "context" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path" - "sort" - "strconv" - "strings" - "time" - - "github.com/ipfs/go-cid" - "github.com/urfave/cli/v2" - cbg "github.com/whyrusleeping/cbor-gen" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/util/adt" - - "github.com/filecoin-project/lotus/api" - lapi "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/v0api" - "github.com/filecoin-project/lotus/build/buildconstants" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/actors/policy" - "github.com/filecoin-project/lotus/chain/consensus" - "github.com/filecoin-project/lotus/chain/types" + "bytes" + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path" + "sort" + "strconv" + "strings" + "time" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + cborutil "github.com/filecoin-project/go-cbor-util" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/account" + "github.com/filecoin-project/specs-actors/actors/builtin/market" + "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/api" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/build/buildconstants" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/types" ) var ChainCmd = &cli.Command{ - Name: "chain", - Usage: "Interact with filecoin blockchain", - Subcommands: []*cli.Command{ - ChainHeadCmd, - ChainGetBlock, - ChainReadObjCmd, - ChainDeleteObjCmd, - ChainStatObjCmd, - ChainGetMsgCmd, - ChainSetHeadCmd, - ChainListCmd, - ChainGetCmd, - ChainBisectCmd, - ChainExportCmd, - ChainExportRangeCmd, - SlashConsensusFault, - ChainGasPriceCmd, - ChainInspectUsage, - ChainDecodeCmd, - ChainEncodeCmd, - ChainDisputeSetCmd, - ChainPruneCmd, - }, + Name: "chain", + Usage: "Interact with filecoin blockchain", + Subcommands: []*cli.Command{ + ChainHeadCmd, + ChainGetBlock, + ChainReadObjCmd, + ChainDeleteObjCmd, + ChainStatObjCmd, + ChainGetMsgCmd, + ChainSetHeadCmd, + ChainListCmd, + ChainGetCmd, + ChainBisectCmd, + ChainExportCmd, + ChainExportRangeCmd, + SlashConsensusFault, + ChainGasPriceCmd, + ChainInspectUsage, + ChainDecodeCmd, + ChainEncodeCmd, + ChainDisputeSetCmd, + ChainPruneCmd, + }, } var ChainHeadCmd = &cli.Command{ - Name: "head", - Usage: "Print chain head", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "height", - Usage: "print just the epoch number of the chain head", - }, - }, - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - head, err := api.ChainHead(ctx) - if err != nil { - return err - } - - if cctx.Bool("height") { - afmt.Println(head.Height()) - } else { - for _, c := range head.Cids() { - afmt.Println(c) - } - } - return nil - }, + Name: "head", + Usage: "Print chain head", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "height", + Usage: "print just the epoch number of the chain head", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + head, err := api.ChainHead(ctx) + if err != nil { + return err + } + + if cctx.Bool("height") { + afmt.Println(head.Height()) + } else { + for _, c := range head.Cids() { + afmt.Println(c) + } + } + return nil + }, } var ChainGetBlock = &cli.Command{ - Name: "get-block", - Aliases: []string{"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 { - afmt := NewAppFmt(cctx.App) - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - bcid, err := cid.Decode(cctx.Args().First()) - if err != nil { - return err - } - - blk, err := api.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 - } - - afmt.Println(string(out)) - return nil - } - - msgs, err := api.ChainGetBlockMessages(ctx, bcid) - if err != nil { - return xerrors.Errorf("failed to get messages: %w", err) - } - - pmsgs, err := api.ChainGetParentMessages(ctx, bcid) - if err != nil { - return xerrors.Errorf("failed to get parent messages: %w", err) - } - - recpts, err := api.ChainGetParentReceipts(ctx, bcid) - if err != nil { - log.Warn(err) - // return xerrors.Errorf("failed to get receipts: %w", 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 - } - - afmt.Println(string(out)) - return nil - }, + Name: "get-block", + Aliases: []string{"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 { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + bcid, err := cid.Decode(cctx.Args().First()) + if err != nil { + return err + } + + blk, err := api.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 + } + + afmt.Println(string(out)) + return nil + } + + msgs, err := api.ChainGetBlockMessages(ctx, bcid) + if err != nil { + return xerrors.Errorf("failed to get messages: %w", err) + } + + pmsgs, err := api.ChainGetParentMessages(ctx, bcid) + if err != nil { + return xerrors.Errorf("failed to get parent messages: %w", err) + } + + recpts, err := api.ChainGetParentReceipts(ctx, bcid) + if err != nil { + log.Warn(err) + // return xerrors.Errorf("failed to get receipts: %w", 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 + } + + afmt.Println(string(out)) + return nil + }, } func apiMsgCids(in []lapi.Message) []cid.Cid { - out := make([]cid.Cid, len(in)) - for k, v := range in { - out[k] = v.Cid - } - return out + out := make([]cid.Cid, len(in)) + for k, v := range in { + out[k] = v.Cid + } + return out } var ChainReadObjCmd = &cli.Command{ - Name: "read-obj", - Usage: "Read the raw bytes of an object", - ArgsUsage: "[objectCid]", - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - c, err := cid.Decode(cctx.Args().First()) - if err != nil { - return fmt.Errorf("failed to parse cid input: %s", err) - } - - obj, err := api.ChainReadObj(ctx, c) - if err != nil { - return err - } - - afmt.Printf("%x\n", obj) - return nil - }, + Name: "read-obj", + Usage: "Read the raw bytes of an object", + ArgsUsage: "[objectCid]", + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + c, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse cid input: %s", err) + } + + obj, err := api.ChainReadObj(ctx, c) + if err != nil { + return err + } + + afmt.Printf("%x\n", obj) + return nil + }, } var ChainDeleteObjCmd = &cli.Command{ - Name: "delete-obj", - Usage: "Delete an object from the chain blockstore", - Description: "WARNING: Removing wrong objects from the chain blockstore may lead to sync issues", - ArgsUsage: "[objectCid]", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "really-do-it", - }, - }, - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - c, err := cid.Decode(cctx.Args().First()) - if err != nil { - return fmt.Errorf("failed to parse cid input: %s", err) - } - - if !cctx.Bool("really-do-it") { - return xerrors.Errorf("pass the --really-do-it flag to proceed") - } - - err = api.ChainDeleteObj(ctx, c) - if err != nil { - return err - } - - afmt.Printf("Obj %s deleted\n", c.String()) - return nil - }, + Name: "delete-obj", + Usage: "Delete an object from the chain blockstore", + Description: "WARNING: Removing wrong objects from the chain blockstore may lead to sync issues", + ArgsUsage: "[objectCid]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + c, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse cid input: %s", err) + } + + if !cctx.Bool("really-do-it") { + return xerrors.Errorf("pass the --really-do-it flag to proceed") + } + + err = api.ChainDeleteObj(ctx, c) + if err != nil { + return err + } + + afmt.Printf("Obj %s deleted\n", c.String()) + return nil + }, } 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. + 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 { - afmt := NewAppFmt(cctx.App) - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - 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 := api.ChainStatObj(ctx, obj, base) - if err != nil { - return err - } - - afmt.Printf("Links: %d\n", stats.Links) - afmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size) - return nil - }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "base", + Usage: "ignore links found in this obj", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + 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 := api.ChainStatObj(ctx, obj, base) + if err != nil { + return err + } + + afmt.Printf("Links: %d\n", stats.Links) + afmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size) + return nil + }, } var ChainGetMsgCmd = &cli.Command{ - Name: "getmessage", - Aliases: []string{"get-message", "get-msg"}, - Usage: "Get and print a message by its cid", - ArgsUsage: "[messageCid]", - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - c, err := cid.Decode(cctx.Args().First()) - if err != nil { - return xerrors.Errorf("failed to parse cid input: %w", err) - } - - mb, err := api.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 - } - - afmt.Println(string(enc)) - return nil - }, + Name: "getmessage", + Aliases: []string{"get-message", "get-msg"}, + Usage: "Get and print a message by its cid", + ArgsUsage: "[messageCid]", + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + c, err := cid.Decode(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("failed to parse cid input: %w", err) + } + + mb, err := api.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 + } + + afmt.Println(string(enc)) + return nil + }, } var ChainSetHeadCmd = &cli.Command{ - Name: "sethead", - Aliases: []string{"set-head"}, - 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 { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - if !cctx.Bool("genesis") && !cctx.IsSet("epoch") && cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - var ts *types.TipSet - - if cctx.Bool("genesis") { - ts, err = api.ChainGetGenesis(ctx) - } - if ts == nil && cctx.IsSet("epoch") { - ts, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), types.EmptyTSK) - } - if ts == nil { - ts, err = parseTipSet(ctx, api, 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 := api.ChainSetHead(ctx, ts.Key()); err != nil { - return err - } - - return nil - }, + Name: "sethead", + Aliases: []string{"set-head"}, + 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 { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if !cctx.Bool("genesis") && !cctx.IsSet("epoch") && cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + var ts *types.TipSet + + if cctx.Bool("genesis") { + ts, err = api.ChainGetGenesis(ctx) + } + if ts == nil && cctx.IsSet("epoch") { + ts, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), types.EmptyTSK) + } + if ts == nil { + ts, err = parseTipSet(ctx, api, 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 := api.ChainSetHead(ctx, ts.Key()); err != nil { + return err + } + + return nil + }, } var ChainInspectUsage = &cli.Command{ - Name: "inspect-usage", - Usage: "Inspect block space usage of a given tipset", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "tipset", - Usage: "specify tipset to view block space usage of", - Value: "@head", - }, - &cli.IntFlag{ - Name: "length", - Usage: "length of chain to inspect block space usage for", - Value: 1, - }, - &cli.IntFlag{ - Name: "num-results", - Usage: "number of results to print per category", - Value: 10, - }, - }, - Action: func(cctx *cli.Context) error { - afmt := NewAppFmt(cctx.App) - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := ReqContext(cctx) - - ts, err := LoadTipSet(ctx, cctx, api) - if err != nil { - return err - } - - cur := ts - var msgs []lapi.Message - for i := 0; i < cctx.Int("length"); i++ { - pmsgs, err := api.ChainGetParentMessages(ctx, cur.Blocks()[0].Cid()) - if err != nil { - return err - } - - msgs = append(msgs, pmsgs...) - - next, err := api.ChainGetTipSet(ctx, cur.Parents()) - if err != nil { - return err - } - - cur = next - } - - codeCache := make(map[address.Address]cid.Cid) - - lookupActorCode := func(a address.Address) (cid.Cid, error) { - c, ok := codeCache[a] - if ok { - return c, nil - } - - act, err := api.StateGetActor(ctx, a, ts.Key()) - if err != nil { - return cid.Undef, err - } - - codeCache[a] = act.Code - return act.Code, nil - } - - bySender := make(map[string]int64) - byDest := make(map[string]int64) - byMethod := make(map[string]int64) - bySenderC := make(map[string]int64) - byDestC := make(map[string]int64) - byMethodC := make(map[string]int64) - - var sum int64 - for _, m := range msgs { - bySender[m.Message.From.String()] += m.Message.GasLimit - bySenderC[m.Message.From.String()]++ - byDest[m.Message.To.String()] += m.Message.GasLimit - byDestC[m.Message.To.String()]++ - sum += m.Message.GasLimit - - code, err := lookupActorCode(m.Message.To) - if err != nil { - if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - continue - } - return err - } - - mm := consensus.NewActorRegistry().Methods[code][m.Message.Method] // TODO: use remote map - - byMethod[mm.Name] += m.Message.GasLimit - byMethodC[mm.Name]++ - } - - type keyGasPair struct { - Key string - Gas int64 - } - - mapToSortedKvs := func(m map[string]int64) []keyGasPair { - var vals []keyGasPair - for k, v := range m { - vals = append(vals, keyGasPair{ - Key: k, - Gas: v, - }) - } - sort.Slice(vals, func(i, j int) bool { - return vals[i].Gas > vals[j].Gas - }) - return vals - } - - senderVals := mapToSortedKvs(bySender) - destVals := mapToSortedKvs(byDest) - methodVals := mapToSortedKvs(byMethod) - - numRes := cctx.Int("num-results") - - afmt.Printf("Total Gas Limit: %d\n", sum) - afmt.Printf("By Sender:\n") - for i := 0; i < numRes && i < len(senderVals); i++ { - sv := senderVals[i] - afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, bySenderC[sv.Key]) - } - afmt.Println() - afmt.Printf("By Receiver:\n") - for i := 0; i < numRes && i < len(destVals); i++ { - sv := destVals[i] - afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byDestC[sv.Key]) - } - afmt.Println() - afmt.Printf("By Method:\n") - for i := 0; i < numRes && i < len(methodVals); i++ { - sv := methodVals[i] - afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byMethodC[sv.Key]) - } - - return nil - }, + Name: "inspect-usage", + Usage: "Inspect block space usage of a given tipset", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset to view block space usage of", + Value: "@head", + }, + &cli.IntFlag{ + Name: "length", + Usage: "length of chain to inspect block space usage for", + Value: 1, + }, + &cli.IntFlag{ + Name: "num-results", + Usage: "number of results to print per category", + Value: 10, + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + ts, err := LoadTipSet(ctx, cctx, api) + if err != nil { + return err + } + + cur := ts + var msgs []lapi.Message + for i := 0; i < cctx.Int("length"); i++ { + pmsgs, err := api.ChainGetParentMessages(ctx, cur.Blocks()[0].Cid()) + if err != nil { + return err + } + + msgs = append(msgs, pmsgs...) + + next, err := api.ChainGetTipSet(ctx, cur.Parents()) + if err != nil { + return err + } + + cur = next + } + + codeCache := make(map[address.Address]cid.Cid) + + lookupActorCode := func(a address.Address) (cid.Cid, error) { + c, ok := codeCache[a] + if ok { + return c, nil + } + + act, err := api.StateGetActor(ctx, a, ts.Key()) + if err != nil { + return cid.Undef, err + } + + codeCache[a] = act.Code + return act.Code, nil + } + + bySender := make(map[string]int64) + byDest := make(map[string]int64) + byMethod := make(map[string]int64) + bySenderC := make(map[string]int64) + byDestC := make(map[string]int64) + byMethodC := make(map[string]int64) + + var sum int64 + for _, m := range msgs { + bySender[m.Message.From.String()] += m.Message.GasLimit + bySenderC[m.Message.From.String()]++ + byDest[m.Message.To.String()] += m.Message.GasLimit + byDestC[m.Message.To.String()]++ + sum += m.Message.GasLimit + + code, err := lookupActorCode(m.Message.To) + if err != nil { + if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { + continue + } + return err + } + + mm := consensus.NewActorRegistry().Methods[code][m.Message.Method] // TODO: use remote map + + byMethod[mm.Name] += m.Message.GasLimit + byMethodC[mm.Name]++ + } + + type keyGasPair struct { + Key string + Gas int64 + } + + mapToSortedKvs := func(m map[string]int64) []keyGasPair { + var vals []keyGasPair + for k, v := range m { + vals = append(vals, keyGasPair{ + Key: k, + Gas: v, + }) + } + sort.Slice(vals, func(i, j int) bool { + return vals[i].Gas > vals[j].Gas + }) + return vals + } + + senderVals := mapToSortedKvs(bySender) + destVals := mapToSortedKvs(byDest) + methodVals := mapToSortedKvs(byMethod) + + numRes := cctx.Int("num-results") + + data := map[string]interface{}{ + "TotalGasLimit": sum, + "BySender": senderVals, + "ByReceiver": destVals, + "ByMethod": methodVals, + } + + return util.FormatOutput(cctx, data) + }, } var ChainListCmd = &cli.Command{ - Name: "list", - Aliases: []string{"love"}, - Usage: "View a segment of the chain", - Flags: []cli.Flag{ - &cli.Uint64Flag{Name: "epoch", Aliases: []string{"height"}, DefaultText: "current head"}, - &cli.IntFlag{Name: "count", Value: 30}, - &cli.StringFlag{ - Name: "format", - Usage: "specify the format to print out tipsets using placeholders: ,