diff --git a/cmd/lotus-shed/fip-0036.go b/cmd/lotus-shed/fip-0036.go new file mode 100644 index 000000000..4e53f7666 --- /dev/null +++ b/cmd/lotus-shed/fip-0036.go @@ -0,0 +1,287 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/store" + cbor "github.com/ipfs/go-ipld-cbor" + + "github.com/filecoin-project/lotus/node/repo" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + + "github.com/filecoin-project/lotus/chain/state" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/types" + + "github.com/mitchellh/go-homedir" + + "golang.org/x/xerrors" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-address" +) + +const APPROVE = 49 + +type Vote struct { + OptionID uint64 `json: "optionId"` + SignerAddress address.Address `json: "signerAddress"` +} + +type msigVote struct { + Multisig msigBriefInfo + AcceptanceSingers []address.Address + RejectionSigners []address.Address +} + +// https://filpoll.io/poll/16 +// snapshot height: 2162760 +// state root: bafy2bzacebdnzh43hw66bmvguk65wiwr5ssaejlq44fpdei2ysfh3eefpdlqs +var fip0036PollResultcmd = &cli.Command{ + Name: "fip0036poll", + Usage: "Process the FIP0036 FilPoll result", + ArgsUsage: "[state root, votes]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", + }, + }, + Subcommands: []*cli.Command{ + //spRBPCmd, + //spDealByteCmd, + //clientCmd, + tokenHolderCmd, + //coreDevCmd, + //finalResultCmd, + }, +} + +func getVoters(file string) ([]Vote, error) { + + var votes []Vote + vb, err := ioutil.ReadFile(file) + if err != nil { + return nil, xerrors.Errorf("read vote: %w", err) + } + + if err := json.Unmarshal(vb, &votes); err != nil { + return nil, xerrors.Errorf("unmarshal vote: %w", err) + } + return votes, nil +} + +func getAllMsig(st *state.StateTree, store adt.Store) ([]msigBriefInfo, error) { + + var msigActorsInfo []msigBriefInfo + err := st.ForEach(func(addr address.Address, act *types.Actor) error { + if builtin.IsMultisigActor(act.Code) { + ms, err := multisig.Load(store, act) + if err != nil { + return fmt.Errorf("load msig failed %v", err) + + } + + signers, _ := ms.Signers() + threshold, _ := ms.Threshold() + info := msigBriefInfo{ + ID: addr, + Signer: signers, + Balance: act.Balance, + Threshold: threshold, + } + msigActorsInfo = append(msigActorsInfo, info) + + } + return nil + }) + if err != nil { + return nil, err + } + return msigActorsInfo, nil +} + +var tokenHolderCmd = &cli.Command{ + Name: "token-holder", + Usage: "get poll result for token holder group", + ArgsUsage: "[state root]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 2 { + return xerrors.New("filpoll0036 token-holder [state root] [votes.json]") + } + + ctx := context.TODO() + 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 + + bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + return fmt.Errorf("failed to open blockstore: %w", err) + } + + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + } + }() + + mds, err := lkrepo.Datastore(context.Background(), "/metadata") + if err != nil { + return err + } + + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + cst := cbor.NewCborStore(bs) + store := adt.WrapStore(ctx, cst) + + tree, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err + } + + //get all the votes' singer address && their vote + vj, err := homedir.Expand(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("fail to get votes json") + } + votes, err := getVoters(vj) + if err != nil { + return xerrors.Errorf("fail to get votesrs: ", err) + } + //get all the msig + msigs, err := getAllMsig(tree, store) + + var acceptanceVote []Vote + var rejectionVote []Vote + acceptanceBalance := abi.NewTokenAmount(0) + rejectionBalance := abi.NewTokenAmount(0) + msigPendingVotes := make(map[address.Address]msigVote) + msigFinalVotes := make(map[address.Address]msigVote) + + for _, v := range votes { + a, err := tree.GetActor(v.SignerAddress) + if err != nil { + return xerrors.Errorf("fail to get account account for signer: ", v.SignerAddress) + } + //regular account + if v.OptionID == APPROVE { + acceptanceVote = append(acceptanceVote, v) + acceptanceBalance = types.BigAdd(acceptanceBalance, a.Balance) + } else { + rejectionVote = append(rejectionVote, v) + rejectionBalance = types.BigAdd(rejectionBalance, a.Balance) + } + + //msig + si, err := tree.LookupID(v.SignerAddress) + if err != nil { + return xerrors.Errorf("cannot resolve singer: ", si) + } + for _, m := range msigs { + + for _, ms := range m.Signer { + if ms == si { + if mv, found := msigPendingVotes[m.ID]; found { //other singer has voted + + if v.OptionID == APPROVE { + mv.AcceptanceSingers = append(mv.AcceptanceSingers, v.SignerAddress) + } else { + mv.RejectionSigners = append(mv.RejectionSigners, v.SignerAddress) + } + + //check if threshold meet + if uint64(len(mv.AcceptanceSingers)) == m.Threshold { + delete(msigPendingVotes, m.ID) + msigFinalVotes[m.ID] = mv + acceptanceBalance = types.BigAdd(acceptanceBalance, m.Balance) + } else if uint64(len(mv.RejectionSigners)) == m.Threshold { + delete(msigPendingVotes, m.ID) + msigFinalVotes[m.ID] = mv + rejectionBalance = types.BigAdd(rejectionBalance, m.Balance) + } else { + msigPendingVotes[m.ID] = mv + } + } else { + + n := msigVote{ + Multisig: m, + } + if v.OptionID == APPROVE { + n.AcceptanceSingers = append(n.AcceptanceSingers, v.SignerAddress) + } else { + n.RejectionSigners = append(n.RejectionSigners, v.SignerAddress) + } + + //check if threshold meet + if uint64(len(mv.AcceptanceSingers)) == m.Threshold { + delete(msigPendingVotes, m.ID) + msigFinalVotes[m.ID] = mv + acceptanceBalance = types.BigAdd(acceptanceBalance, m.Balance) + } else if uint64(len(mv.RejectionSigners)) == m.Threshold { + delete(msigPendingVotes, m.ID) + msigFinalVotes[m.ID] = mv + rejectionBalance = types.BigAdd(rejectionBalance, m.Balance) + } else { + msigPendingVotes[m.ID] = mv + } + } + } + } + } + } + + fmt.Printf("\nTotoal amount of singers: %v\n ", len(votes)) + fmt.Printf("Totoal amount of valid multisig vote: %v\n ", len(msigFinalVotes)) + fmt.Printf("Total balance: %v\n", types.BigAdd(acceptanceBalance, rejectionBalance).String()) + fmt.Printf("Approve: %v\n", acceptanceBalance.String()) + fmt.Printf("Reject: %v\n", rejectionBalance.String()) + av := types.BigDivFloat(acceptanceBalance, types.BigAdd(acceptanceBalance, rejectionBalance)) + rv := types.BigDivFloat(rejectionBalance, types.BigAdd(rejectionBalance, acceptanceBalance)) + if av > 0.05 { + fmt.Printf("Token Holder Group Result: Pass. approve: %.5f, reject: %.5f\n", av, rv) + } else { + fmt.Printf("Token Holder Group Result: Not Pass. approve: %.5f, reject: %.5f\n", av, rv) + } + return nil + }, +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 792d72968..19549c36e 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -74,6 +74,7 @@ func main() { diffCmd, itestdCmd, msigCmd, + fip0036PollResultcmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msig.go b/cmd/lotus-shed/msig.go index 66e4885c3..7d37d3d2d 100644 --- a/cmd/lotus-shed/msig.go +++ b/cmd/lotus-shed/msig.go @@ -25,7 +25,7 @@ import ( type msigBriefInfo struct { ID address.Address - Signer interface{} + Signer []address.Address Balance abi.TokenAmount Threshold uint64 }