889 lines
24 KiB
Go
889 lines
24 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
"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"
|
|
"github.com/filecoin-project/go-state-types/builtin"
|
|
"github.com/filecoin-project/go-state-types/builtin/v9/miner"
|
|
"github.com/filecoin-project/go-state-types/builtin/v9/multisig"
|
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
lcli "github.com/filecoin-project/lotus/cli"
|
|
)
|
|
|
|
var minerMultisigsCmd = &cli.Command{
|
|
Name: "miner-multisig",
|
|
Description: "a collection of utilities for using multisigs as owner addresses of miners",
|
|
Subcommands: []*cli.Command{
|
|
mmProposeWithdrawBalance,
|
|
mmApproveWithdrawBalance,
|
|
mmProposeChangeOwner,
|
|
mmApproveChangeOwner,
|
|
mmProposeChangeWorker,
|
|
mmConfirmChangeWorker,
|
|
mmProposeControlSet,
|
|
mmProposeChangeBeneficiary,
|
|
mmConfirmChangeBeneficiary,
|
|
},
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "from",
|
|
Usage: "specify address to send message from",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "multisig",
|
|
Usage: "specify multisig that will receive the message",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "miner",
|
|
Usage: "specify miner being acted upon",
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
var mmProposeWithdrawBalance = &cli.Command{
|
|
Name: "propose-withdraw",
|
|
Usage: "Propose to withdraw FIL from the miner",
|
|
ArgsUsage: "[amount]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass amount to withdraw")
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
val, err := types.ParseFIL(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(&miner.WithdrawBalanceParams{
|
|
AmountRequested: abi.TokenAmount(val),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.WithdrawBalance), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ProposeReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed during propose\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmApproveWithdrawBalance = &cli.Command{
|
|
Name: "approve-withdraw",
|
|
Usage: "Approve to withdraw FIL from the miner",
|
|
ArgsUsage: "[amount txnId proposer]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 3 {
|
|
return lcli.IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
val, err := types.ParseFIL(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(&miner.WithdrawBalanceParams{
|
|
AmountRequested: abi.TokenAmount(val),
|
|
})
|
|
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
|
|
}
|
|
|
|
acid, err := api.MsigApproveTxnHash(ctx, multisigAddr, txid, proposer, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.WithdrawBalance), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("approving message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, acid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ApproveReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal approve return value: %w", err)
|
|
}
|
|
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed with the approve\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
} else {
|
|
fmt.Println("Transaction was approved, but not executed")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmProposeChangeOwner = &cli.Command{
|
|
Name: "propose-change-owner",
|
|
Usage: "Propose an owner address change",
|
|
ArgsUsage: "[newOwner]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass new owner address")
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.Owner == newAddr {
|
|
return fmt.Errorf("owner address already set to %s", na)
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(&newAddr)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeOwnerAddress), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose owner change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ProposeReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed during propose\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmApproveChangeOwner = &cli.Command{
|
|
Name: "approve-change-owner",
|
|
Usage: "Approve an owner address change",
|
|
ArgsUsage: "[newOwner txnId proposer]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 3 {
|
|
return lcli.IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
|
|
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
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.Owner == newAddr {
|
|
return fmt.Errorf("owner address already set to %s", na)
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(&newAddr)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
acid, err := api.MsigApproveTxnHash(ctx, multisigAddr, txid, proposer, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeOwnerAddress), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("approving message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Approve Message CID:", acid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, acid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Approve owner change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ApproveReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal approve return value: %w", err)
|
|
}
|
|
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed with the approve\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
} else {
|
|
fmt.Println("Transaction was approved, but not executed")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmProposeChangeWorker = &cli.Command{
|
|
Name: "propose-change-worker",
|
|
Usage: "Propose an worker address change",
|
|
ArgsUsage: "[newWorker]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass new worker address")
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.NewWorker.Empty() {
|
|
if mi.Worker == newAddr {
|
|
return fmt.Errorf("worker address already set to %s", na)
|
|
}
|
|
} else {
|
|
if mi.NewWorker == newAddr {
|
|
fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully proposed.\n", na)
|
|
fmt.Fprintf(cctx.App.Writer, "Call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch)
|
|
return fmt.Errorf("change to worker address %s already pending", na)
|
|
}
|
|
}
|
|
|
|
cwp := &miner.ChangeWorkerAddressParams{
|
|
NewWorker: newAddr,
|
|
NewControlAddrs: mi.ControlAddresses,
|
|
}
|
|
|
|
fmt.Fprintf(cctx.App.Writer, "newAddr: %s\n", newAddr)
|
|
fmt.Fprintf(cctx.App.Writer, "NewControlAddrs: %s\n", mi.ControlAddresses)
|
|
|
|
sp, err := actors.SerializeParams(cwp)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeWorkerAddress), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ProposeReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed during propose\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmProposeChangeBeneficiary = &cli.Command{
|
|
Name: "propose-change-beneficiary",
|
|
Usage: "Propose a beneficiary address change",
|
|
ArgsUsage: "[beneficiaryAddress quota expiration]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "overwrite-pending-change",
|
|
Usage: "Overwrite the current beneficiary change proposal",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 3 {
|
|
return lcli.IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting fullnode api: %w", err)
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
na, err := address.NewFromString(cctx.Args().Get(0))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing beneficiary address: %w", err)
|
|
}
|
|
|
|
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return xerrors.Errorf("looking up new beneficiary address: %w", err)
|
|
}
|
|
|
|
quota, err := types.ParseFIL(cctx.Args().Get(1))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing quota: %w", err)
|
|
}
|
|
|
|
expiration, err := types.BigFromString(cctx.Args().Get(2))
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing expiration: %w", err)
|
|
}
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.PendingBeneficiaryTerm != nil {
|
|
fmt.Println("WARNING: replacing Pending Beneficiary Term of:")
|
|
fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary)
|
|
fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota)
|
|
fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration)
|
|
|
|
if !cctx.Bool("overwrite-pending-change") {
|
|
return fmt.Errorf("must pass --overwrite-pending-change to replace current pending beneficiary change. Please review CAREFULLY")
|
|
}
|
|
}
|
|
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please")
|
|
return nil
|
|
}
|
|
|
|
params := &miner.ChangeBeneficiaryParams{
|
|
NewBeneficiary: newAddr,
|
|
NewQuota: abi.TokenAmount(quota),
|
|
NewExpiration: abi.ChainEpoch(expiration.Int64()),
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(params)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeBeneficiary), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Println("Propose Message CID: ", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return xerrors.Errorf("waiting for message to be included in block: %w", err)
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
return fmt.Errorf("propose beneficiary change failed")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmConfirmChangeWorker = &cli.Command{
|
|
Name: "confirm-change-worker",
|
|
Usage: "Confirm an worker address change",
|
|
ArgsUsage: "[newWorker]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass new worker address")
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := api.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.NewWorker.Empty() {
|
|
return xerrors.Errorf("no worker key change proposed")
|
|
} else if mi.NewWorker != newAddr {
|
|
return xerrors.Errorf("worker key %s does not match current worker key proposal %s", newAddr, mi.NewWorker)
|
|
}
|
|
|
|
if head, err := api.ChainHead(ctx); err != nil {
|
|
return xerrors.Errorf("failed to get the chain head: %w", err)
|
|
} else if head.Height() < mi.WorkerChangeEpoch {
|
|
return xerrors.Errorf("worker key change cannot be confirmed until %d, current height is %d", mi.WorkerChangeEpoch, head.Height())
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ConfirmUpdateWorkerKey), nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ProposeReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed during propose\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmConfirmChangeBeneficiary = &cli.Command{
|
|
Name: "confirm-change-beneficiary",
|
|
Usage: "Confirm a beneficiary address change",
|
|
ArgsUsage: "[minerAddress]",
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.NArg() != 1 {
|
|
return lcli.IncorrectNumArgs(cctx)
|
|
}
|
|
|
|
api, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return xerrors.Errorf("getting fullnode api: %w", err)
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mi.PendingBeneficiaryTerm == nil {
|
|
return fmt.Errorf("no pending beneficiary term found for miner %s", minerAddr)
|
|
}
|
|
|
|
fmt.Println("Confirming Pending Beneficiary Term of:")
|
|
fmt.Println("Beneficiary: ", mi.PendingBeneficiaryTerm.NewBeneficiary)
|
|
fmt.Println("Quota:", mi.PendingBeneficiaryTerm.NewQuota)
|
|
fmt.Println("Expiration Epoch:", mi.PendingBeneficiaryTerm.NewExpiration)
|
|
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Println("Pass --really-do-it to actually execute this action. Review what you're about to approve CAREFULLY please")
|
|
return nil
|
|
}
|
|
|
|
params := &miner.ChangeBeneficiaryParams{
|
|
NewBeneficiary: mi.PendingBeneficiaryTerm.NewBeneficiary,
|
|
NewQuota: mi.PendingBeneficiaryTerm.NewQuota,
|
|
NewExpiration: mi.PendingBeneficiaryTerm.NewExpiration,
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(params)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeBeneficiary), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Println("Confirm Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return xerrors.Errorf("waiting for message to be included in block: %w", err)
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
return fmt.Errorf("confirm beneficiary change failed with code %d", wait.Receipt.ExitCode)
|
|
}
|
|
|
|
updatedMinerInfo, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == mi.PendingBeneficiaryTerm.NewBeneficiary {
|
|
fmt.Println("Beneficiary address successfully changed")
|
|
} else {
|
|
fmt.Println("Beneficiary address change awaiting additional confirmations")
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var mmProposeControlSet = &cli.Command{
|
|
Name: "propose-control-set",
|
|
Usage: "Set control address(-es)",
|
|
ArgsUsage: "[...address]",
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass new owner address")
|
|
}
|
|
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
multisigAddr, sender, minerAddr, err := getInputs(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mi, err := api.StateMinerInfo(ctx, minerAddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
del := map[address.Address]struct{}{}
|
|
existing := map[address.Address]struct{}{}
|
|
for _, controlAddress := range mi.ControlAddresses {
|
|
ka, err := api.StateAccountKey(ctx, controlAddress, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
del[ka] = struct{}{}
|
|
existing[ka] = struct{}{}
|
|
}
|
|
|
|
var toSet []address.Address
|
|
|
|
for i, as := range cctx.Args().Slice() {
|
|
a, err := address.NewFromString(as)
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing address %d: %w", i, err)
|
|
}
|
|
|
|
ka, err := api.StateAccountKey(ctx, a, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// make sure the address exists on chain
|
|
_, err = api.StateLookupID(ctx, ka, types.EmptyTSK)
|
|
if err != nil {
|
|
return xerrors.Errorf("looking up %s: %w", ka, err)
|
|
}
|
|
|
|
delete(del, ka)
|
|
toSet = append(toSet, ka)
|
|
}
|
|
|
|
for a := range del {
|
|
fmt.Println("Remove", a)
|
|
}
|
|
for _, a := range toSet {
|
|
if _, exists := existing[a]; !exists {
|
|
fmt.Println("Add", a)
|
|
}
|
|
}
|
|
|
|
cwp := &miner.ChangeWorkerAddressParams{
|
|
NewWorker: mi.Worker,
|
|
NewControlAddrs: toSet,
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(cwp)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
pcid, err := api.MsigPropose(ctx, multisigAddr, minerAddr, big.Zero(), sender, uint64(builtin.MethodsMiner.ChangeWorkerAddress), sp)
|
|
if err != nil {
|
|
return xerrors.Errorf("proposing message: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", pcid)
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := api.StateWaitMsg(ctx, pcid, build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode.IsError() {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
|
|
return err
|
|
}
|
|
|
|
var retval multisig.ProposeReturn
|
|
if err := retval.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return fmt.Errorf("failed to unmarshal propose return value: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Transaction ID: %d\n", retval.TxnID)
|
|
if retval.Applied {
|
|
fmt.Printf("Transaction was executed during propose\n")
|
|
fmt.Printf("Exit Code: %d\n", retval.Code)
|
|
fmt.Printf("Return Value: %x\n", retval.Ret)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func getInputs(cctx *cli.Context) (address.Address, address.Address, address.Address, error) {
|
|
multisigAddr, err := address.NewFromString(cctx.String("multisig"))
|
|
if err != nil {
|
|
return address.Undef, address.Undef, address.Undef, err
|
|
}
|
|
|
|
sender, err := address.NewFromString(cctx.String("from"))
|
|
if err != nil {
|
|
return address.Undef, address.Undef, address.Undef, err
|
|
}
|
|
|
|
minerAddr, err := address.NewFromString(cctx.String("miner"))
|
|
if err != nil {
|
|
return address.Undef, address.Undef, address.Undef, err
|
|
}
|
|
|
|
return multisigAddr, sender, minerAddr, nil
|
|
}
|