package main import ( "fmt" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/specs-actors/v2/actors/builtin" "github.com/urfave/cli/v2" ) var postFindCmd = &cli.Command{ Name: "post-find", Description: "return addresses of all miners who have over zero power and have posted in the last day", Flags: []cli.Flag{ &cli.StringFlag{ Name: "tipset", Usage: "specify tipset state to search on", }, &cli.BoolFlag{ Name: "verbose", Usage: "get more frequent print updates", }, &cli.BoolFlag{ Name: "withpower", Usage: "only print addrs of miners with more than zero power", }, &cli.IntFlag{ Name: "lookback", Usage: "number of past epochs to search for post", Value: 2880, //default 1 day }, }, Action: func(c *cli.Context) error { api, acloser, err := lcli.GetFullNodeAPI(c) if err != nil { return err } defer acloser() ctx := lcli.ReqContext(c) verbose := c.Bool("verbose") withpower := c.Bool("withpower") startTs, err := lcli.LoadTipSet(ctx, c, api) if err != nil { return err } if startTs == nil { startTs, err = api.ChainHead(ctx) if err != nil { return err } } stopEpoch := startTs.Height() - abi.ChainEpoch(c.Int("lookback")) if verbose { fmt.Printf("Collecting messages between %d and %d\n", startTs.Height(), stopEpoch) } // Get all messages over the last day ts := startTs msgs := make([]*types.Message, 0) for ts.Height() > stopEpoch { // Get messages on ts parent next, err := api.ChainGetParentMessages(ctx, ts.Cids()[0]) if err != nil { return err } msgs = append(msgs, messagesFromAPIMessages(next)...) // Next ts ts, err = api.ChainGetTipSet(ctx, ts.Parents()) if err != nil { return err } if verbose && int64(ts.Height())%100 == 0 { fmt.Printf("Collected messages back to height %d\n", ts.Height()) } } fmt.Printf("Loaded messages to height %d\n", ts.Height()) mAddrs, err := api.StateListMiners(ctx, startTs.Key()) if err != nil { return err } minersToCheck := make(map[address.Address]struct{}) for _, mAddr := range mAddrs { // if they have no power ignore. This filters out 14k inactive miners // so we can do 100x fewer expensive message queries if withpower { power, err := api.StateMinerPower(ctx, mAddr, startTs.Key()) if err != nil { return err } if power.MinerPower.RawBytePower.GreaterThan(big.Zero()) { minersToCheck[mAddr] = struct{}{} } } else { minersToCheck[mAddr] = struct{}{} } } fmt.Printf("Loaded %d miners to check\n", len(minersToCheck)) postedMiners := make(map[address.Address]struct{}) for _, msg := range msgs { _, shouldCheck := minersToCheck[msg.To] _, seenBefore := postedMiners[msg.To] if shouldCheck && !seenBefore { if msg.Method == builtin.MethodsMiner.SubmitWindowedPoSt { fmt.Printf("%s\n", msg.To) postedMiners[msg.To] = struct{}{} } } } return nil }, } func messagesFromAPIMessages(apiMessages []lapi.Message) []*types.Message { messages := make([]*types.Message, len(apiMessages)) for i, apiMessage := range apiMessages { messages[i] = apiMessage.Message } return messages }