package cli import ( "bytes" "context" "encoding/base64" "encoding/hex" "encoding/json" "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" evm "github.com/filecoin-project/go-state-types/builtin/v10/evm" "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" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/consensus/filcns" "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, SlashConsensusFault, ChainGasPriceCmd, ChainInspectUsage, ChainDecodeCmd, ChainEncodeCmd, ChainDisputeSetCmd, ChainPruneCmd, ChainExecEVMCmd, ChainInvokeEVMCmd, }, } var ChainHeadCmd = &cli.Command{ Name: "head", Usage: "Print 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 } 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.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 := 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 } 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) 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) 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. 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) 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.Args().Present() { return fmt.Errorf("must pass a cid of a message to get") } 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) 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 := filcns.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 }, } 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: ": (