From 8fa6ea762da15ed622669b2e0d89d91510074b4a Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 10 May 2025 04:16:00 +1000 Subject: [PATCH] feat(shed): lotus-shed find-msg --count X actor:Method Search back through the chain to find messages of a particular type and print them out. Also includes the ability to add special cases for more information, currently just PCS3 which will also print out the number of sectors and the proof type. --- cmd/lotus-shed/find-msg.go | 224 +++++++++++++++++++++++++++++++++++++ cmd/lotus-shed/main.go | 1 + 2 files changed, 225 insertions(+) create mode 100644 cmd/lotus-shed/find-msg.go diff --git a/cmd/lotus-shed/find-msg.go b/cmd/lotus-shed/find-msg.go new file mode 100644 index 00000000000..13c99f148cf --- /dev/null +++ b/cmd/lotus-shed/find-msg.go @@ -0,0 +1,224 @@ +package main + +import ( + "bytes" + "fmt" + "reflect" + "slices" + "strings" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + miner16 "github.com/filecoin-project/go-state-types/builtin/v16/miner" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + lcli "github.com/filecoin-project/lotus/cli" +) + +var findMsgCmd = &cli.Command{ + Name: "find-msg", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "tipset or height (@X or @head for latest)", + Value: "@head", + }, + &cli.IntFlag{ + Name: "count", + Usage: "number of tipsets to inspect, working backwards from the --tipset", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("failed to get full node API: %w", err) + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + ts, err := lcli.LoadTipSet(ctx, cctx, api) + if err != nil { + return xerrors.Errorf("failed to load tipset: %w", err) + } + + if !cctx.IsSet("count") { + return fmt.Errorf("count is required") + } + count := cctx.Int("count") + + if cctx.Args().Len() != 1 { + return fmt.Errorf("method (:) is required") + } + method := cctx.Args().Get(0) + if len(strings.Split(method, ":")) != 2 { + return fmt.Errorf("method (:) is required") + } + actor, methodName := strings.Split(method, ":")[0], strings.Split(method, ":")[1] + + nv, err := api.StateNetworkVersion(ctx, ts.Key()) + if err != nil { + return xerrors.Errorf("failed to get network version: %w", err) + } + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return xerrors.Errorf("failed to get actor version: %w", err) + } + actorKeys := manifest.GetBuiltinActorsKeys(av) + if !slices.Contains(actorKeys, actor) { + return fmt.Errorf("actor %s not valid, one one of: %s", actor, strings.Join(actorKeys, ", ")) + } + + methods, err := (func() (any, error) { + switch actor { + case "account": + return builtin.MethodsAccount, nil + case "cron": + return builtin.MethodsCron, nil + case "init": + return builtin.MethodsInit, nil + case "storagemarket": + return builtin.MethodsMarket, nil + case "storageminer": + return builtin.MethodsMiner, nil + case "multisig": + return builtin.MethodsMultisig, nil + case "paymentchannel": + return builtin.MethodsPaych, nil + case "storagepower": + return builtin.MethodsPower, nil + case "reward": + return builtin.MethodsReward, nil + case "system": + return struct{}{}, nil // nothing here + case "verifiedregistry": + return builtin.MethodsVerifiedRegistry, nil + case "datacap": + return builtin.MethodsDatacap, nil + case "evm": + return builtin.MethodsEVM, nil + case "eam": + return builtin.MethodsEAM, nil + case "placeholder": + return builtin.MethodsPlaceholder, nil + case "ethaccount": + return builtin.MethodsEthAccount, nil + default: + return nil, fmt.Errorf("actor %s not valid, one one of: %s", actor, strings.Join(actorKeys, ", ")) + } + }()) + if err != nil { + return xerrors.Errorf("failed to get methods for actor %s: %w", actor, err) + } + + // use reflection to find the field name on the methods object, and then the uint64 value of that field + + var methodNum abi.MethodNum + var found bool + methodNames := make([]string, 0) + value := reflect.ValueOf(methods) + typeOfMethods := value.Type() + for i := 0; i < typeOfMethods.NumField(); i++ { + field := typeOfMethods.Field(i) + if field.Name == methodName { + methodNum = abi.MethodNum(value.Field(i).Uint()) + found = true + break + } + methodNames = append(methodNames, field.Name) + } + if !found && methodName != "Send" { + return fmt.Errorf("method %s not valid on actor %s, want one of: %s", methodName, actor, strings.Join(methodNames, ", ")) + } // else Send as method 0 is a special case we can handle + + var msgCnt int + actorIsType := make(map[address.Address]bool) + metaCache := make(map[cid.Cid]string) + + additional := "" + switch actor { + case "storageminer": + switch methodName { + case "ProveCommitSectors3": + additional = ", Sectors, Type" + } + } + _, _ = fmt.Fprintf(cctx.App.Writer, "Epoch, MsgCid, From, To, Method%s\n", additional) + + for i := 0; i < count; i++ { + if ts.Height()%2880 == 0 { + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "Epoch: %d, messages: %d\n", ts.Height(), msgCnt) + } + msgs, err := api.ChainGetMessagesInTipset(ctx, ts.Key()) + if err != nil { + return xerrors.Errorf("failed to get messages in tipset: %w", err) + } + msgCnt += len(msgs) + + for _, msg := range msgs { + if msg.Message.Method != methodNum { + continue + } + if is, ok := actorIsType[msg.Message.To]; ok && !is { + continue + } + + var name string + act, err := api.StateGetActor(ctx, msg.Message.To, ts.Key()) + if err != nil && !strings.Contains(err.Error(), "actor not found") { + return xerrors.Errorf("failed to get actor: %w", err) + } else if err != nil { + // not found, assume it's an account actor (may not be, but that's a TODO) + name = "account" + } else { + var has bool + if name, has = metaCache[act.Code]; !has { + var ok bool + if name, _, ok = actors.GetActorMetaByCode(act.Code); ok { + metaCache[act.Code] = name + } else { + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "Unknown actor code: %s\n", act.Code) + } + } + } + if name != actor { + actorIsType[msg.Message.To] = false + continue + } + actorIsType[msg.Message.To] = true + + additional := "" + + switch actor { + case "storageminer": + switch methodName { + case "ProveCommitSectors3": + var params miner16.ProveCommitSectors3Params + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Message.Params)); err != nil { + return err + } + typ := "batch" + if params.AggregateProof != nil { + typ = "aggregate" + } + additional = fmt.Sprintf(", %d, %s", len(params.SectorActivations), typ) + } + } + + _, _ = fmt.Fprintf(cctx.App.Writer, "%d, %s, %s, %s, %d%s\n", ts.Height(), msg.Cid, msg.Message.From, msg.Message.To, msg.Message.Method, additional) + } + + if ts, err = api.ChainGetTipSet(ctx, ts.Parents()); err != nil { + return xerrors.Errorf("failed to get tipset: %w", err) + } + } + return nil + }, +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 1beebee723b..f8a78f8750d 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -93,6 +93,7 @@ func main() { blockCmd, adlCmd, f3Cmd, + findMsgCmd, } app := &cli.App{