diff --git a/cmd/lotus-shed/fevmanalytics.go b/cmd/lotus-shed/fevmanalytics.go new file mode 100644 index 000000000..19416b77e --- /dev/null +++ b/cmd/lotus-shed/fevmanalytics.go @@ -0,0 +1,257 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/urfave/cli/v2" + "golang.org/x/net/context" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + badgerbs "github.com/filecoin-project/lotus/blockstore/badger" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + evm2 "github.com/filecoin-project/lotus/chain/actors/builtin/evm" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/repo" +) + +var FevmAnalyticsCmd = &cli.Command{ + Name: "evm-analytics", + Usage: "Get FEVM related metrics", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", + }, + }, + Subcommands: []*cli.Command{ + FevmBalanceCmd, + FevmActorsCmd, + }, +} + +var FevmBalanceCmd = &cli.Command{ + Name: "evm-balance", + Usage: "Balances in eth accounts, evm contracts and placeholders", + ArgsUsage: "[state root]", + + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return xerrors.New("only needs state root") + } + + if !cctx.Args().Present() { + return fmt.Errorf("must pass state root") + } + + sroot, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + fsrepo, err := repo.NewFS(cctx.String("repo")) + if err != nil { + return err + } + + lkrepo, err := fsrepo.Lock(repo.FullNode) + if err != nil { + return err + } + + defer lkrepo.Close() //nolint:errcheck + + path, err := lkrepo.SplitstorePath() + if err != nil { + return err + } + + path = filepath.Join(path, "hot.badger") + if err := os.MkdirAll(path, 0755); err != nil { + return err + } + + opts, err := repo.BadgerBlockstoreOptions(repo.HotBlockstore, path, lkrepo.Readonly()) + if err != nil { + return err + } + + bs, err := badgerbs.Open(opts) + if err != nil { + return err + } + + cst := cbor.NewCborStore(bs) + st, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err + } + + fmt.Println("iterating over all actors") + count := 0 + balanceEvm := abi.NewTokenAmount(0) + balanceEthAccount := abi.NewTokenAmount(0) + balancePlaceholder := abi.NewTokenAmount(0) + + err = st.ForEach(func(addr address.Address, act *types.Actor) error { + if count%200000 == 0 { + fmt.Println("processed /n", count) + } + count++ + + if builtin.IsEvmActor(act.Code) { + balanceEvm = types.BigAdd(balanceEvm, act.Balance) + } + + if builtin.IsEthAccountActor(act.Code) { + balanceEthAccount = types.BigAdd(balanceEthAccount, act.Balance) + } + + if builtin.IsPlaceholderActor(act.Code) { + balancePlaceholder = types.BigAdd(balancePlaceholder, act.Balance) + } + + return nil + }) + if err != nil { + return err + } + + fmt.Println("balances in Eth contracts: ", balanceEvm) + fmt.Println("balances in Eth accounts: ", balanceEthAccount) + fmt.Println("balances in placeholder: ", balancePlaceholder) + fmt.Println("Total balances: ", big.Add(big.Add(balanceEthAccount, balancePlaceholder), balanceEvm)) + return nil + }, +} + +var FevmActorsCmd = &cli.Command{ + Name: "evm-actors", + Usage: "actors # in eth accounts, evm contracts and placeholders", + ArgsUsage: "[state root]", + + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return xerrors.New("only needs state root") + } + + if !cctx.Args().Present() { + return fmt.Errorf("must pass state root") + } + + sroot, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + fsrepo, err := repo.NewFS(cctx.String("repo")) + if err != nil { + return err + } + + lkrepo, err := fsrepo.Lock(repo.FullNode) + if err != nil { + return err + } + + defer lkrepo.Close() //nolint:errcheck + + path, err := lkrepo.SplitstorePath() + if err != nil { + return err + } + + path = filepath.Join(path, "hot.badger") + if err := os.MkdirAll(path, 0755); err != nil { + return err + } + + opts, err := repo.BadgerBlockstoreOptions(repo.HotBlockstore, path, lkrepo.Readonly()) + if err != nil { + return err + } + + bs, err := badgerbs.Open(opts) + if err != nil { + return err + } + + ctx := context.TODO() + cst := cbor.NewCborStore(bs) + store := adt.WrapStore(ctx, cst) + + st, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err + } + + fmt.Println("iterating over all actors") + count := 0 + EvmCount := 0 + EthAccountCount := 0 + PlaceholderCount := 0 + ea := []cid.Cid{} + + err = st.ForEach(func(addr address.Address, act *types.Actor) error { + if count%200000 == 0 { + fmt.Println("processed /n", count) + } + count++ + + if builtin.IsEvmActor(act.Code) { + EvmCount++ + e, err := evm2.Load(store, act) + if err != nil { + return xerrors.Errorf("fail to load evm actor: %w", err) + } + bcid, err := e.GetBytecodeCID() + if err != nil { + return err + } + + ea = append(ea, bcid) + } + + if builtin.IsEthAccountActor(act.Code) { + EthAccountCount++ + } + + if builtin.IsPlaceholderActor(act.Code) { + PlaceholderCount++ + } + + return nil + }) + if err != nil { + return err + } + + uniquesa := unique(ea) + fmt.Println("# of EVM contracts: ", EvmCount) + fmt.Println("# of unqiue EVM contracts: ", len(uniquesa)) + fmt.Println("b# of Eth accounts: ", EthAccountCount) + fmt.Println("# of placeholder: ", PlaceholderCount) + return nil + }, +} + +func unique(intSlice []cid.Cid) []cid.Cid { + keys := make(map[cid.Cid]bool) + list := []cid.Cid{} + for _, entry := range intSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 6bb779859..1f4d953f9 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -85,6 +85,7 @@ func main() { gasTraceCmd, replayOfflineCmd, msgindexCmd, + FevmAnalyticsCmd, } app := &cli.App{