lotus/cli/multisig.go

551 lines
12 KiB
Go
Raw Normal View History

2020-01-21 19:02:51 +00:00
package cli
import (
"bytes"
2020-02-04 03:37:55 +00:00
"context"
"encoding/binary"
2020-01-21 19:02:51 +00:00
"encoding/hex"
"fmt"
"github.com/minio/blake2b-simd"
2020-01-21 19:02:51 +00:00
"os"
2020-02-04 03:37:55 +00:00
"sort"
2020-01-21 19:02:51 +00:00
"strconv"
"text/tabwriter"
"github.com/filecoin-project/go-address"
2020-02-04 03:37:55 +00:00
"github.com/filecoin-project/specs-actors/actors/abi"
2020-02-06 19:49:21 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin"
2020-02-13 03:50:37 +00:00
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
2020-02-06 19:49:21 +00:00
samsig "github.com/filecoin-project/specs-actors/actors/builtin/multisig"
2020-02-04 03:37:55 +00:00
cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-hamt-ipld"
2020-02-04 22:19:05 +00:00
cbor "github.com/ipfs/go-ipld-cbor"
2020-01-21 19:02:51 +00:00
cbg "github.com/whyrusleeping/cbor-gen"
2020-02-04 03:37:55 +00:00
"golang.org/x/xerrors"
2020-01-21 19:02:51 +00:00
"gopkg.in/urfave/cli.v2"
2020-02-06 19:49:21 +00:00
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/api/apibstore"
actors "github.com/filecoin-project/lotus/chain/actors"
types "github.com/filecoin-project/lotus/chain/types"
2020-01-21 19:02:51 +00:00
)
var multisigCmd = &cli.Command{
Name: "msig",
Usage: "Interact with a multisig wallet",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "specify the account to send propose from",
},
},
Subcommands: []*cli.Command{
msigCreateCmd,
msigInspectCmd,
msigProposeCmd,
msigApproveCmd,
},
}
var msigCreateCmd = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "create",
Usage: "Create a new multisig wallet",
ArgsUsage: "[address1 address2 ...]",
2020-01-21 19:02:51 +00:00
Flags: []cli.Flag{
2020-02-04 03:37:55 +00:00
&cli.Int64Flag{
2020-01-21 19:02:51 +00:00
Name: "required",
},
&cli.StringFlag{
Name: "value",
Usage: "initial funds to give to multisig",
Value: "0",
},
&cli.StringFlag{
Name: "sender",
Usage: "account to send the create message from",
},
2020-01-21 19:02:51 +00:00
},
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
var addrs []address.Address
for _, a := range cctx.Args().Slice() {
addr, err := address.NewFromString(a)
if err != nil {
return err
}
addrs = append(addrs, addr)
}
// get the address we're going to use to create the multisig (can be one of the above, as long as they have funds)
var sendAddr address.Address
if send := cctx.String("sender"); send == "" {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
sendAddr = defaddr
} else {
addr, err := address.NewFromString(send)
if err != nil {
return err
}
sendAddr = addr
2020-01-21 19:02:51 +00:00
}
val := cctx.String("value")
filval, err := types.ParseFIL(val)
if err != nil {
return err
}
2020-02-04 03:37:55 +00:00
required := cctx.Int64("required")
2020-01-21 19:02:51 +00:00
if required == 0 {
2020-02-04 03:37:55 +00:00
required = int64(len(addrs))
2020-01-21 19:02:51 +00:00
}
// Set up constructor parameters for multisig
2020-02-06 19:49:21 +00:00
msigParams := &samsig.ConstructorParams{
2020-02-04 03:37:55 +00:00
Signers: addrs,
NumApprovalsThreshold: required,
2020-01-21 19:02:51 +00:00
}
enc, err := actors.SerializeParams(msigParams)
if err != nil {
return err
}
// new actors are created by invoking 'exec' on the init actor with the constructor params
2020-02-13 03:50:37 +00:00
execParams := &init_.ExecParams{
2020-02-25 20:54:58 +00:00
CodeCID: builtin.MultisigActorCodeID,
2020-02-13 03:50:37 +00:00
ConstructorParams: enc,
2020-01-21 19:02:51 +00:00
}
enc, err = actors.SerializeParams(execParams)
if err != nil {
return err
}
// now we create the message to send this with
msg := types.Message{
2020-02-25 20:54:58 +00:00
To: builtin.InitActorAddr,
2020-01-21 19:02:51 +00:00
From: sendAddr,
2020-02-13 03:50:37 +00:00
Method: builtin.MethodsInit.Exec,
2020-01-21 19:02:51 +00:00
Params: enc,
GasPrice: types.NewInt(1),
2020-03-18 20:45:37 +00:00
GasLimit: 1000000,
2020-01-21 19:02:51 +00:00
Value: types.BigInt(filval),
}
// send the message out to the network
smsg, err := api.MpoolPushMessage(ctx, &msg)
if err != nil {
return err
}
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode != 0 {
fmt.Println("actor creation failed!")
return err
}
// get address of newly created miner
var execreturn init_.ExecReturn
if err := execreturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
2020-01-21 19:02:51 +00:00
return err
}
fmt.Println("Created new multisig: ", execreturn.IDAddress, execreturn.RobustAddress)
2020-04-17 16:27:11 +00:00
// TODO: maybe register this somewhere
2020-01-21 19:02:51 +00:00
return nil
},
}
var msigInspectCmd = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "inspect",
Usage: "Inspect a multisig wallet",
ArgsUsage: "[address]",
2020-03-06 19:01:28 +00:00
Flags: []cli.Flag{},
2020-01-21 19:02:51 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if !cctx.Args().Present() {
return fmt.Errorf("must specify address of multisig to inspect")
}
maddr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return err
}
act, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
2020-01-21 19:02:51 +00:00
if err != nil {
return err
}
obj, err := api.ChainReadObj(ctx, act.Head)
if err != nil {
return err
}
2020-02-08 02:18:32 +00:00
var mstate samsig.State
2020-01-21 19:02:51 +00:00
if err := mstate.UnmarshalCBOR(bytes.NewReader(obj)); err != nil {
return err
}
fmt.Printf("Balance: %sfil\n", types.FIL(act.Balance))
2020-02-04 03:37:55 +00:00
fmt.Printf("Threshold: %d / %d\n", mstate.NumApprovalsThreshold, len(mstate.Signers))
2020-01-21 19:02:51 +00:00
fmt.Println("Signers:")
for _, s := range mstate.Signers {
fmt.Printf("\t%s\n", s)
}
2020-02-04 03:37:55 +00:00
pending, err := GetMultisigPending(ctx, api, mstate.PendingTxns)
if err != nil {
return fmt.Errorf("reading pending transactions: %w", err)
}
fmt.Println("Transactions: ", len(pending))
if len(pending) > 0 {
var txids []int64
for txid := range pending {
txids = append(txids, txid)
}
sort.Slice(txids, func(i, j int) bool {
return txids[i] < txids[j]
})
2020-01-21 19:02:51 +00:00
w := tabwriter.NewWriter(os.Stdout, 8, 4, 0, ' ', 0)
fmt.Fprintf(w, "ID\tState\tTo\tValue\tMethod\tParams\n")
2020-02-04 03:37:55 +00:00
for _, txid := range txids {
tx := pending[txid]
fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%x\n", txid, state(tx), tx.To, types.FIL(tx.Value), tx.Method, tx.Params)
2020-01-21 19:02:51 +00:00
}
w.Flush()
}
return nil
},
}
2020-02-08 02:18:32 +00:00
func GetMultisigPending(ctx context.Context, lapi api.FullNode, hroot cid.Cid) (map[int64]*samsig.Transaction, error) {
2020-02-04 03:37:55 +00:00
bs := apibstore.NewAPIBlockstore(lapi)
2020-02-04 22:19:05 +00:00
cst := cbor.NewCborStore(bs)
2020-02-04 03:37:55 +00:00
2020-02-14 14:14:39 +00:00
nd, err := hamt.LoadNode(ctx, cst, hroot, hamt.UseTreeBitWidth(5))
2020-02-04 03:37:55 +00:00
if err != nil {
return nil, err
}
2020-02-08 02:18:32 +00:00
txs := make(map[int64]*samsig.Transaction)
2020-02-04 03:37:55 +00:00
err = nd.ForEach(ctx, func(k string, val interface{}) error {
d := val.(*cbg.Deferred)
2020-02-08 02:18:32 +00:00
var tx samsig.Transaction
2020-02-04 03:37:55 +00:00
if err := tx.UnmarshalCBOR(bytes.NewReader(d.Raw)); err != nil {
return err
}
txid, _ := binary.Varint([]byte(k))
txs[txid] = &tx
return nil
})
if err != nil {
return nil, xerrors.Errorf("failed to iterate transactions hamt: %w", err)
}
return txs, nil
}
2020-02-08 02:18:32 +00:00
func state(tx *samsig.Transaction) string {
2020-02-04 03:37:55 +00:00
/* // TODO(why): I strongly disagree with not having these... but i need to move forward
2020-01-21 19:02:51 +00:00
if tx.Complete {
return "done"
}
if tx.Canceled {
return "canceled"
}
2020-02-04 03:37:55 +00:00
*/
2020-01-21 19:02:51 +00:00
return "pending"
}
var msigProposeCmd = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "propose",
Usage: "Propose a multisig transaction",
ArgsUsage: "[multisigAddress destinationAddress value <methodId methodParams> (optional)]",
2020-04-24 22:00:31 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "account to send the propose message from",
},
},
2020-01-21 19:02:51 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() < 3 {
return fmt.Errorf("must pass multisig address, destination, and value")
}
if cctx.Args().Len() > 3 && cctx.Args().Len() != 5 {
return fmt.Errorf("usage: msig propose <msig addr> <desination> <value> [ <method> <params> ]")
}
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
dest, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return err
}
value, err := types.ParseFIL(cctx.Args().Get(2))
if err != nil {
return err
}
var method uint64
var params []byte
if cctx.Args().Len() == 5 {
m, err := strconv.ParseUint(cctx.Args().Get(3), 10, 64)
if err != nil {
return err
}
method = m
p, err := hex.DecodeString(cctx.Args().Get(4))
if err != nil {
return err
}
params = p
}
2020-02-06 19:49:21 +00:00
enc, err := actors.SerializeParams(&samsig.ProposeParams{
2020-01-21 19:02:51 +00:00
To: dest,
2020-04-24 22:00:31 +00:00
Value: types.BigInt(value),
2020-02-04 03:37:55 +00:00
Method: abi.MethodNum(method),
2020-01-21 19:02:51 +00:00
Params: params,
})
if err != nil {
return err
}
var from address.Address
if cctx.IsSet("source") {
f, err := address.NewFromString(cctx.String("source"))
if err != nil {
return err
}
from = f
} else {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
from = defaddr
}
msg := &types.Message{
To: msig,
From: from,
Value: types.NewInt(0),
2020-02-13 03:50:37 +00:00
Method: builtin.MethodsMultisig.Propose,
2020-01-21 19:02:51 +00:00
Params: enc,
2020-03-18 20:45:37 +00:00
GasLimit: 100000,
2020-01-21 19:02:51 +00:00
GasPrice: types.NewInt(1),
}
smsg, err := api.MpoolPushMessage(ctx, msg)
if err != nil {
return err
}
fmt.Println("send proposal in message: ", smsg.Cid())
wait, err := api.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return err
}
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("proposal returned exit %d", wait.Receipt.ExitCode)
}
_, v, err := cbg.CborReadHeader(bytes.NewReader(wait.Receipt.Return))
if err != nil {
return err
}
fmt.Printf("Transaction ID: %d\n", v)
return nil
},
}
var msigApproveCmd = &cli.Command{
2020-03-06 19:01:28 +00:00
Name: "approve",
Usage: "Approve a multisig message",
ArgsUsage: "[multisigAddress messageId proposerAddress destination value <methodId methodParams> (optional)]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Usage: "account to send the approve message from",
},
},
2020-01-21 19:02:51 +00:00
Action: func(cctx *cli.Context) error {
api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()
ctx := ReqContext(cctx)
if cctx.Args().Len() < 5 {
return fmt.Errorf("must pass multisig address, message ID, proposer address, destination, and value")
}
if cctx.Args().Len() > 5 && cctx.Args().Len() != 7 {
return fmt.Errorf("usage: msig approve <msig addr> <message ID> <proposer address> <desination> <value> [ <method> <params> ]")
2020-01-21 19:02:51 +00:00
}
msig, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return err
}
txid, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64)
if err != nil {
return err
}
proposer, err := address.NewFromString(cctx.Args().Get(2))
if err != nil {
return err
}
if proposer.Protocol() != address.ID {
proposer, err = api.StateLookupID(ctx, proposer, types.EmptyTSK)
if err != nil {
return err
}
}
dest, err := address.NewFromString(cctx.Args().Get(3))
if err != nil {
return err
}
value, err := types.ParseFIL(cctx.Args().Get(4))
if err != nil {
return err
}
var method uint64
var params []byte
if cctx.Args().Len() == 7 {
m, err := strconv.ParseUint(cctx.Args().Get(5), 10, 64)
if err != nil {
return err
}
method = m
p, err := hex.DecodeString(cctx.Args().Get(6))
if err != nil {
return err
}
params = p
}
p := samsig.ProposalHashData{
Requester: proposer,
To: dest,
Value: types.BigInt(value),
Method: abi.MethodNum(method),
Params: params,
}
pser, err := p.Serialize()
if err != nil {
return err
}
phash := blake2b.Sum256(pser)
2020-02-06 19:49:21 +00:00
enc, err := actors.SerializeParams(&samsig.TxnIDParams{
ID: samsig.TxnID(txid),
ProposalHash: phash[:],
2020-01-21 19:02:51 +00:00
})
2020-01-21 19:02:51 +00:00
if err != nil {
return err
}
var from address.Address
if cctx.IsSet("source") {
f, err := address.NewFromString(cctx.String("source"))
if err != nil {
return err
}
from = f
} else {
defaddr, err := api.WalletDefaultAddress(ctx)
if err != nil {
return err
}
from = defaddr
}
msg := &types.Message{
To: msig,
From: from,
Value: types.NewInt(0),
2020-02-13 03:50:37 +00:00
Method: builtin.MethodsMultisig.Approve,
2020-01-21 19:02:51 +00:00
Params: enc,
2020-03-18 20:45:37 +00:00
GasLimit: 100000,
2020-01-21 19:02:51 +00:00
GasPrice: types.NewInt(1),
}
smsg, err := api.MpoolPushMessage(ctx, msg)
if err != nil {
return err
}
fmt.Println("sent approval in message: ", smsg.Cid())
wait, err := api.StateWaitMsg(ctx, smsg.Cid())
if err != nil {
return err
}
if wait.Receipt.ExitCode != 0 {
return fmt.Errorf("approve returned exit %d", wait.Receipt.ExitCode)
}
return nil
},
}