Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multisig: lotus-sheed miner-multisig change-worker command. #8281

Merged
merged 3 commits into from
Mar 17, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 300 additions & 0 deletions cmd/lotus-shed/miner-multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/filecoin-project/go-state-types/abi"
miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner"

miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner"

msig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig"

"github.com/filecoin-project/go-address"
Expand All @@ -29,6 +31,9 @@ var minerMultisigsCmd = &cli.Command{
mmApproveWithdrawBalance,
mmProposeChangeOwner,
mmApproveChangeOwner,
mmProposeChangeWorker,
mmConfirmChangeWorker,
mmProposeControlSet,
},
Flags: []cli.Flag{
&cli.StringFlag{
Expand Down Expand Up @@ -368,6 +373,301 @@ var mmApproveChangeOwner = &cli.Command{
},
}

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 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.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 := &miner2.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(miner.Methods.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 != 0 {
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
return err
}

var retval msig5.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 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 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.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(miner.Methods.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 != 0 {
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
return err
}

var retval msig5.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 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 := &miner2.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(miner.Methods.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 != 0 {
fmt.Fprintln(cctx.App.Writer, "Propose worker change tx failed!")
return err
}

var retval msig5.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 {
Expand Down