diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 5da5c0188..ad4a262b0 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -30,6 +30,7 @@ func main() { importObjectCmd, commpToCidCmd, fetchParamCmd, + postFindCmd, proofsCmd, verifRegCmd, miscCmd, diff --git a/cmd/lotus-shed/postfind.go b/cmd/lotus-shed/postfind.go new file mode 100644 index 000000000..83006fd09 --- /dev/null +++ b/cmd/lotus-shed/postfind.go @@ -0,0 +1,129 @@ +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 +}