lotus/cmd/lotus-miner/actor.go

1591 lines
39 KiB
Go
Raw Normal View History

package main
import (
"bytes"
"fmt"
"os"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/ipfs/go-cid"
2022-06-14 15:00:51 +00:00
cbor "github.com/ipfs/go-ipld-cbor"
2022-08-25 18:20:41 +00:00
"github.com/libp2p/go-libp2p/core/peer"
ma "github.com/multiformats/go-multiaddr"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-bitfield"
2022-06-14 15:00:51 +00:00
rlepluslazy "github.com/filecoin-project/go-bitfield/rle"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
2022-04-20 21:34:28 +00:00
"github.com/filecoin-project/go-state-types/builtin"
minerV12 "github.com/filecoin-project/go-state-types/builtin/v12/miner"
"github.com/filecoin-project/go-state-types/builtin/v9/miner"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/go-state-types/network"
2023-11-14 00:06:11 +00:00
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/actors/adt"
builtin2 "github.com/filecoin-project/lotus/chain/actors/builtin"
2022-06-14 15:00:51 +00:00
lminer "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{
2020-06-23 23:00:32 +00:00
Name: "actor",
Usage: "manipulate the miner actor",
Subcommands: []*cli.Command{
actorSetAddrsCmd,
2020-08-13 08:20:09 +00:00
actorWithdrawCmd,
actorRepayDebtCmd,
2020-08-18 00:53:02 +00:00
actorSetPeeridCmd,
actorSetOwnerCmd,
actorControl,
2020-10-21 03:35:18 +00:00
actorProposeChangeWorker,
actorConfirmChangeWorker,
2021-07-22 14:18:55 +00:00
actorCompactAllocatedCmd,
actorMovePartitionsCmd,
actorProposeChangeBeneficiary,
actorConfirmChangeBeneficiary,
},
}
var actorSetAddrsCmd = &cli.Command{
Name: "set-addresses",
Aliases: []string{"set-addrs"},
Usage: "set addresses that your miner can be publicly dialed on",
ArgsUsage: "<multiaddrs>",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "optionally specify the account to send the message from",
},
&cli.Int64Flag{
Name: "gas-limit",
Usage: "set gas limit",
Value: 0,
},
2021-04-06 03:24:32 +00:00
&cli.BoolFlag{
Name: "unset",
Usage: "unset address",
Value: false,
},
},
Action: func(cctx *cli.Context) error {
2021-04-02 11:24:15 +00:00
args := cctx.Args().Slice()
2021-04-06 03:24:32 +00:00
unset := cctx.Bool("unset")
if len(args) == 0 && !unset {
2021-04-02 11:24:15 +00:00
return cli.ShowSubcommandHelp(cctx)
}
2021-04-06 03:24:32 +00:00
if len(args) > 0 && unset {
return fmt.Errorf("unset can only be used with no arguments")
2021-04-06 03:24:32 +00:00
}
2021-04-02 11:24:15 +00:00
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
var addrs []abi.Multiaddrs
2021-04-02 11:24:15 +00:00
for _, a := range args {
maddr, err := ma.NewMultiaddr(a)
if err != nil {
return fmt.Errorf("failed to parse %q as a multiaddr: %w", a, err)
}
maddrNop2p, strip := ma.SplitFunc(maddr, func(c ma.Component) bool {
return c.Protocol().Code == ma.P_P2P
})
if strip != nil {
fmt.Println("Stripping peerid ", strip, " from ", maddr)
}
addrs = append(addrs, maddrNop2p.Bytes())
}
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
if err != nil {
return err
}
minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
fromAddr := minfo.Worker
if from := cctx.String("from"); from != "" {
addr, err := address.NewFromString(from)
if err != nil {
return err
}
fromAddr = addr
}
fromId, err := api.StateLookupID(ctx, fromAddr, types.EmptyTSK)
if err != nil {
return err
}
if !isController(minfo, fromId) {
return xerrors.Errorf("sender isn't a controller of miner: %s", fromId)
}
2022-04-20 21:34:28 +00:00
params, err := actors.SerializeParams(&miner.ChangeMultiaddrsParams{NewMultiaddrs: addrs})
if err != nil {
return err
}
gasLimit := cctx.Int64("gas-limit")
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
To: maddr,
From: fromId,
Value: types.NewInt(0),
GasLimit: gasLimit,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.ChangeMultiaddrs,
Params: params,
}, nil)
2020-06-23 23:00:32 +00:00
if err != nil {
return err
}
fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid())
return nil
},
}
2020-08-13 08:20:09 +00:00
2020-08-18 00:53:02 +00:00
var actorSetPeeridCmd = &cli.Command{
Name: "set-peer-id",
Usage: "set the peer id of your miner",
ArgsUsage: "<peer id>",
2020-08-18 00:53:02 +00:00
Flags: []cli.Flag{
&cli.Int64Flag{
Name: "gas-limit",
Usage: "set gas limit",
Value: 0,
},
},
Action: func(cctx *cli.Context) error {
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
2020-08-18 00:53:02 +00:00
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
if cctx.NArg() != 1 {
return lcli.IncorrectNumArgs(cctx)
}
pid, err := peer.Decode(cctx.Args().Get(0))
2020-08-18 00:53:02 +00:00
if err != nil {
return fmt.Errorf("failed to parse input as a peerId: %w", err)
}
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
2020-08-18 00:53:02 +00:00
if err != nil {
return err
}
minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
2022-04-20 21:34:28 +00:00
params, err := actors.SerializeParams(&miner.ChangePeerIDParams{NewID: abi.PeerID(pid)})
2020-08-18 00:53:02 +00:00
if err != nil {
return err
}
gasLimit := cctx.Int64("gas-limit")
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
To: maddr,
From: minfo.Worker,
Value: types.NewInt(0),
GasLimit: gasLimit,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.ChangePeerID,
2020-08-18 00:53:02 +00:00
Params: params,
}, nil)
if err != nil {
return err
}
fmt.Printf("Requested peerid change in message %s\n", smsg.Cid())
return nil
},
}
2020-08-13 08:20:09 +00:00
var actorWithdrawCmd = &cli.Command{
2020-08-13 08:20:41 +00:00
Name: "withdraw",
Usage: "withdraw available balance to beneficiary",
2020-08-13 08:20:09 +00:00
ArgsUsage: "[amount (FIL)]",
Flags: []cli.Flag{
&cli.IntFlag{
Name: "confidence",
Usage: "number of block confirmations to wait for",
Value: int(build.MessageConfidence),
},
&cli.BoolFlag{
Name: "beneficiary",
Usage: "send withdraw message from the beneficiary address",
},
},
2020-08-13 08:20:09 +00:00
Action: func(cctx *cli.Context) error {
2022-07-29 14:39:49 +00:00
amount := abi.NewTokenAmount(0)
2020-08-13 08:20:09 +00:00
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)
}
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
2020-08-13 08:20:09 +00:00
if err != nil {
return err
}
2022-07-29 14:39:49 +00:00
defer closer()
2020-08-13 08:20:09 +00:00
2022-07-29 14:39:49 +00:00
api, acloser, err := lcli.GetFullNodeAPI(cctx)
2020-08-13 08:20:09 +00:00
if err != nil {
return err
}
2022-07-29 14:39:49 +00:00
defer acloser()
2020-08-13 08:20:09 +00:00
2022-07-29 14:39:49 +00:00
ctx := lcli.ReqContext(cctx)
var res cid.Cid
if cctx.IsSet("beneficiary") {
2022-09-14 18:51:18 +00:00
res, err = minerApi.BeneficiaryWithdrawBalance(ctx, amount)
} else {
2022-09-14 18:51:18 +00:00
res, err = minerApi.ActorWithdrawBalance(ctx, amount)
}
if err != nil {
return err
}
fmt.Printf("Requested withdrawal in message %s\nwaiting for it to be included in a block..\n", res)
2022-08-01 18:11:24 +00:00
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, res, uint64(cctx.Int("confidence")))
2022-07-29 14:39:49 +00:00
if err != nil {
return xerrors.Errorf("Timeout waiting for withdrawal message %s", res)
2022-08-01 18:11:24 +00:00
}
if wait.Receipt.ExitCode.IsError() {
2022-08-01 18:11:24 +00:00
return xerrors.Errorf("Failed to execute withdrawal message %s: %w", wait.Message, wait.Receipt.ExitCode.Error())
}
2022-08-01 18:11:24 +00:00
nv, err := api.StateNetworkVersion(ctx, wait.TipSet)
if err != nil {
return err
}
if nv >= network.Version14 {
var withdrawn abi.TokenAmount
2022-08-01 18:11:24 +00:00
if err := withdrawn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)); err != nil {
return err
}
2021-10-27 00:51:40 +00:00
fmt.Printf("Successfully withdrew %s \n", types.FIL(withdrawn))
if withdrawn.LessThan(amount) {
2021-10-27 00:51:40 +00:00
fmt.Printf("Note that this is less than the requested amount of %s\n", types.FIL(amount))
}
}
2020-08-13 08:20:09 +00:00
return nil
},
}
var actorRepayDebtCmd = &cli.Command{
Name: "repay-debt",
Usage: "pay down a miner's debt",
ArgsUsage: "[amount (FIL)]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "from",
Usage: "optionally specify the account to send funds from",
},
},
Action: func(cctx *cli.Context) error {
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
if err != nil {
return err
}
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
var amount abi.TokenAmount
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)
} else {
mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(api)))
2022-04-20 21:34:28 +00:00
mst, err := lminer.Load(store, mact)
if err != nil {
return err
}
amount, err = mst.FeeDebt()
if err != nil {
return err
}
}
fromAddr := mi.Worker
if from := cctx.String("from"); from != "" {
addr, err := address.NewFromString(from)
if err != nil {
return err
}
fromAddr = addr
}
fromId, err := api.StateLookupID(ctx, fromAddr, types.EmptyTSK)
if err != nil {
return err
}
2022-04-20 21:34:28 +00:00
if !isController(mi, fromId) {
return xerrors.Errorf("sender isn't a controller of miner: %s", fromId)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
To: maddr,
From: fromId,
Value: amount,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.RepayDebt,
Params: nil,
}, nil)
if err != nil {
return err
}
fmt.Printf("Sent repay debt message %s\n", smsg.Cid())
return nil
},
}
var actorControl = &cli.Command{
2020-08-19 23:26:13 +00:00
Name: "control",
Usage: "Manage control addresses",
Subcommands: []*cli.Command{
actorControlList,
actorControlSet,
},
}
var actorControlList = &cli.Command{
2020-08-19 23:26:13 +00:00
Name: "list",
Usage: "Get currently set control addresses",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
},
},
Action: func(cctx *cli.Context) error {
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPIV1(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
2021-08-11 11:23:37 +00:00
maddr, err := getActorAddress(ctx, cctx)
if err != nil {
return err
}
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
tw := tablewriter.New(
tablewriter.Col("name"),
tablewriter.Col("ID"),
tablewriter.Col("key"),
tablewriter.Col("use"),
tablewriter.Col("balance"),
)
2022-09-14 18:51:18 +00:00
ac, err := minerApi.ActorAddressConfig(ctx)
if err != nil {
2020-12-02 19:46:07 +00:00
return err
}
commit := map[address.Address]struct{}{}
precommit := map[address.Address]struct{}{}
terminate := map[address.Address]struct{}{}
dealPublish := map[address.Address]struct{}{}
2020-12-02 19:46:07 +00:00
post := map[address.Address]struct{}{}
for _, ca := range mi.ControlAddresses {
post[ca] = struct{}{}
}
for _, ca := range ac.PreCommitControl {
ca, err := api.StateLookupID(ctx, ca, types.EmptyTSK)
if err != nil {
return err
}
delete(post, ca)
precommit[ca] = struct{}{}
}
for _, ca := range ac.CommitControl {
ca, err := api.StateLookupID(ctx, ca, types.EmptyTSK)
if err != nil {
return err
}
delete(post, ca)
commit[ca] = struct{}{}
}
for _, ca := range ac.TerminateControl {
ca, err := api.StateLookupID(ctx, ca, types.EmptyTSK)
if err != nil {
return err
}
delete(post, ca)
terminate[ca] = struct{}{}
}
for _, ca := range ac.DealPublishControl {
ca, err := api.StateLookupID(ctx, ca, types.EmptyTSK)
if err != nil {
return err
}
delete(post, ca)
dealPublish[ca] = struct{}{}
}
printKey := func(name string, a address.Address) {
var actor *types.Actor
if actor, err = api.StateGetActor(ctx, a, types.EmptyTSK); err != nil {
fmt.Printf("%s\t%s: error getting actor: %s\n", name, a, err)
return
}
b := actor.Balance
var k = a
// 'a' maybe a 'robust', in that case, 'StateAccountKey' returns an error.
if builtin2.IsAccountActor(actor.Code) {
if k, err = api.StateAccountKey(ctx, a, types.EmptyTSK); err != nil {
fmt.Printf("%s\t%s: error getting account key: %s\n", name, a, err)
return
}
}
kstr := k.String()
if !cctx.Bool("verbose") {
if len(kstr) > 9 {
kstr = kstr[:6] + "..."
}
}
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)
}
var uses []string
if a == mi.Worker {
uses = append(uses, color.YellowString("other"))
}
2020-12-02 19:46:07 +00:00
if _, ok := post[a]; ok {
uses = append(uses, color.GreenString("post"))
}
2020-12-02 19:46:07 +00:00
if _, ok := precommit[a]; ok {
uses = append(uses, color.CyanString("precommit"))
}
if _, ok := commit[a]; ok {
uses = append(uses, color.BlueString("commit"))
}
if _, ok := terminate[a]; ok {
uses = append(uses, color.YellowString("terminate"))
}
if _, ok := dealPublish[a]; ok {
uses = append(uses, color.MagentaString("deals"))
}
tw.Write(map[string]interface{}{
2020-08-19 23:26:13 +00:00
"name": name,
"ID": a,
"key": kstr,
"use": strings.Join(uses, " "),
"balance": bstr,
})
}
printKey("owner", mi.Owner)
printKey("worker", mi.Worker)
2022-11-12 02:58:30 +00:00
printKey("beneficiary", mi.Beneficiary)
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.BoolFlag{
2020-08-19 23:26:13 +00:00
Name: "really-do-it",
Usage: "Actually send transaction performing the action",
Value: false,
},
},
Action: func(cctx *cli.Context) error {
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
if err != nil {
return err
}
mi, err := api.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 := 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)
}
}
if !cctx.Bool("really-do-it") {
fmt.Println("Pass --really-do-it to actually execute this action")
return nil
}
2022-04-20 21:34:28 +00:00
cwp := &miner.ChangeWorkerAddressParams{
NewWorker: mi.Worker,
NewControlAddrs: toSet,
}
sp, err := actors.SerializeParams(cwp)
if err != nil {
return xerrors.Errorf("serializing params: %w", err)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
2020-08-19 23:26:13 +00:00
From: mi.Owner,
To: maddr,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.ChangeWorkerAddress,
2020-08-19 23:26:13 +00:00
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 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.BoolFlag{
Name: "really-do-it",
Usage: "Actually send transaction performing the action",
Value: false,
},
},
Action: func(cctx *cli.Context) error {
if cctx.NArg() != 2 {
return lcli.IncorrectNumArgs(cctx)
}
if !cctx.Bool("really-do-it") {
fmt.Println("Pass --really-do-it to actually execute this action")
return nil
}
api, 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 := api.StateLookupID(ctx, na, types.EmptyTSK)
if err != nil {
return err
}
fa, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return err
}
2021-01-08 06:25:24 +00:00
fromAddrId, err := api.StateLookupID(ctx, fa, types.EmptyTSK)
if err != nil {
return err
}
maddr, err := getActorAddress(ctx, cctx)
if err != nil {
return err
}
mi, err := api.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 := api.MpoolPushMessage(ctx, &types.Message{
From: fromAddrId,
To: maddr,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.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 := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode.IsError() {
fmt.Println("owner change failed!")
return err
}
2021-01-13 08:03:19 +00:00
fmt.Println("message succeeded!")
return nil
},
}
2020-10-21 03:35:18 +00:00
var actorProposeChangeWorker = &cli.Command{
Name: "propose-change-worker",
Usage: "Propose a worker address change",
ArgsUsage: "[address]",
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.Args().Present() {
return fmt.Errorf("must pass address of new worker address")
}
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
2020-10-21 03:35:18 +00:00
if err != nil {
return err
}
defer closer()
api, 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 := api.StateLookupID(ctx, na, types.EmptyTSK)
if err != nil {
return err
}
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
2020-10-21 03:35:18 +00:00
if err != nil {
return err
}
mi, err := api.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)
}
}
if !cctx.Bool("really-do-it") {
fmt.Fprintln(cctx.App.Writer, "Pass --really-do-it to actually execute this action")
return nil
}
2022-04-20 21:34:28 +00:00
cwp := &miner.ChangeWorkerAddressParams{
2020-10-21 03:35:18 +00:00
NewWorker: newAddr,
NewControlAddrs: mi.ControlAddresses,
}
sp, err := actors.SerializeParams(cwp)
if err != nil {
return xerrors.Errorf("serializing params: %w", err)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: mi.Owner,
To: maddr,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.ChangeWorkerAddress,
2020-10-21 03:35:18 +00:00
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 := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode.IsError() {
return fmt.Errorf("propose worker change failed")
2020-10-21 03:35:18 +00:00
}
mi, err = api.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)
}
2022-03-28 07:46:08 +00:00
fmt.Fprintf(cctx.App.Writer, "Worker key change to %s successfully sent, change happens at height %d.\n", na, mi.WorkerChangeEpoch)
fmt.Fprintf(cctx.App.Writer, "If you have no active deadlines, call 'confirm-change-worker' at or after height %d to complete.\n", mi.WorkerChangeEpoch)
2020-10-21 03:35:18 +00:00
return nil
},
}
var actorProposeChangeBeneficiary = &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,
},
&cli.StringFlag{
Name: "actor",
Usage: "specify the address of miner actor",
},
},
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 := strconv.ParseInt(cctx.Args().Get(2), 10, 64)
if err != nil {
return xerrors.Errorf("parsing expiration: %w", err)
}
maddr, err := getActorAddress(ctx, cctx)
if err != nil {
return xerrors.Errorf("getting miner address: %w", err)
}
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return xerrors.Errorf("getting miner info: %w", err)
}
if mi.Beneficiary == mi.Owner && newAddr == mi.Owner {
return fmt.Errorf("beneficiary %s already set to owner address", mi.Beneficiary)
}
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") {
2022-09-14 21:38:01 +00:00
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),
}
sp, err := actors.SerializeParams(params)
if err != nil {
return xerrors.Errorf("serializing params: %w", err)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: mi.Owner,
To: maddr,
Method: builtin.MethodsMiner.ChangeBeneficiary,
Value: big.Zero(),
Params: sp,
}, nil)
if err != nil {
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("Propose Message CID:", smsg.Cid())
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 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")
}
updatedMinerInfo, err := api.StateMinerInfo(ctx, maddr, wait.TipSet)
if err != nil {
return xerrors.Errorf("getting miner info: %w", err)
}
2022-09-14 21:38:01 +00:00
if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == newAddr {
fmt.Println("Beneficiary address successfully changed")
} else {
fmt.Println("Beneficiary address change awaiting additional confirmations")
}
return nil
},
}
2020-10-21 03:35:18 +00:00
var actorConfirmChangeWorker = &cli.Command{
Name: "confirm-change-worker",
Usage: "Confirm a worker address change",
ArgsUsage: "[address]",
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.Args().Present() {
return fmt.Errorf("must pass address of new worker address")
}
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
2020-10-21 03:35:18 +00:00
if err != nil {
return err
}
defer closer()
api, 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 := api.StateLookupID(ctx, na, types.EmptyTSK)
if err != nil {
return err
}
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
2020-10-21 03:35:18 +00:00
if err != nil {
return err
}
mi, err := api.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 := 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())
}
if !cctx.Bool("really-do-it") {
fmt.Println("Pass --really-do-it to actually execute this action")
2020-10-21 03:35:18 +00:00
return nil
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: mi.Owner,
To: maddr,
2022-12-15 00:02:43 +00:00
Method: builtin.MethodsMiner.ConfirmChangeWorkerAddress,
2020-10-21 03:35:18 +00:00
Value: big.Zero(),
}, nil)
if err != nil {
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("Confirm Message CID:", smsg.Cid())
2020-10-21 03:35:18 +00:00
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode.IsError() {
2020-10-21 03:35:18 +00:00
fmt.Fprintln(cctx.App.Writer, "Worker change failed!")
return err
}
mi, err = api.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
},
}
var actorConfirmChangeBeneficiary = &cli.Command{
Name: "confirm-change-beneficiary",
Usage: "Confirm a beneficiary address change",
ArgsUsage: "[minerID]",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "really-do-it",
Usage: "Actually send transaction performing the action",
Value: false,
},
&cli.BoolFlag{
Name: "existing-beneficiary",
Usage: "send confirmation from the existing beneficiary address",
},
&cli.BoolFlag{
Name: "new-beneficiary",
Usage: "send confirmation from the new beneficiary address",
},
},
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)
maddr, err := address.NewFromString(cctx.Args().First())
if err != nil {
return xerrors.Errorf("parsing beneficiary address: %w", err)
}
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return xerrors.Errorf("getting miner info: %w", err)
}
if mi.PendingBeneficiaryTerm == nil {
return fmt.Errorf("no pending beneficiary term found for miner %s", maddr)
}
if (cctx.IsSet("existing-beneficiary") && cctx.IsSet("new-beneficiary")) || (!cctx.IsSet("existing-beneficiary") && !cctx.IsSet("new-beneficiary")) {
2022-09-14 21:38:01 +00:00
return lcli.ShowHelp(cctx, fmt.Errorf("must pass exactly one of --existing-beneficiary or --new-beneficiary"))
}
var fromAddr address.Address
if cctx.IsSet("existing-beneficiary") {
if mi.PendingBeneficiaryTerm.ApprovedByBeneficiary {
return fmt.Errorf("beneficiary change already approved by current beneficiary")
}
fromAddr = mi.Beneficiary
} else {
if mi.PendingBeneficiaryTerm.ApprovedByNominee {
return fmt.Errorf("beneficiary change already approved by new beneficiary")
}
fromAddr = mi.PendingBeneficiaryTerm.NewBeneficiary
}
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") {
2022-09-14 21:38:01 +00:00
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)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: fromAddr,
To: maddr,
Method: builtin.MethodsMiner.ChangeBeneficiary,
Value: big.Zero(),
Params: sp,
}, nil)
if err != nil {
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("Confirm Message CID:", smsg.Cid())
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 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() {
2022-09-14 21:38:01 +00:00
return fmt.Errorf("confirm beneficiary change failed with code %d", wait.Receipt.ExitCode)
}
updatedMinerInfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
2022-09-14 21:38:01 +00:00
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 actorMovePartitionsCmd = &cli.Command{
Name: "move-partitions",
Usage: "move deadline of specified partitions from one to another",
Flags: []cli.Flag{
&cli.Int64SliceFlag{
Name: "partition-indices",
Usage: "Indices of partitions to update, separated by comma",
},
&cli.Uint64Flag{
Name: "orig-deadline",
Usage: "Deadline to move partition from",
},
&cli.Uint64Flag{
Name: "dest-deadline",
Usage: "Deadline to move partition to",
},
&cli.BoolFlag{
Name: "really-do-it",
Usage: "Actually send transaction performing the action",
Value: false,
},
},
Action: func(cctx *cli.Context) error {
2023-09-26 20:56:16 +00:00
if !cctx.Bool("really-do-it") {
fmt.Println("Pass --really-do-it to actually execute this action")
return nil
}
if cctx.Args().Present() {
return fmt.Errorf("please use flags to provide arguments")
}
ctx := lcli.ReqContext(cctx)
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
maddr, err := minerApi.ActorAddress(ctx)
if err != nil {
return err
}
fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr))
fullNodeApi, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
minfo, err := fullNodeApi.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
origDeadline := cctx.Uint64("orig-deadline")
if origDeadline > miner.WPoStPeriodDeadlines {
return fmt.Errorf("orig-deadline %d out of range", origDeadline)
}
destDeadline := cctx.Uint64("dest-deadline")
if destDeadline > miner.WPoStPeriodDeadlines {
return fmt.Errorf("dest-deadline %d out of range", destDeadline)
}
if origDeadline == destDeadline {
return fmt.Errorf("dest-desdline cannot be the same as orig-deadline")
}
partitions := cctx.Int64Slice("partition-indices")
if len(partitions) == 0 {
return fmt.Errorf("must include at least one partition to move")
}
curPartitions, err := fullNodeApi.StateMinerPartitions(ctx, maddr, origDeadline, types.EmptyTSK)
if err != nil {
return fmt.Errorf("getting partitions for deadline %d: %w", origDeadline, err)
}
if len(partitions) > len(curPartitions) {
return fmt.Errorf("partition size(%d) cannot be bigger than current partition size(%d) for deadline %d", len(partitions), len(curPartitions), origDeadline)
}
fmt.Printf("Moving %d paritions\n", len(partitions))
partitionsBf := bitfield.New()
for _, partition := range partitions {
if partition >= int64(len(curPartitions)) {
return fmt.Errorf("partition index(%d) doesn't exist", partition)
}
partitionsBf.Set(uint64(partition))
}
params := minerV12.MovePartitionsParams{
OrigDeadline: origDeadline,
DestDeadline: destDeadline,
Partitions: partitionsBf,
}
serializedParams, err := actors.SerializeParams(&params)
if err != nil {
return fmt.Errorf("serializing params: %w", err)
}
smsg, err := fullNodeApi.MpoolPushMessage(ctx, &types.Message{
From: minfo.Worker,
To: maddr,
Method: builtin.MethodsMiner.MovePartitions,
Value: big.Zero(),
Params: serializedParams,
}, nil)
if err != nil {
return fmt.Errorf("mpool push: %w", err)
}
fmt.Println("MovePartitions Message CID:", smsg.Cid())
// wait for it to get mined into a block
fmt.Println("Waiting for block confirmation...")
wait, err := fullNodeApi.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode.IsError() {
fmt.Println("Moving partitions failed!")
return err
}
fmt.Println("Move partition confirmed")
return nil
},
}
var actorCompactAllocatedCmd = &cli.Command{
Name: "compact-allocated",
Usage: "compact allocated sectors bitfield",
Flags: []cli.Flag{
&cli.Uint64Flag{
Name: "mask-last-offset",
Usage: "Mask sector IDs from 0 to 'highest_allocated - offset'",
},
&cli.Uint64Flag{
Name: "mask-upto-n",
Usage: "Mask sector IDs from 0 to 'n'",
},
&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.Args().Present() {
return fmt.Errorf("must pass address of new owner address")
}
2022-09-14 18:51:18 +00:00
minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
if err != nil {
return err
}
defer closer()
api, acloser, err := lcli.GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer acloser()
ctx := lcli.ReqContext(cctx)
2022-09-14 18:51:18 +00:00
maddr, err := minerApi.ActorAddress(ctx)
if err != nil {
return err
}
mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(api)))
2022-04-20 21:34:28 +00:00
mst, err := lminer.Load(store, mact)
if err != nil {
return err
}
allocs, err := mst.GetAllocatedSectors()
if err != nil {
return err
}
var maskBf bitfield.BitField
{
exclusiveFlags := []string{"mask-last-offset", "mask-upto-n"}
hasFlag := false
for _, f := range exclusiveFlags {
if hasFlag && cctx.IsSet(f) {
return xerrors.Errorf("more than one 'mask` flag set")
}
hasFlag = hasFlag || cctx.IsSet(f)
}
}
switch {
case cctx.IsSet("mask-last-offset"):
last, err := allocs.Last()
if err != nil {
return err
}
m := cctx.Uint64("mask-last-offset")
if last <= m+1 {
2021-07-22 14:18:55 +00:00
return xerrors.Errorf("highest allocated sector lower than mask offset %d: %d", m+1, last)
}
// securty to not brick a miner
if last > 1<<60 {
return xerrors.Errorf("very high last sector number, refusing to mask: %d", last)
}
maskBf, err = bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{
Runs: []rlepluslazy.Run{{Val: true, Len: last - m}}})
if err != nil {
return xerrors.Errorf("forming bitfield: %w", err)
}
case cctx.IsSet("mask-upto-n"):
n := cctx.Uint64("mask-upto-n")
maskBf, err = bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{
Runs: []rlepluslazy.Run{{Val: true, Len: n}}})
if err != nil {
return xerrors.Errorf("forming bitfield: %w", err)
}
default:
return xerrors.Errorf("no 'mask' flags set")
}
mi, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK)
if err != nil {
return err
}
2022-04-20 21:34:28 +00:00
params := &miner.CompactSectorNumbersParams{
MaskSectorNumbers: maskBf,
}
sp, err := actors.SerializeParams(params)
if err != nil {
return xerrors.Errorf("serializing params: %w", err)
}
smsg, err := api.MpoolPushMessage(ctx, &types.Message{
From: mi.Worker,
To: maddr,
2022-04-20 21:34:28 +00:00
Method: builtin.MethodsMiner.CompactSectorNumbers,
Value: big.Zero(),
Params: sp,
}, nil)
if err != nil {
return xerrors.Errorf("mpool push: %w", err)
}
fmt.Println("CompactSectorNumbers Message CID:", smsg.Cid())
// wait for it to get mined into a block
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence)
if err != nil {
return err
}
// check it executed successfully
if wait.Receipt.ExitCode.IsError() {
fmt.Println("Propose owner change failed!")
return err
}
return nil
},
}
2022-04-20 21:34:28 +00:00
func isController(mi api.MinerInfo, addr address.Address) bool {
if addr == mi.Owner || addr == mi.Worker {
return true
}
for _, ca := range mi.ControlAddresses {
if addr == ca {
return true
}
}
return false
}