778 lines
18 KiB
Go
778 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/filecoin-project/go-state-types/network"
|
|
|
|
"github.com/fatih/color"
|
|
"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/lotus/api"
|
|
|
|
miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner"
|
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
lcli "github.com/filecoin-project/lotus/cli"
|
|
"github.com/filecoin-project/lotus/lib/tablewriter"
|
|
)
|
|
|
|
var actorCmd = &cli.Command{
|
|
Name: "actor",
|
|
Usage: "manipulate the miner actor",
|
|
Subcommands: []*cli.Command{
|
|
actorWithdrawCmd,
|
|
actorSetOwnerCmd,
|
|
actorControl,
|
|
actorProposeChangeWorker,
|
|
actorConfirmChangeWorker,
|
|
},
|
|
}
|
|
|
|
var actorWithdrawCmd = &cli.Command{
|
|
Name: "withdraw",
|
|
Usage: "withdraw available balance",
|
|
ArgsUsage: "[amount (FIL)]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "confidence",
|
|
Usage: "number of block confirmations to wait for",
|
|
Value: int(build.MessageConfidence),
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
available, err := nodeAPI.StateMinerAvailableBalance(ctx, maddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
amount := available
|
|
if cctx.Args().Present() {
|
|
f, err := types.ParseFIL(cctx.Args().First())
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing 'amount' argument: %w", err)
|
|
}
|
|
|
|
amount = abi.TokenAmount(f)
|
|
|
|
if amount.GreaterThan(available) {
|
|
return xerrors.Errorf("can't withdraw more funds than available; requested: %s; available: %s", types.FIL(amount), types.FIL(available))
|
|
}
|
|
}
|
|
|
|
params, err := actors.SerializeParams(&miner2.WithdrawBalanceParams{
|
|
AmountRequested: amount, // Default to attempting to withdraw all the extra funds in the miner actor
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{
|
|
To: maddr,
|
|
From: mi.Owner,
|
|
Value: types.NewInt(0),
|
|
Method: miner.Methods.WithdrawBalance,
|
|
Params: params,
|
|
}, &api.MessageSendSpec{MaxFee: abi.TokenAmount(types.MustParseFIL("0.1"))})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Requested rewards withdrawal in message %s\n", smsg.Cid())
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), uint64(cctx.Int("confidence")))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
fmt.Println(cctx.App.Writer, "withdrawal failed!")
|
|
return err
|
|
}
|
|
|
|
nv, err := nodeAPI.StateNetworkVersion(ctx, wait.TipSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if nv >= network.Version14 {
|
|
var withdrawn abi.TokenAmount
|
|
if err := withdrawn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Successfully withdrew %s \n", types.FIL(withdrawn))
|
|
if withdrawn.LessThan(amount) {
|
|
fmt.Printf("Note that this is less than the requested amount of %s \n", types.FIL(amount))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var actorSetOwnerCmd = &cli.Command{
|
|
Name: "set-owner",
|
|
Usage: "Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner)",
|
|
ArgsUsage: "[newOwnerAddress senderAddress]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Println("Pass --really-do-it to actually execute this action")
|
|
return nil
|
|
}
|
|
|
|
if cctx.NArg() != 2 {
|
|
return fmt.Errorf("must pass new owner address and sender address")
|
|
}
|
|
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddrId, err := nodeAPI.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fa, err := address.NewFromString(cctx.Args().Get(1))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fromAddrId, err := nodeAPI.StateLookupID(ctx, fa, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if fromAddrId != mi.Owner && fromAddrId != newAddrId {
|
|
return xerrors.New("from address must either be the old owner or the new owner")
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(&newAddrId)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{
|
|
From: fromAddrId,
|
|
To: maddr,
|
|
Method: miner.Methods.ChangeOwnerAddress,
|
|
Value: big.Zero(),
|
|
Params: sp,
|
|
}, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("mpool push: %w", err)
|
|
}
|
|
|
|
fmt.Println("Message CID:", smsg.Cid())
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
fmt.Println("owner change failed!")
|
|
return err
|
|
}
|
|
|
|
fmt.Println("message succeeded!")
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var actorControl = &cli.Command{
|
|
Name: "control",
|
|
Usage: "Manage control addresses",
|
|
Subcommands: []*cli.Command{
|
|
actorControlList,
|
|
actorControlSet,
|
|
},
|
|
}
|
|
|
|
var actorControlList = &cli.Command{
|
|
Name: "list",
|
|
Usage: "Get currently set control addresses",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "verbose",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "color",
|
|
Usage: "use color in display output",
|
|
DefaultText: "depends on output being a TTY",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if cctx.IsSet("color") {
|
|
color.NoColor = !cctx.Bool("color")
|
|
}
|
|
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tw := tablewriter.New(
|
|
tablewriter.Col("name"),
|
|
tablewriter.Col("ID"),
|
|
tablewriter.Col("key"),
|
|
tablewriter.Col("balance"),
|
|
)
|
|
|
|
printKey := func(name string, a address.Address) {
|
|
b, err := nodeAPI.WalletBalance(ctx, a)
|
|
if err != nil {
|
|
fmt.Printf("%s\t%s: error getting balance: %s\n", name, a, err)
|
|
return
|
|
}
|
|
|
|
k, err := nodeAPI.StateAccountKey(ctx, a, types.EmptyTSK)
|
|
if err != nil {
|
|
fmt.Printf("%s\t%s: error getting account key: %s\n", name, a, err)
|
|
return
|
|
}
|
|
|
|
kstr := k.String()
|
|
if !cctx.Bool("verbose") {
|
|
kstr = kstr[:9] + "..."
|
|
}
|
|
|
|
bstr := types.FIL(b).String()
|
|
switch {
|
|
case b.LessThan(types.FromFil(10)):
|
|
bstr = color.RedString(bstr)
|
|
case b.LessThan(types.FromFil(50)):
|
|
bstr = color.YellowString(bstr)
|
|
default:
|
|
bstr = color.GreenString(bstr)
|
|
}
|
|
|
|
tw.Write(map[string]interface{}{
|
|
"name": name,
|
|
"ID": a,
|
|
"key": kstr,
|
|
"balance": bstr,
|
|
})
|
|
}
|
|
|
|
printKey("owner", mi.Owner)
|
|
printKey("worker", mi.Worker)
|
|
for i, ca := range mi.ControlAddresses {
|
|
printKey(fmt.Sprintf("control-%d", i), ca)
|
|
}
|
|
|
|
return tw.Flush(os.Stdout)
|
|
},
|
|
}
|
|
|
|
var actorControlSet = &cli.Command{
|
|
Name: "set",
|
|
Usage: "Set control address(-es)",
|
|
ArgsUsage: "[...address]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Println("Pass --really-do-it to actually execute this action")
|
|
return nil
|
|
}
|
|
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
del := map[address.Address]struct{}{}
|
|
existing := map[address.Address]struct{}{}
|
|
for _, controlAddress := range mi.ControlAddresses {
|
|
ka, err := nodeAPI.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 := nodeAPI.StateAccountKey(ctx, a, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// make sure the address exists on chain
|
|
_, err = nodeAPI.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 := &miner2.ChangeWorkerAddressParams{
|
|
NewWorker: mi.Worker,
|
|
NewControlAddrs: toSet,
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(cwp)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{
|
|
From: mi.Owner,
|
|
To: maddr,
|
|
Method: miner.Methods.ChangeWorkerAddress,
|
|
|
|
Value: big.Zero(),
|
|
Params: sp,
|
|
}, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("mpool push: %w", err)
|
|
}
|
|
|
|
fmt.Println("Message CID:", smsg.Cid())
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var actorProposeChangeWorker = &cli.Command{
|
|
Name: "propose-change-worker",
|
|
Usage: "Propose a worker address change",
|
|
ArgsUsage: "[address]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass address of new worker address")
|
|
}
|
|
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action")
|
|
return nil
|
|
}
|
|
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := nodeAPI.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, 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 {
|
|
return fmt.Errorf("change to worker address %s already pending", na)
|
|
}
|
|
}
|
|
|
|
cwp := &miner2.ChangeWorkerAddressParams{
|
|
NewWorker: newAddr,
|
|
NewControlAddrs: mi.ControlAddresses,
|
|
}
|
|
|
|
sp, err := actors.SerializeParams(cwp)
|
|
if err != nil {
|
|
return xerrors.Errorf("serializing params: %w", err)
|
|
}
|
|
|
|
smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{
|
|
From: mi.Owner,
|
|
To: maddr,
|
|
Method: miner.Methods.ChangeWorkerAddress,
|
|
Value: big.Zero(),
|
|
Params: sp,
|
|
}, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("mpool push: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Propose Message CID:", smsg.Cid())
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
fmt.Fprintln(cctx.App.Writer, "Propose worker change failed!")
|
|
return err
|
|
}
|
|
|
|
mi, err = nodeAPI.StateMinerInfo(ctx, maddr, wait.TipSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mi.NewWorker != newAddr {
|
|
return fmt.Errorf("Proposed worker address change not reflected on chain: expected '%s', found '%s'", na, mi.NewWorker)
|
|
}
|
|
|
|
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 nil
|
|
},
|
|
}
|
|
|
|
var actorConfirmChangeWorker = &cli.Command{
|
|
Name: "confirm-change-worker",
|
|
Usage: "Confirm a worker address change",
|
|
ArgsUsage: "[address]",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "actor",
|
|
Usage: "specify the address of miner actor",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "really-do-it",
|
|
Usage: "Actually send transaction performing the action",
|
|
Value: false,
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass address of new worker address")
|
|
}
|
|
|
|
if !cctx.Bool("really-do-it") {
|
|
fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action")
|
|
return nil
|
|
}
|
|
|
|
var maddr address.Address
|
|
if act := cctx.String("actor"); act != "" {
|
|
var err error
|
|
maddr, err = address.NewFromString(act)
|
|
if err != nil {
|
|
return fmt.Errorf("parsing address %s: %w", act, err)
|
|
}
|
|
}
|
|
|
|
nodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer acloser()
|
|
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
na, err := address.NewFromString(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
newAddr, err := nodeAPI.StateLookupID(ctx, na, types.EmptyTSK)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if maddr.Empty() {
|
|
minerAPI, closer, err := lcli.GetStorageMinerAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closer()
|
|
|
|
maddr, err = minerAPI.ActorAddress(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mi, err := nodeAPI.StateMinerInfo(ctx, maddr, 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 := nodeAPI.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())
|
|
}
|
|
|
|
smsg, err := nodeAPI.MpoolPushMessage(ctx, &types.Message{
|
|
From: mi.Owner,
|
|
To: maddr,
|
|
Method: miner.Methods.ConfirmUpdateWorkerKey,
|
|
Value: big.Zero(),
|
|
}, nil)
|
|
if err != nil {
|
|
return xerrors.Errorf("mpool push: %w", err)
|
|
}
|
|
|
|
fmt.Fprintln(cctx.App.Writer, "Confirm Message CID:", smsg.Cid())
|
|
|
|
// wait for it to get mined into a block
|
|
wait, err := nodeAPI.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check it executed successfully
|
|
if wait.Receipt.ExitCode != 0 {
|
|
fmt.Fprintln(cctx.App.Writer, "Worker change failed!")
|
|
return err
|
|
}
|
|
|
|
mi, err = nodeAPI.StateMinerInfo(ctx, maddr, wait.TipSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mi.Worker != newAddr {
|
|
return fmt.Errorf("Confirmed worker address change not reflected on chain: expected '%s', found '%s'", newAddr, mi.Worker)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|