diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 0f10a617f..792d72968 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -73,6 +73,7 @@ func main() { migrationsCmd, diffCmd, itestdCmd, + msigCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go new file mode 100644 index 000000000..dafa9a55b --- /dev/null +++ b/cmd/lotus-shed/msig.go @@ -0,0 +1,184 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/chain/types" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/build" + + "github.com/filecoin-project/go-state-types/abi" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +var GetFullNodeAPI = cliutil.GetFullNodeAPI +var ReqContext = cliutil.ReqContext + +func LoadTipSet(ctx context.Context, cctx *cli.Context, api v0api.FullNode) (*types.TipSet, error) { + tss := cctx.String("tipset") + if tss == "" { + return api.ChainHead(ctx) + } + + return ParseTipSetRef(ctx, api, tss) +} + +func ParseTipSetRef(ctx context.Context, api v0api.FullNode, tss string) (*types.TipSet, error) { + if tss[0] == '@' { + if tss == "@head" { + return api.ChainHead(ctx) + } + + var h uint64 + if _, err := fmt.Sscanf(tss, "@%d", &h); err != nil { + return nil, xerrors.Errorf("parsing height tipset ref: %w", err) + } + + return api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(h), types.EmptyTSK) + } + + cids, err := ParseTipSetString(tss) + if err != nil { + return nil, err + } + + if len(cids) == 0 { + return nil, nil + } + + k := types.NewTipSetKey(cids...) + ts, err := api.ChainGetTipSet(ctx, k) + if err != nil { + return nil, err + } + + return ts, nil +} + +func ParseTipSetString(ts string) ([]cid.Cid, error) { + strs := strings.Split(ts, ",") + + var cids []cid.Cid + for _, s := range strs { + c, err := cid.Parse(strings.TrimSpace(s)) + if err != nil { + return nil, err + } + cids = append(cids, c) + } + + return cids, nil +} + +type msigBriefInfo struct { + ID address.Address + Signer interface{} + Balance abi.TokenAmount + Threshold float64 +} + +var msigCmd = &cli.Command{ + Name: "multisig", + Usage: "utils for multisig actors", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset to call method on (pass comma separated array of cids)", + }, + }, + Subcommands: []*cli.Command{ + multisigGetAllCmd, + }, +} + +var multisigGetAllCmd = &cli.Command{ + Name: "all", + Usage: "get all multisig actor on chain with id, siigners, threshold and balance", + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "network-version", + Value: uint(build.NewestNetworkVersion), + }, + }, + Action: func(cctx *cli.Context) error { + 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 + } + + actors, err := api.StateListActors(ctx, ts.Key()) + if err != nil { + return err + } + + nv := network.Version(cctx.Uint64("network-version")) + codeCids, err := api.StateActorCodeCIDs(ctx, nv) + if err != nil { + return err + } + + msigCid, exists := codeCids["multisig"] + if !exists { + return xerrors.Errorf("bad code cid key") + } + + var msigActorsInfo []msigBriefInfo + for _, actor := range actors { + + act, err := api.StateGetActor(ctx, actor, ts.Key()) + if err != nil { + return err + } + + if act.Code == msigCid { + + actorState, err := api.StateReadState(ctx, actor, ts.Key()) + if err != nil { + return err + } + + stateI, ok := actorState.State.(map[string]interface{}) + if !ok { + return xerrors.Errorf("fail to map msig state") + } + + signersI, _ := stateI["Signers"] + signers := signersI.([]interface{}) + thresholdI, _ := stateI["NumApprovalsThreshold"] + threshold := thresholdI.(float64) + info := msigBriefInfo{ + ID: actor, + Signer: signers, + Balance: actorState.Balance, + Threshold: threshold, + } + msigActorsInfo = append(msigActorsInfo, info) + } + } + out, err := json.MarshalIndent(msigActorsInfo, "", " ") + if err != nil { + return err + } + fmt.Println(string(out)) + return nil + }, +}