diff --git a/.gitignore b/.gitignore index ea354ba73..d620e557b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /lotus-shed /lotus-sim /curio +/sptool /lotus-townhall /lotus-fountain /lotus-stats @@ -36,7 +37,6 @@ build/paramfetch.sh /darwin /linux *.snap -curio devgen.car localnet.json diff --git a/Makefile b/Makefile index 6a1c9cc8a..83e3fd89d 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ CLEAN+=build/.update-modules deps: $(BUILD_DEPS) .PHONY: deps -build-devnets: build lotus-seed lotus-shed curio +build-devnets: build lotus-seed lotus-shed curio sptool .PHONY: build-devnets debug: GOFLAGS+=-tags=debug @@ -106,6 +106,12 @@ BINS+=curio cu2k: GOFLAGS+=-tags=2k cu2k: curio +sptool: $(BUILD_DEPS) + rm -f sptool + $(GOCC) build $(GOFLAGS) -o sptool ./cmd/sptool +.PHONY: sptool +BINS+=sptool + lotus-worker: $(BUILD_DEPS) rm -f lotus-worker $(GOCC) build $(GOFLAGS) -o lotus-worker ./cmd/lotus-worker @@ -124,13 +130,13 @@ lotus-gateway: $(BUILD_DEPS) .PHONY: lotus-gateway BINS+=lotus-gateway -build: lotus lotus-miner lotus-worker curio +build: lotus lotus-miner lotus-worker curio sptool @[[ $$(type -P "lotus") ]] && echo "Caution: you have \ an existing lotus binary in your PATH. This may cause problems if you don't run 'sudo make install'" || true .PHONY: build -install: install-daemon install-miner install-worker install-curio +install: install-daemon install-miner install-worker install-curio install-sptool install-daemon: install -C ./lotus /usr/local/bin/lotus @@ -141,6 +147,9 @@ install-miner: install-curio: install -C ./curio /usr/local/bin/curio +install-sptool: + install -C ./sptool /usr/local/bin/sptool + install-worker: install -C ./lotus-worker /usr/local/bin/lotus-worker @@ -159,6 +168,9 @@ uninstall-miner: uninstall-curio: rm -f /usr/local/bin/curio +uninstall-sptool: + rm -f /usr/local/bin/sptool + uninstall-worker: rm -f /usr/local/bin/lotus-worker @@ -260,7 +272,7 @@ install-miner-service: install-miner install-daemon-service @echo "To start the service, run: 'sudo systemctl start lotus-miner'" @echo "To enable the service on startup, run: 'sudo systemctl enable lotus-miner'" -install-curio-service: install-curio install-daemon-service +install-curio-service: install-curio install-sptool install-daemon-service mkdir -p /etc/systemd/system mkdir -p /var/log/lotus install -C -m 0644 ./scripts/curio.service /etc/systemd/system/curio.service @@ -401,12 +413,12 @@ gen: actors-code-gen type-gen cfgdoc-gen docsgen api-gen circleci jen: gen -snap: lotus lotus-miner lotus-worker curio +snap: lotus lotus-miner lotus-worker curio sptool snapcraft # snapcraft upload ./lotus_*.snap # separate from gen because it needs binaries -docsgen-cli: lotus lotus-miner lotus-worker curio +docsgen-cli: lotus lotus-miner lotus-worker curio sptool python3 ./scripts/generate-lotus-cli.py ./lotus config default > documentation/en/default-lotus-config.toml ./lotus-miner config default > documentation/en/default-lotus-miner-config.toml diff --git a/cli/clicommands/cmd.go b/cli/clicommands/cmd.go new file mode 100644 index 000000000..a37ce329a --- /dev/null +++ b/cli/clicommands/cmd.go @@ -0,0 +1,30 @@ +package clicommands + +import ( + "github.com/urfave/cli/v2" + + lcli "github.com/filecoin-project/lotus/cli" +) + +var Commands = []*cli.Command{ + lcli.WithCategory("basic", lcli.SendCmd), + lcli.WithCategory("basic", lcli.WalletCmd), + lcli.WithCategory("basic", lcli.InfoCmd), + lcli.WithCategory("basic", lcli.ClientCmd), + lcli.WithCategory("basic", lcli.MultisigCmd), + lcli.WithCategory("basic", lcli.FilplusCmd), + lcli.WithCategory("basic", lcli.PaychCmd), + lcli.WithCategory("developer", lcli.AuthCmd), + lcli.WithCategory("developer", lcli.MpoolCmd), + lcli.WithCategory("developer", StateCmd), + lcli.WithCategory("developer", lcli.ChainCmd), + lcli.WithCategory("developer", lcli.LogCmd), + lcli.WithCategory("developer", lcli.WaitApiCmd), + lcli.WithCategory("developer", lcli.FetchParamCmd), + lcli.WithCategory("developer", lcli.EvmCmd), + lcli.WithCategory("network", lcli.NetCmd), + lcli.WithCategory("network", lcli.SyncCmd), + lcli.WithCategory("status", lcli.StatusCmd), + lcli.PprofCmd, + lcli.VersionCmd, +} diff --git a/cli/clicommands/state.go b/cli/clicommands/state.go new file mode 100644 index 000000000..e990cceb0 --- /dev/null +++ b/cli/clicommands/state.go @@ -0,0 +1,70 @@ +// Package clicommands contains only the cli.Command definitions that are +// common to sptool and miner. These are here because they can't be referenced +// in cli/spcli or cli/ because of the import cycle with all the other cli functions. +package clicommands + +import ( + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-address" + + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" +) + +var StateCmd = &cli.Command{ + Name: "state", + Usage: "Interact with and query filecoin chain state", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset to call method on (pass comma separated array of cids)", + }, + }, + Subcommands: []*cli.Command{ + lcli.StatePowerCmd, + lcli.StateSectorsCmd, + lcli.StateActiveSectorsCmd, + lcli.StateListActorsCmd, + lcli.StateListMinersCmd, + lcli.StateCircSupplyCmd, + lcli.StateSectorCmd, + lcli.StateGetActorCmd, + lcli.StateLookupIDCmd, + lcli.StateReplayCmd, + lcli.StateSectorSizeCmd, + lcli.StateReadStateCmd, + lcli.StateListMessagesCmd, + lcli.StateComputeStateCmd, + lcli.StateCallCmd, + lcli.StateGetDealSetCmd, + lcli.StateWaitMsgCmd, + lcli.StateSearchMsgCmd, + StateMinerInfo, + lcli.StateMarketCmd, + lcli.StateExecTraceCmd, + lcli.StateNtwkVersionCmd, + lcli.StateMinerProvingDeadlineCmd, + lcli.StateSysActorCIDsCmd, + }, +} + +var StateMinerInfo = &cli.Command{ + Name: "miner-info", + Usage: "Retrieve miner information", + ArgsUsage: "[minerAddress]", + Action: func(cctx *cli.Context) error { + addressGetter := func(_ *cli.Context) (address.Address, error) { + if cctx.NArg() != 1 { + return address.Address{}, lcli.IncorrectNumArgs(cctx) + } + + return address.NewFromString(cctx.Args().First()) + } + err := spcli.InfoCmd(addressGetter).Action(cctx) + if err != nil { + return err + } + return nil + }, +} diff --git a/cli/client.go b/cli/client.go index 81299b8fb..302e31e98 100644 --- a/cli/client.go +++ b/cli/client.go @@ -74,7 +74,7 @@ func GetCidEncoder(cctx *cli.Context) (cidenc.Encoder, error) { return e, nil } -var clientCmd = &cli.Command{ +var ClientCmd = &cli.Command{ Name: "client", Usage: "Make deals, store data, retrieve data", Subcommands: []*cli.Command{ diff --git a/cli/cmd.go b/cli/cmd.go index 802df0c99..76c0ab300 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -66,29 +66,6 @@ var CommonCommands = []*cli.Command{ VersionCmd, } -var Commands = []*cli.Command{ - WithCategory("basic", sendCmd), - WithCategory("basic", walletCmd), - WithCategory("basic", infoCmd), - WithCategory("basic", clientCmd), - WithCategory("basic", multisigCmd), - WithCategory("basic", filplusCmd), - WithCategory("basic", paychCmd), - WithCategory("developer", AuthCmd), - WithCategory("developer", MpoolCmd), - WithCategory("developer", StateCmd), - WithCategory("developer", ChainCmd), - WithCategory("developer", LogCmd), - WithCategory("developer", WaitApiCmd), - WithCategory("developer", FetchParamCmd), - WithCategory("developer", EvmCmd), - WithCategory("network", NetCmd), - WithCategory("network", SyncCmd), - WithCategory("status", StatusCmd), - PprofCmd, - VersionCmd, -} - func WithCategory(cat string, cmd *cli.Command) *cli.Command { cmd.Category = strings.ToUpper(cat) return cmd diff --git a/cli/filplus.go b/cli/filplus.go index 8c39c21e8..b8e887498 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -39,7 +39,7 @@ import ( "github.com/filecoin-project/lotus/lib/tablewriter" ) -var filplusCmd = &cli.Command{ +var FilplusCmd = &cli.Command{ Name: "filplus", Usage: "Interact with the verified registry actor used by Filplus", Flags: []cli.Flag{}, diff --git a/cli/info.go b/cli/info.go index 8b36be488..a406fc480 100644 --- a/cli/info.go +++ b/cli/info.go @@ -23,7 +23,7 @@ import ( "github.com/filecoin-project/lotus/journal/alerting" ) -var infoCmd = &cli.Command{ +var InfoCmd = &cli.Command{ Name: "info", Usage: "Print node info", Action: infoCmdAct, diff --git a/cli/multisig.go b/cli/multisig.go index 1af2a4c9e..290cf6700 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -32,7 +32,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) -var multisigCmd = &cli.Command{ +var MultisigCmd = &cli.Command{ Name: "msig", Usage: "Interact with a multisig wallet", Flags: []cli.Flag{ diff --git a/cli/paych.go b/cli/paych.go index 1067d0913..46b043d6a 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -20,7 +20,7 @@ import ( "github.com/filecoin-project/lotus/paychmgr" ) -var paychCmd = &cli.Command{ +var PaychCmd = &cli.Command{ Name: "paych", Usage: "Manage payment channels", Subcommands: []*cli.Command{ diff --git a/cli/send.go b/cli/send.go index cfa2515c0..89c79e109 100644 --- a/cli/send.go +++ b/cli/send.go @@ -19,7 +19,7 @@ import ( "github.com/filecoin-project/lotus/chain/types/ethtypes" ) -var sendCmd = &cli.Command{ +var SendCmd = &cli.Command{ Name: "send", Usage: "Send funds between accounts", ArgsUsage: "[targetAddress] [amount]", diff --git a/cli/send_test.go b/cli/send_test.go index 2c59a9641..59b8942f4 100644 --- a/cli/send_test.go +++ b/cli/send_test.go @@ -45,7 +45,7 @@ func TestSendCLI(t *testing.T) { oneFil := abi.TokenAmount(types.MustParseFIL("1")) t.Run("simple", func(t *testing.T) { - app, mockSrvcs, buf, done := newMockApp(t, sendCmd) + app, mockSrvcs, buf, done := newMockApp(t, SendCmd) defer done() arbtProto := &api.MessagePrototype{ @@ -76,7 +76,7 @@ func TestSendEthereum(t *testing.T) { oneFil := abi.TokenAmount(types.MustParseFIL("1")) t.Run("simple", func(t *testing.T) { - app, mockSrvcs, buf, done := newMockApp(t, sendCmd) + app, mockSrvcs, buf, done := newMockApp(t, SendCmd) defer done() testEthAddr, err := ethtypes.CastEthAddress(make([]byte, 20)) diff --git a/cli/spcli/actor.go b/cli/spcli/actor.go new file mode 100644 index 000000000..296d5ffb1 --- /dev/null +++ b/cli/spcli/actor.go @@ -0,0 +1,1240 @@ +package spcli + +import ( + "bytes" + "fmt" + "strconv" + + cbor "github.com/ipfs/go-ipld-cbor" + "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" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "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/network" + + "github.com/filecoin-project/lotus/api" + lapi "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" + 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/node/impl" +) + +func ActorWithdrawCmd(getActor ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "withdraw", + Usage: "withdraw available balance to beneficiary", + 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", + }, + }, + Action: func(cctx *cli.Context) error { + amount := abi.NewTokenAmount(0) + + 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) + } + + api, acloser, err := lcli.GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActor(cctx) + if err != nil { + return err + } + + res, err := impl.WithdrawBalance(ctx, api, maddr, amount, !cctx.IsSet("beneficiary")) + if err != nil { + return err + } + + fmt.Printf("Requested withdrawal in message %s\nwaiting for it to be included in a block..\n", res) + + // wait for it to get mined into a block + wait, err := api.StateWaitMsg(ctx, res, uint64(cctx.Int("confidence")), lapi.LookbackNoLimit, true) + if err != nil { + return xerrors.Errorf("Timeout waiting for withdrawal message %s", res) + } + + if wait.Receipt.ExitCode.IsError() { + return xerrors.Errorf("Failed to execute withdrawal message %s: %w", wait.Message, wait.Receipt.ExitCode.Error()) + } + + nv, err := api.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 + }, + } +} + +func ActorSetAddrsCmd(getActor ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "set-addresses", + Aliases: []string{"set-addrs"}, + Usage: "set addresses that your miner can be publicly dialed on", + ArgsUsage: "", + 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, + }, + &cli.BoolFlag{ + Name: "unset", + Usage: "unset address", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + args := cctx.Args().Slice() + unset := cctx.Bool("unset") + if len(args) == 0 && !unset { + return cli.ShowSubcommandHelp(cctx) + } + if len(args) > 0 && unset { + return fmt.Errorf("unset can only be used with no arguments") + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + var addrs []abi.Multiaddrs + 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()) + } + + maddr, err := getActor(cctx) + 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) + } + + 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, + Method: builtin.MethodsMiner.ChangeMultiaddrs, + Params: params, + }, nil) + if err != nil { + return err + } + + fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) + return nil + + }, + } +} + +func ActorSetPeeridCmd(getActor ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "set-peer-id", + Usage: "set the peer id of your miner", + ArgsUsage: "", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "gas-limit", + Usage: "set gas limit", + Value: 0, + }, + }, + Action: func(cctx *cli.Context) error { + + 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)) + if err != nil { + return fmt.Errorf("failed to parse input as a peerId: %w", err) + } + + maddr, err := getActor(cctx) + if err != nil { + return err + } + + minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + params, err := actors.SerializeParams(&miner.ChangePeerIDParams{NewID: abi.PeerID(pid)}) + 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, + Method: builtin.MethodsMiner.ChangePeerID, + Params: params, + }, nil) + if err != nil { + return err + } + + fmt.Printf("Requested peerid change in message %s\n", smsg.Cid()) + return nil + + }, + } +} + +func ActorRepayDebtCmd(getActor ActorAddressGetter) *cli.Command { + return &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 { + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActor(cctx) + 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))) + + 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 + } + + 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, + Method: builtin.MethodsMiner.RepayDebt, + Params: nil, + }, nil) + if err != nil { + return err + } + + fmt.Printf("Sent repay debt message %s\n", smsg.Cid()) + + return nil + }, + } +} + +func ActorControlCmd(getActor ActorAddressGetter, actorControlListCmd *cli.Command) *cli.Command { + return &cli.Command{ + Name: "control", + Usage: "Manage control addresses", + Subcommands: []*cli.Command{ + actorControlListCmd, + actorControlSet(getActor), + }, + } +} + +func actorControlSet(getActor ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "set", + Usage: "Set control address(-es)", + 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 { + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActor(cctx) + 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 + } + + 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{ + From: mi.Owner, + To: maddr, + Method: builtin.MethodsMiner.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 + }, + } +} + +func ActorSetOwnerCmd(getActor ActorAddressGetter) *cli.Command { + return &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 + } + + fromAddrId, err := api.StateLookupID(ctx, fa, types.EmptyTSK) + if err != nil { + return err + } + + maddr, err := getActor(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, + 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 + } + + fmt.Println("message succeeded!") + + return nil + }, + } +} + +func ActorProposeChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { + return &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") + } + + 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 + } + + maddr, err := getActor(cctx) + 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 + } + + cwp := &miner.ChangeWorkerAddressParams{ + 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, + Method: builtin.MethodsMiner.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 := 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") + } + + 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) + } + + 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) + + return nil + }, + } +} + +func ActorProposeChangeBeneficiaryCmd(getActor ActorAddressGetter) *cli.Command { + + return &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 := getActor(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") { + 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) + } + + if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == newAddr { + fmt.Println("Beneficiary address successfully changed") + } else { + fmt.Println("Beneficiary address change awaiting additional confirmations") + } + + return nil + }, + } +} + +func ActorConfirmChangeWorkerCmd(getActor ActorAddressGetter) *cli.Command { + return &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") + } + + 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 + } + + maddr, err := getActor(cctx) + 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") + return nil + } + + smsg, err := api.MpoolPushMessage(ctx, &types.Message{ + From: mi.Owner, + To: maddr, + Method: builtin.MethodsMiner.ConfirmChangeWorkerAddress, + Value: big.Zero(), + }, 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 err + } + + // check it executed successfully + if wait.Receipt.ExitCode.IsError() { + 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 + }, + } +} + +func ActorConfirmChangeBeneficiaryCmd(getActor ActorAddressGetter) *cli.Command { + return &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")) { + 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") { + 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() { + 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 + } + + 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 + }, + } +} + +func ActorCompactAllocatedCmd(getActor ActorAddressGetter) *cli.Command { + return &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 xerrors.Errorf("must pass address of new owner address") + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActor(cctx) + 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))) + + 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 { + 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 + } + + 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, + 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("Sector Bitfield compaction failed") + return err + } + + return nil + }, + } +} + +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 +} diff --git a/cli/spcli/info.go b/cli/spcli/info.go new file mode 100644 index 000000000..69436b2c7 --- /dev/null +++ b/cli/spcli/info.go @@ -0,0 +1,121 @@ +package spcli + +import ( + "fmt" + + "github.com/fatih/color" + "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-state-types/big" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + cliutil "github.com/filecoin-project/lotus/cli/util" +) + +func InfoCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "info", + Usage: "Print miner actor info", + Action: func(cctx *cli.Context) error { + api, closer, err := cliutil.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := cliutil.ReqContext(cctx) + + ts, err := lcli.LoadTipSet(ctx, cctx, api) + if err != nil { + return err + } + + addr, err := getActorAddress(cctx) + if err != nil { + return err + } + mi, err := api.StateMinerInfo(ctx, addr, ts.Key()) + if err != nil { + return err + } + + availableBalance, err := api.StateMinerAvailableBalance(ctx, addr, ts.Key()) + if err != nil { + return xerrors.Errorf("getting miner available balance: %w", err) + } + fmt.Printf("Available Balance: %s\n", types.FIL(availableBalance)) + fmt.Printf("Owner:\t%s\n", mi.Owner) + fmt.Printf("Worker:\t%s\n", mi.Worker) + for i, controlAddress := range mi.ControlAddresses { + fmt.Printf("Control %d: \t%s\n", i, controlAddress) + } + if mi.Beneficiary != address.Undef { + fmt.Printf("Beneficiary:\t%s\n", mi.Beneficiary) + if mi.Beneficiary != mi.Owner { + fmt.Printf("Beneficiary Quota:\t%s\n", mi.BeneficiaryTerm.Quota) + fmt.Printf("Beneficiary Used Quota:\t%s\n", mi.BeneficiaryTerm.UsedQuota) + fmt.Printf("Beneficiary Expiration:\t%s\n", mi.BeneficiaryTerm.Expiration) + } + } + if mi.PendingBeneficiaryTerm != nil { + fmt.Printf("Pending Beneficiary Term:\n") + fmt.Printf("New Beneficiary:\t%s\n", mi.PendingBeneficiaryTerm.NewBeneficiary) + fmt.Printf("New Quota:\t%s\n", mi.PendingBeneficiaryTerm.NewQuota) + fmt.Printf("New Expiration:\t%s\n", mi.PendingBeneficiaryTerm.NewExpiration) + fmt.Printf("Approved By Beneficiary:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByBeneficiary) + fmt.Printf("Approved By Nominee:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByNominee) + } + + fmt.Printf("PeerID:\t%s\n", mi.PeerId) + fmt.Printf("Multiaddrs:\t") + for _, addr := range mi.Multiaddrs { + a, err := multiaddr.NewMultiaddrBytes(addr) + if err != nil { + return xerrors.Errorf("undecodable listen address: %w", err) + } + fmt.Printf("%s ", a) + } + fmt.Println() + fmt.Printf("Consensus Fault End:\t%d\n", mi.ConsensusFaultElapsed) + + fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize) + pow, err := api.StateMinerPower(ctx, addr, ts.Key()) + if err != nil { + return err + } + + fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n", + color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), + types.SizeStr(pow.TotalPower.RawBytePower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), + pow.TotalPower.RawBytePower, + ), + ) + + fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n", + color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), + types.DeciStr(pow.TotalPower.QualityAdjPower), + types.BigDivFloat( + types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), + pow.TotalPower.QualityAdjPower, + ), + ) + + fmt.Println() + + cd, err := api.StateMinerProvingDeadline(ctx, addr, ts.Key()) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + fmt.Printf("Proving Period Start:\t%s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.PeriodStart)) + + return nil + }, + } +} diff --git a/cli/spcli/proving.go b/cli/spcli/proving.go new file mode 100644 index 000000000..ed4251f1b --- /dev/null +++ b/cli/spcli/proving.go @@ -0,0 +1,451 @@ +package spcli + +import ( + "bytes" + "fmt" + "os" + "strconv" + "strings" + "text/tabwriter" + "time" + + "github.com/fatih/color" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/dline" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + cliutil "github.com/filecoin-project/lotus/cli/util" +) + +func ProvingInfoCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "info", + Usage: "View current state information", + Action: func(cctx *cli.Context) error { + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + head, err := api.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) + } + + mact, err := api.StateGetActor(ctx, maddr, head.Key()) + if err != nil { + return err + } + + stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(api)) + + mas, err := miner.Load(stor, mact) + if err != nil { + return err + } + + cd, err := api.StateMinerProvingDeadline(ctx, maddr, head.Key()) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) + + proving := uint64(0) + faults := uint64(0) + recovering := uint64(0) + curDeadlineSectors := uint64(0) + + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + if bf, err := part.LiveSectors(); err != nil { + return err + } else if count, err := bf.Count(); err != nil { + return err + } else { + proving += count + if dlIdx == cd.Index { + curDeadlineSectors += count + } + } + + if bf, err := part.FaultySectors(); err != nil { + return err + } else if count, err := bf.Count(); err != nil { + return err + } else { + faults += count + } + + if bf, err := part.RecoveringSectors(); err != nil { + return err + } else if count, err := bf.Count(); err != nil { + return err + } else { + recovering += count + } + + return nil + }) + }); err != nil { + return xerrors.Errorf("walking miner deadlines and partitions: %w", err) + } + + var faultPerc float64 + if proving > 0 { + faultPerc = float64(faults * 100 / proving) + } + + fmt.Printf("Current Epoch: %d\n", cd.CurrentEpoch) + + fmt.Printf("Proving Period Boundary: %d\n", cd.PeriodStart%cd.WPoStProvingPeriod) + fmt.Printf("Proving Period Start: %s\n", cliutil.EpochTimeTs(cd.CurrentEpoch, cd.PeriodStart, head)) + fmt.Printf("Next Period Start: %s\n\n", cliutil.EpochTimeTs(cd.CurrentEpoch, cd.PeriodStart+cd.WPoStProvingPeriod, head)) + + fmt.Printf("Faults: %d (%.2f%%)\n", faults, faultPerc) + fmt.Printf("Recovering: %d\n", recovering) + + fmt.Printf("Deadline Index: %d\n", cd.Index) + fmt.Printf("Deadline Sectors: %d\n", curDeadlineSectors) + fmt.Printf("Deadline Open: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Open)) + fmt.Printf("Deadline Close: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Close)) + fmt.Printf("Deadline Challenge: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Challenge)) + fmt.Printf("Deadline FaultCutoff: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.FaultCutoff)) + return nil + }, + } +} + +func ProvingDeadlinesCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "deadlines", + Usage: "View the current proving period deadlines information", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "all", + Usage: "Count all sectors (only live sectors are counted by default)", + Aliases: []string{"a"}, + }, + }, + Action: func(cctx *cli.Context) error { + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + deadlines, err := api.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting deadlines: %w", err) + } + + di, err := api.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting deadlines: %w", err) + } + + head, err := api.ChainHead(ctx) + if err != nil { + return err + } + + fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) + + tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) + _, _ = fmt.Fprintln(tw, "deadline\topen\tpartitions\tsectors (faults)\tproven partitions") + + for dlIdx, deadline := range deadlines { + partitions, err := api.StateMinerPartitions(ctx, maddr, uint64(dlIdx), types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting partitions for deadline %d: %w", dlIdx, err) + } + + provenPartitions, err := deadline.PostSubmissions.Count() + if err != nil { + return err + } + + sectors := uint64(0) + faults := uint64(0) + var partitionCount int + + for _, partition := range partitions { + if !cctx.Bool("all") { + sc, err := partition.LiveSectors.Count() + if err != nil { + return err + } + + if sc > 0 { + partitionCount++ + } + + sectors += sc + } else { + sc, err := partition.AllSectors.Count() + if err != nil { + return err + } + + partitionCount++ + sectors += sc + } + + fc, err := partition.FaultySectors.Count() + if err != nil { + return err + } + + faults += fc + } + + var cur string + if di.Index == uint64(dlIdx) { + cur += "\t(current)" + } + + _, _ = fmt.Fprintf(tw, "%d\t%s\t%d\t%d (%d)\t%d%s\n", dlIdx, deadlineOpenTime(head, uint64(dlIdx), di), + partitionCount, sectors, faults, provenPartitions, cur) + } + + return tw.Flush() + }, + } +} + +func deadlineOpenTime(ts *types.TipSet, dlIdx uint64, di *dline.Info) string { + gapIdx := dlIdx - di.Index + gapHeight := uint64(di.WPoStProvingPeriod) / di.WPoStPeriodDeadlines * gapIdx + + openHeight := di.Open + abi.ChainEpoch(gapHeight) + genesisBlockTimestamp := ts.MinTimestamp() - uint64(ts.Height())*build.BlockDelaySecs + + return time.Unix(int64(genesisBlockTimestamp+build.BlockDelaySecs*uint64(openHeight)), 0).Format(time.TimeOnly) +} + +func ProvingDeadlineInfoCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "deadline", + Usage: "View the current proving period deadline information by its index", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "sector-nums", + Aliases: []string{"n"}, + Usage: "Print sector/fault numbers belonging to this deadline", + }, + &cli.BoolFlag{ + Name: "bitfield", + Aliases: []string{"b"}, + Usage: "Print partition bitfield stats", + }, + }, + ArgsUsage: "", + Action: func(cctx *cli.Context) error { + + if cctx.NArg() != 1 { + return lcli.IncorrectNumArgs(cctx) + } + + dlIdx, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) + if err != nil { + return xerrors.Errorf("could not parse deadline index: %w", err) + } + + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + deadlines, err := api.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting deadlines: %w", err) + } + + di, err := api.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting deadlines: %w", err) + } + + partitions, err := api.StateMinerPartitions(ctx, maddr, dlIdx, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting partitions for deadline %d: %w", dlIdx, err) + } + + head, err := api.ChainHead(ctx) + if err != nil { + return err + } + + provenPartitions, err := deadlines[dlIdx].PostSubmissions.Count() + if err != nil { + return err + } + + fmt.Printf("Deadline Index: %d\n", dlIdx) + fmt.Printf("Deadline Open: %s\n", deadlineOpenTime(head, dlIdx, di)) + fmt.Printf("Partitions: %d\n", len(partitions)) + fmt.Printf("Proven Partitions: %d\n", provenPartitions) + fmt.Printf("Current: %t\n\n", di.Index == dlIdx) + + for pIdx, partition := range partitions { + fmt.Printf("Partition Index: %d\n", pIdx) + + printStats := func(bf bitfield.BitField, name string) error { + count, err := bf.Count() + if err != nil { + return err + } + + rit, err := bf.RunIterator() + if err != nil { + return err + } + + if cctx.Bool("bitfield") { + var ones, zeros, oneRuns, zeroRuns, invalid uint64 + for rit.HasNext() { + r, err := rit.NextRun() + if err != nil { + return xerrors.Errorf("next run: %w", err) + } + if !r.Valid() { + invalid++ + } + if r.Val { + ones += r.Len + oneRuns++ + } else { + zeros += r.Len + zeroRuns++ + } + } + + var buf bytes.Buffer + if err := bf.MarshalCBOR(&buf); err != nil { + return err + } + sz := len(buf.Bytes()) + szstr := types.SizeStr(types.NewInt(uint64(sz))) + + fmt.Printf("\t%s Sectors:%s%d (bitfield - runs %d+%d=%d - %d 0s %d 1s - %d inv - %s %dB)\n", name, strings.Repeat(" ", 18-len(name)), count, zeroRuns, oneRuns, zeroRuns+oneRuns, zeros, ones, invalid, szstr, sz) + } else { + fmt.Printf("\t%s Sectors:%s%d\n", name, strings.Repeat(" ", 18-len(name)), count) + } + + if cctx.Bool("sector-nums") { + nums, err := bf.All(count) + if err != nil { + return err + } + fmt.Printf("\t%s Sector Numbers:%s%v\n", name, strings.Repeat(" ", 12-len(name)), nums) + } + + return nil + } + + if err := printStats(partition.AllSectors, "All"); err != nil { + return err + } + if err := printStats(partition.LiveSectors, "Live"); err != nil { + return err + } + if err := printStats(partition.ActiveSectors, "Active"); err != nil { + return err + } + if err := printStats(partition.FaultySectors, "Faulty"); err != nil { + return err + } + if err := printStats(partition.RecoveringSectors, "Recovering"); err != nil { + return err + } + } + return nil + }, + } +} + +func ProvingFaultsCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "faults", + Usage: "View the currently known proving faulty sectors information", + Action: func(cctx *cli.Context) error { + api, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(api)) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + mas, err := miner.Load(stor, mact) + if err != nil { + return err + } + + fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) + + tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) + _, _ = fmt.Fprintln(tw, "deadline\tpartition\tsectors") + err = mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + faults, err := part.FaultySectors() + if err != nil { + return err + } + return faults.ForEach(func(num uint64) error { + _, _ = fmt.Fprintf(tw, "%d\t%d\t%d\n", dlIdx, partIdx, num) + return nil + }) + }) + }) + if err != nil { + return err + } + return tw.Flush() + }, + } +} diff --git a/cli/spcli/sectors.go b/cli/spcli/sectors.go new file mode 100644 index 000000000..5528c6438 --- /dev/null +++ b/cli/spcli/sectors.go @@ -0,0 +1,1398 @@ +package spcli + +import ( + "bufio" + "encoding/csv" + "encoding/json" + "errors" + "fmt" + "math" + "os" + "sort" + "strconv" + "strings" + "time" + + "github.com/fatih/color" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/lib/tablewriter" +) + +type OnDiskInfoGetter func(cctx *cli.Context, id abi.SectorNumber, onChainInfo bool) (api.SectorInfo, error) + +func SectorsStatusCmd(getActorAddress ActorAddressGetter, getOnDiskInfo OnDiskInfoGetter) *cli.Command { + return &cli.Command{ + Name: "status", + Usage: "Get the seal status of a sector by its number", + ArgsUsage: "", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "log", + Usage: "display event log", + Aliases: []string{"l"}, + }, + &cli.BoolFlag{ + Name: "on-chain-info", + Usage: "show sector on chain info", + Aliases: []string{"c"}, + }, + &cli.BoolFlag{ + Name: "partition-info", + Usage: "show partition related info", + Aliases: []string{"p"}, + }, + &cli.BoolFlag{ + Name: "proof", + Usage: "print snark proof bytes as hex", + }, + }, + Action: func(cctx *cli.Context) error { + ctx := lcli.ReqContext(cctx) + + if cctx.NArg() != 1 { + return lcli.IncorrectNumArgs(cctx) + } + + id, err := strconv.ParseUint(cctx.Args().First(), 10, 64) + if err != nil { + return err + } + + onChainInfo := cctx.Bool("on-chain-info") + + var status api.SectorInfo + if getOnDiskInfo != nil { + status, err = getOnDiskInfo(cctx, abi.SectorNumber(id), onChainInfo) + if err != nil { + return err + } + fmt.Printf("SectorID:\t%d\n", status.SectorID) + fmt.Printf("Status:\t\t%s\n", status.State) + fmt.Printf("CIDcommD:\t%s\n", status.CommD) + fmt.Printf("CIDcommR:\t%s\n", status.CommR) + fmt.Printf("Ticket:\t\t%x\n", status.Ticket.Value) + fmt.Printf("TicketH:\t%d\n", status.Ticket.Epoch) + fmt.Printf("Seed:\t\t%x\n", status.Seed.Value) + fmt.Printf("SeedH:\t\t%d\n", status.Seed.Epoch) + fmt.Printf("Precommit:\t%s\n", status.PreCommitMsg) + fmt.Printf("Commit:\t\t%s\n", status.CommitMsg) + if cctx.Bool("proof") { + fmt.Printf("Proof:\t\t%x\n", status.Proof) + } + fmt.Printf("Deals:\t\t%v\n", status.Deals) + fmt.Printf("Retries:\t%d\n", status.Retries) + if status.LastErr != "" { + fmt.Printf("Last Error:\t\t%s\n", status.LastErr) + } + + fmt.Printf("\nExpiration Info\n") + fmt.Printf("OnTime:\t\t%v\n", status.OnTime) + fmt.Printf("Early:\t\t%v\n", status.Early) + + } else { + onChainInfo = true + } + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + if onChainInfo { + fullApi, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) + } + + status, err := fullApi.StateSectorGetInfo(ctx, maddr, abi.SectorNumber(id), head.Key()) + if err != nil { + return err + } + + mid, err := address.IDFromAddress(maddr) + if err != nil { + return err + } + fmt.Printf("\nSector On Chain Info\n") + fmt.Printf("SealProof:\t\t%x\n", status.SealProof) + fmt.Printf("Activation:\t\t%v\n", status.Activation) + fmt.Printf("Expiration:\t\t%v\n", status.Expiration) + fmt.Printf("DealWeight:\t\t%v\n", status.DealWeight) + fmt.Printf("VerifiedDealWeight:\t\t%v\n", status.VerifiedDealWeight) + fmt.Printf("InitialPledge:\t\t%v\n", types.FIL(status.InitialPledge)) + fmt.Printf("SectorID:\t\t{Miner: %v, Number: %v}\n", abi.ActorID(mid), status.SectorNumber) + } + + if cctx.Bool("partition-info") { + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + mact, err := fullApi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } + + errFound := errors.New("found") + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + pas, err := part.AllSectors() + if err != nil { + return err + } + + set, err := pas.IsSet(id) + if err != nil { + return err + } + if set { + fmt.Printf("\nDeadline:\t%d\n", dlIdx) + fmt.Printf("Partition:\t%d\n", partIdx) + + checkIn := func(name string, bg func() (bitfield.BitField, error)) error { + bf, err := bg() + if err != nil { + return err + } + + set, err := bf.IsSet(id) + if err != nil { + return err + } + setstr := "no" + if set { + setstr = "yes" + } + fmt.Printf("%s: \t%s\n", name, setstr) + return nil + } + + if err := checkIn("Unproven", part.UnprovenSectors); err != nil { + return err + } + if err := checkIn("Live", part.LiveSectors); err != nil { + return err + } + if err := checkIn("Active", part.ActiveSectors); err != nil { + return err + } + if err := checkIn("Faulty", part.FaultySectors); err != nil { + return err + } + if err := checkIn("Recovering", part.RecoveringSectors); err != nil { + return err + } + + return errFound + } + + return nil + }) + }); err != errFound { + if err != nil { + return err + } + + fmt.Println("\nNot found in any partition") + } + } + + if cctx.Bool("log") { + fmt.Printf("--------\nEvent Log:\n") + + for i, l := range status.Log { + fmt.Printf("%d.\t%s:\t[%s]\t%s\n", i, time.Unix(int64(l.Timestamp), 0), l.Kind, l.Message) + if l.Trace != "" { + fmt.Printf("\t%s\n", l.Trace) + } + } + } + return nil + }, + } +} + +func SectorsListUpgradeBoundsCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "upgrade-bounds", + Usage: "Output upgrade bounds for available sectors", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "buckets", + Value: 25, + }, + &cli.BoolFlag{ + Name: "csv", + Usage: "output machine-readable values", + }, + &cli.BoolFlag{ + Name: "deal-terms", + Usage: "bucket by how many deal-sectors can start at a given expiration", + }, + }, + Action: func(cctx *cli.Context) error { + fullApi, closer2, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer2() + + ctx := lcli.ReqContext(cctx) + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) + } + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + list, err := fullApi.StateMinerActiveSectors(ctx, maddr, head.Key()) + if err != nil { + return err + } + filter := bitfield.New() + + for _, s := range list { + filter.Set(uint64(s.SectorNumber)) + } + sset, err := fullApi.StateMinerSectors(ctx, maddr, &filter, head.Key()) + if err != nil { + return err + } + + if len(sset) == 0 { + return nil + } + + var minExpiration, maxExpiration abi.ChainEpoch + + for _, s := range sset { + if s.Expiration < minExpiration || minExpiration == 0 { + minExpiration = s.Expiration + } + if s.Expiration > maxExpiration { + maxExpiration = s.Expiration + } + } + + buckets := cctx.Int("buckets") + bucketSize := (maxExpiration - minExpiration) / abi.ChainEpoch(buckets) + bucketCounts := make([]int, buckets+1) + + for b := range bucketCounts { + bucketMin := minExpiration + abi.ChainEpoch(b)*bucketSize + bucketMax := minExpiration + abi.ChainEpoch(b+1)*bucketSize + + if cctx.Bool("deal-terms") { + bucketMax = bucketMax + policy.MarketDefaultAllocationTermBuffer + } + + for _, s := range sset { + isInBucket := s.Expiration >= bucketMin && s.Expiration < bucketMax + + if isInBucket { + bucketCounts[b]++ + } + } + + } + + // Creating CSV writer + writer := csv.NewWriter(os.Stdout) + + // Writing CSV headers + err = writer.Write([]string{"Max Expiration in Bucket", "Sector Count"}) + if err != nil { + return xerrors.Errorf("writing csv headers: %w", err) + } + + // Writing bucket details + + if cctx.Bool("csv") { + for i := 0; i < buckets; i++ { + maxExp := minExpiration + abi.ChainEpoch(i+1)*bucketSize + + timeStr := strconv.FormatInt(int64(maxExp), 10) + + err = writer.Write([]string{ + timeStr, + strconv.Itoa(bucketCounts[i]), + }) + if err != nil { + return xerrors.Errorf("writing csv row: %w", err) + } + } + + // Flush to make sure all data is written to the underlying writer + writer.Flush() + + if err := writer.Error(); err != nil { + return xerrors.Errorf("flushing csv writer: %w", err) + } + + return nil + } + + tw := tablewriter.New( + tablewriter.Col("Bucket Expiration"), + tablewriter.Col("Sector Count"), + tablewriter.Col("Bar"), + ) + + var barCols = 40 + var maxCount int + + for _, c := range bucketCounts { + if c > maxCount { + maxCount = c + } + } + + for i := 0; i < buckets; i++ { + maxExp := minExpiration + abi.ChainEpoch(i+1)*bucketSize + timeStr := cliutil.EpochTime(head.Height(), maxExp) + + tw.Write(map[string]interface{}{ + "Bucket Expiration": timeStr, + "Sector Count": color.YellowString("%d", bucketCounts[i]), + "Bar": "[" + color.GreenString(strings.Repeat("|", bucketCounts[i]*barCols/maxCount)) + strings.Repeat(" ", barCols-bucketCounts[i]*barCols/maxCount) + "]", + }) + } + + return tw.Flush(os.Stdout) + }, + } +} + +func SectorPreCommitsCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "precommits", + Usage: "Print on-chain precommit info", + Action: func(cctx *cli.Context) error { + ctx := lcli.ReqContext(cctx) + mapi, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + mact, err := mapi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(mapi))) + mst, err := miner.Load(store, mact) + if err != nil { + return err + } + preCommitSector := make([]miner.SectorPreCommitOnChainInfo, 0) + err = mst.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { + preCommitSector = append(preCommitSector, info) + return err + }) + less := func(i, j int) bool { + return preCommitSector[i].Info.SectorNumber <= preCommitSector[j].Info.SectorNumber + } + sort.Slice(preCommitSector, less) + for _, info := range preCommitSector { + fmt.Printf("%s: %s\n", info.Info.SectorNumber, info.PreCommitEpoch) + } + + return nil + }, + } +} + +func SectorsCheckExpireCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "check-expire", + Usage: "Inspect expiring sectors", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "cutoff", + Usage: "skip sectors whose current expiration is more than epochs from now, defaults to 60 days", + Value: 172800, + }, + }, + Action: func(cctx *cli.Context) error { + + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return err + } + currEpoch := head.Height() + + nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return err + } + + sectors, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + n := 0 + for _, s := range sectors { + if s.Expiration-currEpoch <= abi.ChainEpoch(cctx.Int64("cutoff")) { + sectors[n] = s + n++ + } + } + sectors = sectors[:n] + + sort.Slice(sectors, func(i, j int) bool { + if sectors[i].Expiration == sectors[j].Expiration { + return sectors[i].SectorNumber < sectors[j].SectorNumber + } + return sectors[i].Expiration < sectors[j].Expiration + }) + + tw := tablewriter.New( + tablewriter.Col("ID"), + tablewriter.Col("SealProof"), + tablewriter.Col("InitialPledge"), + tablewriter.Col("Activation"), + tablewriter.Col("Expiration"), + tablewriter.Col("MaxExpiration"), + tablewriter.Col("MaxExtendNow")) + + for _, sector := range sectors { + MaxExpiration := sector.Activation + policy.GetSectorMaxLifetime(sector.SealProof, nv) + maxExtension, err := policy.GetMaxSectorExpirationExtension(nv) + if err != nil { + return xerrors.Errorf("failed to get max extension: %w", err) + } + + MaxExtendNow := currEpoch + maxExtension + + if MaxExtendNow > MaxExpiration { + MaxExtendNow = MaxExpiration + } + + tw.Write(map[string]interface{}{ + "ID": sector.SectorNumber, + "SealProof": sector.SealProof, + "InitialPledge": types.FIL(sector.InitialPledge).Short(), + "Activation": cliutil.EpochTime(currEpoch, sector.Activation), + "Expiration": cliutil.EpochTime(currEpoch, sector.Expiration), + "MaxExpiration": cliutil.EpochTime(currEpoch, MaxExpiration), + "MaxExtendNow": cliutil.EpochTime(currEpoch, MaxExtendNow), + }) + } + + return tw.Flush(os.Stdout) + }, + } +} + +func SectorsExtendCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "extend", + Usage: "Extend expiring sectors while not exceeding each sector's max life", + ArgsUsage: "", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "from", + Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 120 (1 hour)", + }, + &cli.Int64Flag{ + Name: "to", + Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 92160 (32 days)", + }, + &cli.StringFlag{ + Name: "sector-file", + Usage: "provide a file containing one sector number in each line, ignoring above selecting criteria", + }, + &cli.StringFlag{ + Name: "exclude", + Usage: "optionally provide a file containing excluding sectors", + }, + &cli.Int64Flag{ + Name: "extension", + Usage: "try to extend selected sectors by this number of epochs, defaults to 540 days", + Value: 1555200, + }, + &cli.Int64Flag{ + Name: "new-expiration", + Usage: "try to extend selected sectors to this epoch, ignoring extension", + }, + &cli.BoolFlag{ + Name: "only-cc", + Usage: "only extend CC sectors (useful for making sector ready for snap upgrade)", + }, + &cli.BoolFlag{ + Name: "drop-claims", + Usage: "drop claims for sectors that can be extended, but only by dropping some of their verified power claims", + }, + &cli.Int64Flag{ + Name: "tolerance", + Usage: "don't try to extend sectors by fewer than this number of epochs, defaults to 7 days", + Value: 20160, + }, + &cli.StringFlag{ + Name: "max-fee", + Usage: "use up to this amount of FIL for one message. pass this flag to avoid message congestion.", + Value: "0", + }, + &cli.Int64Flag{ + Name: "max-sectors", + Usage: "the maximum number of sectors contained in each message", + }, + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "pass this flag to really extend sectors, otherwise will only print out json representation of parameters", + }, + }, + Action: func(cctx *cli.Context) error { + mf, err := types.ParseFIL(cctx.String("max-fee")) + if err != nil { + return err + } + + spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(mf)} + + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer nCloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return err + } + currEpoch := head.Height() + + nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return err + } + + activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + activeSectorsInfo := make(map[abi.SectorNumber]*miner.SectorOnChainInfo, len(activeSet)) + for _, info := range activeSet { + activeSectorsInfo[info.SectorNumber] = info + } + + mact, err := fullApi.StateGetActor(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) + adtStore := adt.WrapStore(ctx, cbor.NewCborStore(tbs)) + mas, err := miner.Load(adtStore, mact) + if err != nil { + return err + } + + activeSectorsLocation := make(map[abi.SectorNumber]*miner.SectorLocation, len(activeSet)) + + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + pas, err := part.ActiveSectors() + if err != nil { + return err + } + + return pas.ForEach(func(i uint64) error { + activeSectorsLocation[abi.SectorNumber(i)] = &miner.SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + } + return nil + }) + }) + }); err != nil { + return err + } + + excludeSet := make(map[abi.SectorNumber]struct{}) + if cctx.IsSet("exclude") { + excludeSectors, err := getSectorsFromFile(cctx.String("exclude")) + if err != nil { + return err + } + + for _, id := range excludeSectors { + excludeSet[id] = struct{}{} + } + } + + var sectors []abi.SectorNumber + if cctx.Args().Present() { + if cctx.IsSet("sector-file") { + return xerrors.Errorf("sector-file specified along with command line params") + } + + for i, s := range cctx.Args().Slice() { + id, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return xerrors.Errorf("could not parse sector %d: %w", i, err) + } + + sectors = append(sectors, abi.SectorNumber(id)) + } + } else if cctx.IsSet("sector-file") { + sectors, err = getSectorsFromFile(cctx.String("sector-file")) + if err != nil { + return err + } + } else { + from := currEpoch + 120 + to := currEpoch + 92160 + + if cctx.IsSet("from") { + from = abi.ChainEpoch(cctx.Int64("from")) + } + + if cctx.IsSet("to") { + to = abi.ChainEpoch(cctx.Int64("to")) + } + + for _, si := range activeSet { + if si.Expiration >= from && si.Expiration <= to { + sectors = append(sectors, si.SectorNumber) + } + } + } + + var sis []*miner.SectorOnChainInfo + for _, id := range sectors { + if _, exclude := excludeSet[id]; exclude { + continue + } + + si, found := activeSectorsInfo[id] + if !found { + return xerrors.Errorf("sector %d is not active", id) + } + if len(si.DealIDs) > 0 && cctx.Bool("only-cc") { + continue + } + + sis = append(sis, si) + } + + withinTolerance := func(a, b abi.ChainEpoch) bool { + diff := a - b + if diff < 0 { + diff = -diff + } + + return diff <= abi.ChainEpoch(cctx.Int64("tolerance")) + } + + extensions := map[miner.SectorLocation]map[abi.ChainEpoch][]abi.SectorNumber{} + for _, si := range sis { + extension := abi.ChainEpoch(cctx.Int64("extension")) + newExp := si.Expiration + extension + + if cctx.IsSet("new-expiration") { + newExp = abi.ChainEpoch(cctx.Int64("new-expiration")) + } + + maxExtension, err := policy.GetMaxSectorExpirationExtension(nv) + if err != nil { + return xerrors.Errorf("failed to get max extension: %w", err) + } + + maxExtendNow := currEpoch + maxExtension + if newExp > maxExtendNow { + newExp = maxExtendNow + } + + maxExp := si.Activation + policy.GetSectorMaxLifetime(si.SealProof, nv) + if newExp > maxExp { + newExp = maxExp + } + + if newExp <= si.Expiration || withinTolerance(newExp, si.Expiration) { + continue + } + + l, found := activeSectorsLocation[si.SectorNumber] + if !found { + return xerrors.Errorf("location for sector %d not found", si.SectorNumber) + } + + es, found := extensions[*l] + if !found { + ne := make(map[abi.ChainEpoch][]abi.SectorNumber) + ne[newExp] = []abi.SectorNumber{si.SectorNumber} + extensions[*l] = ne + } else { + added := false + for exp := range es { + if withinTolerance(newExp, exp) { + es[exp] = append(es[exp], si.SectorNumber) + added = true + break + } + } + + if !added { + es[newExp] = []abi.SectorNumber{si.SectorNumber} + } + } + } + + verifregAct, err := fullApi.StateGetActor(ctx, builtin.VerifiedRegistryActorAddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("failed to lookup verifreg actor: %w", err) + } + + verifregSt, err := verifreg.Load(adtStore, verifregAct) + if err != nil { + return xerrors.Errorf("failed to load verifreg state: %w", err) + } + + claimsMap, err := verifregSt.GetClaims(maddr) + if err != nil { + return xerrors.Errorf("failed to lookup claims for miner: %w", err) + } + + claimIdsBySector, err := verifregSt.GetClaimIdsBySector(maddr) + if err != nil { + return xerrors.Errorf("failed to lookup claim IDs by sector: %w", err) + } + + sectorsMax, err := policy.GetAddressedSectorsMax(nv) + if err != nil { + return err + } + + declMax, err := policy.GetDeclarationsMax(nv) + if err != nil { + return err + } + + addrSectors := sectorsMax + if cctx.Int("max-sectors") != 0 { + addrSectors = cctx.Int("max-sectors") + if addrSectors > sectorsMax { + return xerrors.Errorf("the specified max-sectors exceeds the maximum limit") + } + } + + var params []miner.ExtendSectorExpiration2Params + + p := miner.ExtendSectorExpiration2Params{} + scount := 0 + + for l, exts := range extensions { + for newExp, numbers := range exts { + sectorsWithoutClaimsToExtend := bitfield.New() + var sectorsWithClaims []miner.SectorClaim + for _, sectorNumber := range numbers { + claimIdsToMaintain := make([]verifreg.ClaimId, 0) + claimIdsToDrop := make([]verifreg.ClaimId, 0) + cannotExtendSector := false + claimIds, ok := claimIdsBySector[sectorNumber] + // Nothing to check, add to ccSectors + if !ok { + sectorsWithoutClaimsToExtend.Set(uint64(sectorNumber)) + } else { + for _, claimId := range claimIds { + claim, ok := claimsMap[claimId] + if !ok { + return xerrors.Errorf("failed to find claim for claimId %d", claimId) + } + claimExpiration := claim.TermStart + claim.TermMax + // can be maintained in the extended sector + if claimExpiration > newExp { + claimIdsToMaintain = append(claimIdsToMaintain, claimId) + } else { + sectorInfo, ok := activeSectorsInfo[sectorNumber] + if !ok { + return xerrors.Errorf("failed to find sector in active sector set: %w", err) + } + if !cctx.Bool("drop-claims") || + // FIP-0045 requires the claim minimum duration to have passed + currEpoch <= (claim.TermStart+claim.TermMin) || + // FIP-0045 requires the sector to be in its last 30 days of life + (currEpoch <= sectorInfo.Expiration-builtin.EndOfLifeClaimDropPeriod) { + fmt.Printf("skipping sector %d because claim %d does not live long enough \n", sectorNumber, claimId) + cannotExtendSector = true + break + } + + claimIdsToDrop = append(claimIdsToDrop, claimId) + } + } + if cannotExtendSector { + continue + } + + if len(claimIdsToMaintain)+len(claimIdsToDrop) != 0 { + sectorsWithClaims = append(sectorsWithClaims, miner.SectorClaim{ + SectorNumber: sectorNumber, + MaintainClaims: claimIdsToMaintain, + DropClaims: claimIdsToDrop, + }) + } + } + } + + sectorsWithoutClaimsCount, err := sectorsWithoutClaimsToExtend.Count() + if err != nil { + return xerrors.Errorf("failed to count cc sectors: %w", err) + } + + sectorsInDecl := int(sectorsWithoutClaimsCount) + len(sectorsWithClaims) + scount += sectorsInDecl + + if scount > addrSectors || len(p.Extensions) >= declMax { + params = append(params, p) + p = miner.ExtendSectorExpiration2Params{} + scount = sectorsInDecl + } + + p.Extensions = append(p.Extensions, miner.ExpirationExtension2{ + Deadline: l.Deadline, + Partition: l.Partition, + Sectors: SectorNumsToBitfield(numbers), + SectorsWithClaims: sectorsWithClaims, + NewExpiration: newExp, + }) + + } + } + + // if we have any sectors, then one last append is needed here + if scount != 0 { + params = append(params, p) + } + + if len(params) == 0 { + fmt.Println("nothing to extend") + return nil + } + + mi, err := fullApi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return xerrors.Errorf("getting miner info: %w", err) + } + + stotal := 0 + + for i := range params { + scount := 0 + for _, ext := range params[i].Extensions { + count, err := ext.Sectors.Count() + if err != nil { + return err + } + scount += int(count) + } + fmt.Printf("Extending %d sectors: ", scount) + stotal += scount + + if !cctx.Bool("really-do-it") { + pp, err := NewPseudoExtendParams(¶ms[i]) + if err != nil { + return err + } + + data, err := json.MarshalIndent(pp, "", " ") + if err != nil { + return err + } + + fmt.Println("\n", string(data)) + continue + } + + sp, aerr := actors.SerializeParams(¶ms[i]) + if aerr != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + smsg, err := fullApi.MpoolPushMessage(ctx, &types.Message{ + From: mi.Worker, + To: maddr, + Method: builtin.MethodsMiner.ExtendSectorExpiration2, + Value: big.Zero(), + Params: sp, + }, spec) + if err != nil { + return xerrors.Errorf("mpool push message: %w", err) + } + + fmt.Println(smsg.Cid()) + } + + fmt.Printf("%d sectors extended\n", stotal) + + return nil + }, + } +} + +func SectorNumsToBitfield(sectors []abi.SectorNumber) bitfield.BitField { + var numbers []uint64 + for _, sector := range sectors { + numbers = append(numbers, uint64(sector)) + } + + return bitfield.NewFromSet(numbers) +} + +func getSectorsFromFile(filePath string) ([]abi.SectorNumber, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + + scanner := bufio.NewScanner(file) + sectors := make([]abi.SectorNumber, 0) + + for scanner.Scan() { + line := scanner.Text() + + id, err := strconv.ParseUint(line, 10, 64) + if err != nil { + return nil, xerrors.Errorf("could not parse %s as sector id: %s", line, err) + } + + sectors = append(sectors, abi.SectorNumber(id)) + } + + if err = file.Close(); err != nil { + return nil, err + } + + return sectors, nil +} + +func NewPseudoExtendParams(p *miner.ExtendSectorExpiration2Params) (*PseudoExtendSectorExpirationParams, error) { + res := PseudoExtendSectorExpirationParams{} + for _, ext := range p.Extensions { + scount, err := ext.Sectors.Count() + if err != nil { + return nil, err + } + + sectors, err := ext.Sectors.All(scount) + if err != nil { + return nil, err + } + + res.Extensions = append(res.Extensions, PseudoExpirationExtension{ + Deadline: ext.Deadline, + Partition: ext.Partition, + Sectors: ArrayToString(sectors), + NewExpiration: ext.NewExpiration, + }) + } + return &res, nil +} + +type PseudoExtendSectorExpirationParams struct { + Extensions []PseudoExpirationExtension +} + +type PseudoExpirationExtension struct { + Deadline uint64 + Partition uint64 + Sectors string + NewExpiration abi.ChainEpoch +} + +// ArrayToString Example: {1,3,4,5,8,9} -> "1,3-5,8-9" +func ArrayToString(array []uint64) string { + sort.Slice(array, func(i, j int) bool { + return array[i] < array[j] + }) + + var sarray []string + s := "" + + for i, elm := range array { + if i == 0 { + s = strconv.FormatUint(elm, 10) + continue + } + if elm == array[i-1] { + continue // filter out duplicates + } else if elm == array[i-1]+1 { + s = strings.Split(s, "-")[0] + "-" + strconv.FormatUint(elm, 10) + } else { + sarray = append(sarray, s) + s = strconv.FormatUint(elm, 10) + } + } + + if s != "" { + sarray = append(sarray, s) + } + + return strings.Join(sarray, ",") +} + +func SectorsCompactPartitionsCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "compact-partitions", + Usage: "removes dead sectors from partitions and reduces the number of partitions used if possible", + Flags: []cli.Flag{ + &cli.Uint64Flag{ + Name: "deadline", + Usage: "the deadline to compact the partitions in", + Required: true, + }, + &cli.Int64SliceFlag{ + Name: "partitions", + Usage: "list of partitions to compact sectors in", + Required: true, + }, + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "Actually send transaction performing the action", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + fullNodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActorAddress(cctx) + if err != nil { + return err + } + + minfo, err := fullNodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + deadline := cctx.Uint64("deadline") + if deadline > miner.WPoStPeriodDeadlines { + return fmt.Errorf("deadline %d out of range", deadline) + } + + parts := cctx.Int64Slice("partitions") + if len(parts) <= 0 { + return fmt.Errorf("must include at least one partition to compact") + } + fmt.Printf("compacting %d partitions\n", len(parts)) + + var makeMsgForPartitions func(partitionsBf bitfield.BitField) ([]*types.Message, error) + makeMsgForPartitions = func(partitionsBf bitfield.BitField) ([]*types.Message, error) { + params := miner.CompactPartitionsParams{ + Deadline: deadline, + Partitions: partitionsBf, + } + + sp, aerr := actors.SerializeParams(¶ms) + if aerr != nil { + return nil, xerrors.Errorf("serializing params: %w", err) + } + + msg := &types.Message{ + From: minfo.Worker, + To: maddr, + Method: builtin.MethodsMiner.CompactPartitions, + Value: big.Zero(), + Params: sp, + } + + estimatedMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) + if err != nil && errors.Is(err, &api.ErrOutOfGas{}) { + // the message is too big -- split into 2 + partitionsSlice, err := partitionsBf.All(math.MaxUint64) + if err != nil { + return nil, err + } + + partitions1 := bitfield.New() + for i := 0; i < len(partitionsSlice)/2; i++ { + partitions1.Set(uint64(i)) + } + + msgs1, err := makeMsgForPartitions(partitions1) + if err != nil { + return nil, err + } + + // time for the second half + partitions2 := bitfield.New() + for i := len(partitionsSlice) / 2; i < len(partitionsSlice); i++ { + partitions2.Set(uint64(i)) + } + + msgs2, err := makeMsgForPartitions(partitions2) + if err != nil { + return nil, err + } + + return append(msgs1, msgs2...), nil + } else if err != nil { + return nil, err + } + + return []*types.Message{estimatedMsg}, nil + } + + partitions := bitfield.New() + for _, partition := range parts { + partitions.Set(uint64(partition)) + } + + msgs, err := makeMsgForPartitions(partitions) + if err != nil { + return xerrors.Errorf("failed to make messages: %w", err) + } + + // Actually send the messages if really-do-it provided, simulate otherwise + if cctx.Bool("really-do-it") { + smsgs, err := fullNodeAPI.MpoolBatchPushMessage(ctx, msgs, nil) + if err != nil { + return xerrors.Errorf("mpool push: %w", err) + } + + if len(smsgs) == 1 { + fmt.Printf("Requested compact partitions in message %s\n", smsgs[0].Cid()) + } else { + fmt.Printf("Requested compact partitions in %d messages\n\n", len(smsgs)) + for _, v := range smsgs { + fmt.Println(v.Cid()) + } + } + + for _, v := range smsgs { + wait, err := fullNodeAPI.StateWaitMsg(ctx, v.Cid(), 2) + if err != nil { + return err + } + + // check it executed successfully + if wait.Receipt.ExitCode.IsError() { + fmt.Println(cctx.App.Writer, "compact partitions msg %s failed!", v.Cid()) + return err + } + } + + return nil + } + + for i, v := range msgs { + fmt.Printf("total of %d CompactPartitions msgs would be sent\n", len(msgs)) + + estMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, v, nil, types.EmptyTSK) + if err != nil { + return err + } + + fmt.Printf("msg %d would cost up to %s\n", i+1, types.FIL(estMsg.RequiredFunds())) + } + + return nil + + }, + } +} + +func TerminateSectorCmd(getActorAddress ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "terminate", + Usage: "Forcefully terminate a sector (WARNING: This means losing power and pay a one-time termination penalty(including collateral) for the terminated sector)", + ArgsUsage: "[sectorNum1 sectorNum2 ...]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "actor", + Usage: "specify the address of miner actor", + }, + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "pass this flag if you know what you are doing", + }, + &cli.StringFlag{ + Name: "from", + Usage: "specify the address to send the terminate message from", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.NArg() < 1 { + return lcli.ShowHelp(cctx, fmt.Errorf("at least one sector must be specified")) + } + + 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) + } + } + + if !cctx.Bool("really-do-it") { + return fmt.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing") + } + + nodeApi, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + if maddr.Empty() { + maddr, err = getActorAddress(cctx) + if err != nil { + return err + } + } + + mi, err := nodeApi.StateMinerInfo(ctx, maddr, types.EmptyTSK) + if err != nil { + return err + } + + terminationDeclarationParams := []miner2.TerminationDeclaration{} + + for _, sn := range cctx.Args().Slice() { + sectorNum, err := strconv.ParseUint(sn, 10, 64) + if err != nil { + return fmt.Errorf("could not parse sector number: %w", err) + } + + sectorbit := bitfield.New() + sectorbit.Set(sectorNum) + + loca, err := nodeApi.StateSectorPartition(ctx, maddr, abi.SectorNumber(sectorNum), types.EmptyTSK) + if err != nil { + return fmt.Errorf("get state sector partition %s", err) + } + + para := miner2.TerminationDeclaration{ + Deadline: loca.Deadline, + Partition: loca.Partition, + Sectors: sectorbit, + } + + terminationDeclarationParams = append(terminationDeclarationParams, para) + } + + terminateSectorParams := &miner2.TerminateSectorsParams{ + Terminations: terminationDeclarationParams, + } + + sp, err := actors.SerializeParams(terminateSectorParams) + if err != nil { + return xerrors.Errorf("serializing params: %w", err) + } + + var fromAddr address.Address + if from := cctx.String("from"); from != "" { + var err error + fromAddr, err = address.NewFromString(from) + if err != nil { + return fmt.Errorf("parsing address %s: %w", from, err) + } + } else { + fromAddr = mi.Worker + } + + smsg, err := nodeApi.MpoolPushMessage(ctx, &types.Message{ + From: fromAddr, + To: maddr, + Method: builtin.MethodsMiner.TerminateSectors, + + Value: big.Zero(), + Params: sp, + }, nil) + if err != nil { + return xerrors.Errorf("mpool push message: %w", err) + } + + fmt.Println("sent termination message:", smsg.Cid()) + + wait, err := nodeApi.StateWaitMsg(ctx, smsg.Cid(), uint64(cctx.Int("confidence"))) + if err != nil { + return err + } + + if wait.Receipt.ExitCode.IsError() { + return fmt.Errorf("terminate sectors message returned exit %d", wait.Receipt.ExitCode) + } + + return nil + }, + } +} diff --git a/cli/spcli/statemeta.go b/cli/spcli/statemeta.go new file mode 100644 index 000000000..72de87807 --- /dev/null +++ b/cli/spcli/statemeta.go @@ -0,0 +1,95 @@ +package spcli + +import ( + "github.com/fatih/color" + + sealing "github.com/filecoin-project/lotus/storage/pipeline" +) + +type StateMeta struct { + I int + Col color.Attribute + State sealing.SectorState +} + +var StateOrder = map[sealing.SectorState]StateMeta{} +var StateList = []StateMeta{ + {Col: 39, State: "Total"}, + {Col: color.FgGreen, State: sealing.Proving}, + {Col: color.FgGreen, State: sealing.Available}, + {Col: color.FgGreen, State: sealing.UpdateActivating}, + + {Col: color.FgMagenta, State: sealing.ReceiveSector}, + + {Col: color.FgBlue, State: sealing.Empty}, + {Col: color.FgBlue, State: sealing.WaitDeals}, + {Col: color.FgBlue, State: sealing.AddPiece}, + {Col: color.FgBlue, State: sealing.SnapDealsWaitDeals}, + {Col: color.FgBlue, State: sealing.SnapDealsAddPiece}, + + {Col: color.FgRed, State: sealing.UndefinedSectorState}, + {Col: color.FgYellow, State: sealing.Packing}, + {Col: color.FgYellow, State: sealing.GetTicket}, + {Col: color.FgYellow, State: sealing.PreCommit1}, + {Col: color.FgYellow, State: sealing.PreCommit2}, + {Col: color.FgYellow, State: sealing.PreCommitting}, + {Col: color.FgYellow, State: sealing.PreCommitWait}, + {Col: color.FgYellow, State: sealing.SubmitPreCommitBatch}, + {Col: color.FgYellow, State: sealing.PreCommitBatchWait}, + {Col: color.FgYellow, State: sealing.WaitSeed}, + {Col: color.FgYellow, State: sealing.Committing}, + {Col: color.FgYellow, State: sealing.CommitFinalize}, + {Col: color.FgYellow, State: sealing.SubmitCommit}, + {Col: color.FgYellow, State: sealing.CommitWait}, + {Col: color.FgYellow, State: sealing.SubmitCommitAggregate}, + {Col: color.FgYellow, State: sealing.CommitAggregateWait}, + {Col: color.FgYellow, State: sealing.FinalizeSector}, + {Col: color.FgYellow, State: sealing.SnapDealsPacking}, + {Col: color.FgYellow, State: sealing.UpdateReplica}, + {Col: color.FgYellow, State: sealing.ProveReplicaUpdate}, + {Col: color.FgYellow, State: sealing.SubmitReplicaUpdate}, + {Col: color.FgYellow, State: sealing.ReplicaUpdateWait}, + {Col: color.FgYellow, State: sealing.WaitMutable}, + {Col: color.FgYellow, State: sealing.FinalizeReplicaUpdate}, + {Col: color.FgYellow, State: sealing.ReleaseSectorKey}, + + {Col: color.FgCyan, State: sealing.Terminating}, + {Col: color.FgCyan, State: sealing.TerminateWait}, + {Col: color.FgCyan, State: sealing.TerminateFinality}, + {Col: color.FgCyan, State: sealing.TerminateFailed}, + {Col: color.FgCyan, State: sealing.Removing}, + {Col: color.FgCyan, State: sealing.Removed}, + {Col: color.FgCyan, State: sealing.AbortUpgrade}, + + {Col: color.FgRed, State: sealing.FailedUnrecoverable}, + {Col: color.FgRed, State: sealing.AddPieceFailed}, + {Col: color.FgRed, State: sealing.SealPreCommit1Failed}, + {Col: color.FgRed, State: sealing.SealPreCommit2Failed}, + {Col: color.FgRed, State: sealing.PreCommitFailed}, + {Col: color.FgRed, State: sealing.ComputeProofFailed}, + {Col: color.FgRed, State: sealing.RemoteCommitFailed}, + {Col: color.FgRed, State: sealing.CommitFailed}, + {Col: color.FgRed, State: sealing.CommitFinalizeFailed}, + {Col: color.FgRed, State: sealing.PackingFailed}, + {Col: color.FgRed, State: sealing.FinalizeFailed}, + {Col: color.FgRed, State: sealing.Faulty}, + {Col: color.FgRed, State: sealing.FaultReported}, + {Col: color.FgRed, State: sealing.FaultedFinal}, + {Col: color.FgRed, State: sealing.RemoveFailed}, + {Col: color.FgRed, State: sealing.DealsExpired}, + {Col: color.FgRed, State: sealing.RecoverDealIDs}, + {Col: color.FgRed, State: sealing.SnapDealsAddPieceFailed}, + {Col: color.FgRed, State: sealing.SnapDealsDealsExpired}, + {Col: color.FgRed, State: sealing.ReplicaUpdateFailed}, + {Col: color.FgRed, State: sealing.ReleaseSectorKeyFailed}, + {Col: color.FgRed, State: sealing.FinalizeReplicaUpdateFailed}, +} + +func init() { + for i, state := range StateList { + StateOrder[state.State] = StateMeta{ + I: i, + Col: state.Col, + } + } +} diff --git a/cli/spcli/util.go b/cli/spcli/util.go new file mode 100644 index 000000000..71ac371fe --- /dev/null +++ b/cli/spcli/util.go @@ -0,0 +1,9 @@ +package spcli + +import ( + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-address" +) + +type ActorAddressGetter func(cctx *cli.Context) (address address.Address, err error) diff --git a/cli/state.go b/cli/state.go index f7d7e7127..343e68b53 100644 --- a/cli/state.go +++ b/cli/state.go @@ -17,10 +17,8 @@ import ( "text/tabwriter" "time" - "github.com/fatih/color" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" - "github.com/multiformats/go-multiaddr" "github.com/urfave/cli/v2" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" @@ -47,43 +45,6 @@ import ( cliutil "github.com/filecoin-project/lotus/cli/util" ) -var StateCmd = &cli.Command{ - Name: "state", - Usage: "Interact with and query filecoin chain state", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "tipset", - Usage: "specify tipset to call method on (pass comma separated array of cids)", - }, - }, - Subcommands: []*cli.Command{ - StatePowerCmd, - StateSectorsCmd, - StateActiveSectorsCmd, - StateListActorsCmd, - StateListMinersCmd, - StateCircSupplyCmd, - StateSectorCmd, - StateGetActorCmd, - StateLookupIDCmd, - StateReplayCmd, - StateSectorSizeCmd, - StateReadStateCmd, - StateListMessagesCmd, - StateComputeStateCmd, - StateCallCmd, - StateGetDealSetCmd, - StateWaitMsgCmd, - StateSearchMsgCmd, - StateMinerInfo, - StateMarketCmd, - StateExecTraceCmd, - StateNtwkVersionCmd, - StateMinerProvingDeadlineCmd, - StateSysActorCIDsCmd, - }, -} - var StateMinerProvingDeadlineCmd = &cli.Command{ Name: "miner-proving-deadline", Usage: "Retrieve information about a given miner's proving deadline", @@ -127,114 +88,6 @@ var StateMinerProvingDeadlineCmd = &cli.Command{ }, } -var StateMinerInfo = &cli.Command{ - Name: "miner-info", - Usage: "Retrieve miner information", - ArgsUsage: "[minerAddress]", - Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := ReqContext(cctx) - - if cctx.NArg() != 1 { - return IncorrectNumArgs(cctx) - } - - addr, err := address.NewFromString(cctx.Args().First()) - if err != nil { - return err - } - - ts, err := LoadTipSet(ctx, cctx, api) - if err != nil { - return err - } - - mi, err := api.StateMinerInfo(ctx, addr, ts.Key()) - if err != nil { - return err - } - - availableBalance, err := api.StateMinerAvailableBalance(ctx, addr, ts.Key()) - if err != nil { - return xerrors.Errorf("getting miner available balance: %w", err) - } - fmt.Printf("Available Balance: %s\n", types.FIL(availableBalance)) - fmt.Printf("Owner:\t%s\n", mi.Owner) - fmt.Printf("Worker:\t%s\n", mi.Worker) - for i, controlAddress := range mi.ControlAddresses { - fmt.Printf("Control %d: \t%s\n", i, controlAddress) - } - if mi.Beneficiary != address.Undef { - fmt.Printf("Beneficiary:\t%s\n", mi.Beneficiary) - if mi.Beneficiary != mi.Owner { - fmt.Printf("Beneficiary Quota:\t%s\n", mi.BeneficiaryTerm.Quota) - fmt.Printf("Beneficiary Used Quota:\t%s\n", mi.BeneficiaryTerm.UsedQuota) - fmt.Printf("Beneficiary Expiration:\t%s\n", mi.BeneficiaryTerm.Expiration) - } - } - if mi.PendingBeneficiaryTerm != nil { - fmt.Printf("Pending Beneficiary Term:\n") - fmt.Printf("New Beneficiary:\t%s\n", mi.PendingBeneficiaryTerm.NewBeneficiary) - fmt.Printf("New Quota:\t%s\n", mi.PendingBeneficiaryTerm.NewQuota) - fmt.Printf("New Expiration:\t%s\n", mi.PendingBeneficiaryTerm.NewExpiration) - fmt.Printf("Approved By Beneficiary:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByBeneficiary) - fmt.Printf("Approved By Nominee:\t%t\n", mi.PendingBeneficiaryTerm.ApprovedByNominee) - } - - fmt.Printf("PeerID:\t%s\n", mi.PeerId) - fmt.Printf("Multiaddrs:\t") - for _, addr := range mi.Multiaddrs { - a, err := multiaddr.NewMultiaddrBytes(addr) - if err != nil { - return xerrors.Errorf("undecodable listen address: %w", err) - } - fmt.Printf("%s ", a) - } - fmt.Println() - fmt.Printf("Consensus Fault End:\t%d\n", mi.ConsensusFaultElapsed) - - fmt.Printf("SectorSize:\t%s (%d)\n", types.SizeStr(types.NewInt(uint64(mi.SectorSize))), mi.SectorSize) - pow, err := api.StateMinerPower(ctx, addr, ts.Key()) - if err != nil { - return err - } - - fmt.Printf("Byte Power: %s / %s (%0.4f%%)\n", - color.BlueString(types.SizeStr(pow.MinerPower.RawBytePower)), - types.SizeStr(pow.TotalPower.RawBytePower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.RawBytePower, big.NewInt(100)), - pow.TotalPower.RawBytePower, - ), - ) - - fmt.Printf("Actual Power: %s / %s (%0.4f%%)\n", - color.GreenString(types.DeciStr(pow.MinerPower.QualityAdjPower)), - types.DeciStr(pow.TotalPower.QualityAdjPower), - types.BigDivFloat( - types.BigMul(pow.MinerPower.QualityAdjPower, big.NewInt(100)), - pow.TotalPower.QualityAdjPower, - ), - ) - - fmt.Println() - - cd, err := api.StateMinerProvingDeadline(ctx, addr, ts.Key()) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - fmt.Printf("Proving Period Start:\t%s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.PeriodStart)) - - return nil - }, -} - func ParseTipSetString(ts string) ([]cid.Cid, error) { strs := strings.Split(ts, ",") diff --git a/cli/wallet.go b/cli/wallet.go index faf7bc239..4af8dca58 100644 --- a/cli/wallet.go +++ b/cli/wallet.go @@ -27,7 +27,7 @@ import ( "github.com/filecoin-project/lotus/lib/tablewriter" ) -var walletCmd = &cli.Command{ +var WalletCmd = &cli.Command{ Name: "wallet", Usage: "Manage wallet", Subcommands: []*cli.Command{ diff --git a/cmd/lotus-miner/actor.go b/cmd/lotus-miner/actor.go index 6d76cc07f..1ff613fc1 100644 --- a/cmd/lotus-miner/actor.go +++ b/cmd/lotus-miner/actor.go @@ -1,38 +1,20 @@ package main import ( - "bytes" "fmt" "os" - "strconv" "strings" "github.com/fatih/color" - "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" - "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" - rlepluslazy "github.com/filecoin-project/go-bitfield/rle" - "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/network" - "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" + lapi "github.com/filecoin-project/lotus/api" builtin2 "github.com/filecoin-project/lotus/chain/actors/builtin" - 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/cli/spcli" "github.com/filecoin-project/lotus/lib/tablewriter" ) @@ -40,399 +22,37 @@ var actorCmd = &cli.Command{ Name: "actor", Usage: "manipulate the miner actor", Subcommands: []*cli.Command{ - actorSetAddrsCmd, - actorWithdrawCmd, - actorRepayDebtCmd, - actorSetPeeridCmd, - actorSetOwnerCmd, - actorControl, - actorProposeChangeWorker, - actorConfirmChangeWorker, - actorCompactAllocatedCmd, - actorProposeChangeBeneficiary, - actorConfirmChangeBeneficiary, + spcli.ActorSetAddrsCmd(LMActorGetter), + spcli.ActorWithdrawCmd(LMActorGetter), + spcli.ActorRepayDebtCmd(LMActorGetter), + spcli.ActorSetPeeridCmd(LMActorGetter), + spcli.ActorSetOwnerCmd(LMConfigOrActorGetter), + spcli.ActorControlCmd(LMConfigOrActorGetter, actorControlListCmd), + spcli.ActorProposeChangeWorkerCmd(LMActorGetter), + spcli.ActorConfirmChangeWorkerCmd(LMActorGetter), + spcli.ActorCompactAllocatedCmd(LMActorGetter), + spcli.ActorProposeChangeBeneficiaryCmd(LMActorGetter), + spcli.ActorConfirmChangeBeneficiaryCmd(LMConfigOrActorGetter), }, } -var actorSetAddrsCmd = &cli.Command{ - Name: "set-addresses", - Aliases: []string{"set-addrs"}, - Usage: "set addresses that your miner can be publicly dialed on", - ArgsUsage: "", - 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, - }, - &cli.BoolFlag{ - Name: "unset", - Usage: "unset address", - Value: false, - }, - }, - Action: func(cctx *cli.Context) error { - args := cctx.Args().Slice() - unset := cctx.Bool("unset") - if len(args) == 0 && !unset { - return cli.ShowSubcommandHelp(cctx) - } - if len(args) > 0 && unset { - return fmt.Errorf("unset can only be used with no arguments") - } - - 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 - 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()) - } - - 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) - } - - 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, - Method: builtin.MethodsMiner.ChangeMultiaddrs, - Params: params, - }, nil) - if err != nil { - return err - } - - fmt.Printf("Requested multiaddrs change in message %s\n", smsg.Cid()) - return nil - - }, +func LMConfigOrActorGetter(cctx *cli.Context) (address.Address, error) { + ctx := lcli.ReqContext(cctx) + return getActorAddress(ctx, cctx) } -var actorSetPeeridCmd = &cli.Command{ - Name: "set-peer-id", - Usage: "set the peer id of your miner", - ArgsUsage: "", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "gas-limit", - Usage: "set gas limit", - Value: 0, - }, - }, - Action: func(cctx *cli.Context) error { - minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer closer() +func getControlAddresses(cctx *cli.Context, actor address.Address) (lapi.AddressConfig, error) { + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return lapi.AddressConfig{}, 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)) - if err != nil { - return fmt.Errorf("failed to parse input as a peerId: %w", err) - } - - maddr, err := minerApi.ActorAddress(ctx) - if err != nil { - return err - } - - minfo, err := api.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - params, err := actors.SerializeParams(&miner.ChangePeerIDParams{NewID: abi.PeerID(pid)}) - 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, - Method: builtin.MethodsMiner.ChangePeerID, - Params: params, - }, nil) - if err != nil { - return err - } - - fmt.Printf("Requested peerid change in message %s\n", smsg.Cid()) - return nil - - }, + ctx := lcli.ReqContext(cctx) + return minerApi.ActorAddressConfig(ctx) } -var actorWithdrawCmd = &cli.Command{ - Name: "withdraw", - Usage: "withdraw available balance to beneficiary", - 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", - }, - }, - Action: func(cctx *cli.Context) error { - amount := abi.NewTokenAmount(0) - - 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) - } - - 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 res cid.Cid - if cctx.IsSet("beneficiary") { - res, err = minerApi.BeneficiaryWithdrawBalance(ctx, amount) - } else { - 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) - - // wait for it to get mined into a block - wait, err := api.StateWaitMsg(ctx, res, uint64(cctx.Int("confidence"))) - if err != nil { - return xerrors.Errorf("Timeout waiting for withdrawal message %s", res) - } - - if wait.Receipt.ExitCode.IsError() { - return xerrors.Errorf("Failed to execute withdrawal message %s: %w", wait.Message, wait.Receipt.ExitCode.Error()) - } - - nv, err := api.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 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 { - 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) - - 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))) - - 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 - } - - 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, - 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{ - Name: "control", - Usage: "Manage control addresses", - Subcommands: []*cli.Command{ - actorControlList, - actorControlSet, - }, -} - -var actorControlList = &cli.Command{ +var actorControlListCmd = &cli.Command{ Name: "list", Usage: "Get currently set control addresses", Flags: []cli.Flag{ @@ -441,12 +61,6 @@ var actorControlList = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer closer() - api, acloser, err := lcli.GetFullNodeAPIV1(cctx) if err != nil { return err @@ -455,7 +69,7 @@ var actorControlList = &cli.Command{ ctx := lcli.ReqContext(cctx) - maddr, err := getActorAddress(ctx, cctx) + maddr, err := LMActorOrEnvGetter(cctx) if err != nil { return err } @@ -473,11 +87,10 @@ var actorControlList = &cli.Command{ tablewriter.Col("balance"), ) - ac, err := minerApi.ActorAddressConfig(ctx) + ac, err := getControlAddresses(cctx, maddr) if err != nil { return err } - commit := map[address.Address]struct{}{} precommit := map[address.Address]struct{}{} terminate := map[address.Address]struct{}{} @@ -600,853 +213,3 @@ var actorControlList = &cli.Command{ return tw.Flush(os.Stdout) }, } - -var actorControlSet = &cli.Command{ - Name: "set", - Usage: "Set control address(-es)", - 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 { - 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) - - 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 - } - - 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{ - From: mi.Owner, - To: maddr, - Method: builtin.MethodsMiner.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 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 - } - - 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, - 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 - } - - fmt.Println("message succeeded!") - - return nil - }, -} - -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") - } - - 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) - - 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 - } - - maddr, err := minerApi.ActorAddress(ctx) - 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 - } - - cwp := &miner.ChangeWorkerAddressParams{ - 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, - Method: builtin.MethodsMiner.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 := 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") - } - - 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) - } - - 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) - - 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") { - 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) - } - - if updatedMinerInfo.PendingBeneficiaryTerm == nil && updatedMinerInfo.Beneficiary == newAddr { - fmt.Println("Beneficiary address successfully changed") - } else { - fmt.Println("Beneficiary address change awaiting additional confirmations") - } - - return nil - }, -} - -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") - } - - 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) - - 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 - } - - maddr, err := minerApi.ActorAddress(ctx) - 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") - return nil - } - - smsg, err := api.MpoolPushMessage(ctx, &types.Message{ - From: mi.Owner, - To: maddr, - Method: builtin.MethodsMiner.ConfirmChangeWorkerAddress, - Value: big.Zero(), - }, 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 err - } - - // check it executed successfully - if wait.Receipt.ExitCode.IsError() { - 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")) { - 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") { - 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() { - 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 - } - - 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 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") - } - - 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) - - 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))) - - 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 { - 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 - } - - 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, - 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 - }, -} - -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 -} diff --git a/cmd/lotus-miner/actor_test.go b/cmd/lotus-miner/actor_test.go index dfb452213..5f9e923e6 100644 --- a/cmd/lotus-miner/actor_test.go +++ b/cmd/lotus-miner/actor_test.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/cli/spcli" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/node/repo" ) @@ -67,7 +68,7 @@ func TestWorkerKeyChange(t *testing.T) { // Initialize wallet. kit.SendFunds(ctx, t, client1, newKey, abi.NewTokenAmount(0)) - require.NoError(t, run(actorProposeChangeWorker, "--really-do-it", newKey.String())) + require.NoError(t, run(spcli.ActorProposeChangeWorkerCmd(LMActorGetter), "--really-do-it", newKey.String())) result := output.String() output.Reset() @@ -82,12 +83,12 @@ func TestWorkerKeyChange(t *testing.T) { require.NotZero(t, targetEpoch) // Too early. - require.Error(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) + require.Error(t, run(spcli.ActorConfirmChangeWorkerCmd(LMActorGetter), "--really-do-it", newKey.String())) output.Reset() client1.WaitTillChain(ctx, kit.HeightAtLeast(abi.ChainEpoch(targetEpoch))) - require.NoError(t, run(actorConfirmChangeWorker, "--really-do-it", newKey.String())) + require.NoError(t, run(spcli.ActorConfirmChangeWorkerCmd(LMActorGetter), "--really-do-it", newKey.String())) output.Reset() head, err := client1.ChainHead(ctx) diff --git a/cmd/lotus-miner/info.go b/cmd/lotus-miner/info.go index 6d8ade340..52b230daa 100644 --- a/cmd/lotus-miner/info.go +++ b/cmd/lotus-miner/info.go @@ -29,6 +29,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/filecoin-project/lotus/journal/alerting" sealing "github.com/filecoin-project/lotus/storage/pipeline" @@ -369,94 +370,6 @@ func handleMiningInfo(ctx context.Context, cctx *cli.Context, fullapi v1api.Full return nil } -type stateMeta struct { - i int - col color.Attribute - state sealing.SectorState -} - -var stateOrder = map[sealing.SectorState]stateMeta{} -var stateList = []stateMeta{ - {col: 39, state: "Total"}, - {col: color.FgGreen, state: sealing.Proving}, - {col: color.FgGreen, state: sealing.Available}, - {col: color.FgGreen, state: sealing.UpdateActivating}, - - {col: color.FgMagenta, state: sealing.ReceiveSector}, - - {col: color.FgBlue, state: sealing.Empty}, - {col: color.FgBlue, state: sealing.WaitDeals}, - {col: color.FgBlue, state: sealing.AddPiece}, - {col: color.FgBlue, state: sealing.SnapDealsWaitDeals}, - {col: color.FgBlue, state: sealing.SnapDealsAddPiece}, - - {col: color.FgRed, state: sealing.UndefinedSectorState}, - {col: color.FgYellow, state: sealing.Packing}, - {col: color.FgYellow, state: sealing.GetTicket}, - {col: color.FgYellow, state: sealing.PreCommit1}, - {col: color.FgYellow, state: sealing.PreCommit2}, - {col: color.FgYellow, state: sealing.PreCommitting}, - {col: color.FgYellow, state: sealing.PreCommitWait}, - {col: color.FgYellow, state: sealing.SubmitPreCommitBatch}, - {col: color.FgYellow, state: sealing.PreCommitBatchWait}, - {col: color.FgYellow, state: sealing.WaitSeed}, - {col: color.FgYellow, state: sealing.Committing}, - {col: color.FgYellow, state: sealing.CommitFinalize}, - {col: color.FgYellow, state: sealing.SubmitCommit}, - {col: color.FgYellow, state: sealing.CommitWait}, - {col: color.FgYellow, state: sealing.SubmitCommitAggregate}, - {col: color.FgYellow, state: sealing.CommitAggregateWait}, - {col: color.FgYellow, state: sealing.FinalizeSector}, - {col: color.FgYellow, state: sealing.SnapDealsPacking}, - {col: color.FgYellow, state: sealing.UpdateReplica}, - {col: color.FgYellow, state: sealing.ProveReplicaUpdate}, - {col: color.FgYellow, state: sealing.SubmitReplicaUpdate}, - {col: color.FgYellow, state: sealing.ReplicaUpdateWait}, - {col: color.FgYellow, state: sealing.WaitMutable}, - {col: color.FgYellow, state: sealing.FinalizeReplicaUpdate}, - {col: color.FgYellow, state: sealing.ReleaseSectorKey}, - - {col: color.FgCyan, state: sealing.Terminating}, - {col: color.FgCyan, state: sealing.TerminateWait}, - {col: color.FgCyan, state: sealing.TerminateFinality}, - {col: color.FgCyan, state: sealing.TerminateFailed}, - {col: color.FgCyan, state: sealing.Removing}, - {col: color.FgCyan, state: sealing.Removed}, - {col: color.FgCyan, state: sealing.AbortUpgrade}, - - {col: color.FgRed, state: sealing.FailedUnrecoverable}, - {col: color.FgRed, state: sealing.AddPieceFailed}, - {col: color.FgRed, state: sealing.SealPreCommit1Failed}, - {col: color.FgRed, state: sealing.SealPreCommit2Failed}, - {col: color.FgRed, state: sealing.PreCommitFailed}, - {col: color.FgRed, state: sealing.ComputeProofFailed}, - {col: color.FgRed, state: sealing.RemoteCommitFailed}, - {col: color.FgRed, state: sealing.CommitFailed}, - {col: color.FgRed, state: sealing.CommitFinalizeFailed}, - {col: color.FgRed, state: sealing.PackingFailed}, - {col: color.FgRed, state: sealing.FinalizeFailed}, - {col: color.FgRed, state: sealing.Faulty}, - {col: color.FgRed, state: sealing.FaultReported}, - {col: color.FgRed, state: sealing.FaultedFinal}, - {col: color.FgRed, state: sealing.RemoveFailed}, - {col: color.FgRed, state: sealing.DealsExpired}, - {col: color.FgRed, state: sealing.RecoverDealIDs}, - {col: color.FgRed, state: sealing.SnapDealsAddPieceFailed}, - {col: color.FgRed, state: sealing.SnapDealsDealsExpired}, - {col: color.FgRed, state: sealing.ReplicaUpdateFailed}, - {col: color.FgRed, state: sealing.ReleaseSectorKeyFailed}, - {col: color.FgRed, state: sealing.FinalizeReplicaUpdateFailed}, -} - -func init() { - for i, state := range stateList { - stateOrder[state.state] = stateMeta{ - i: i, - col: state.col, - } - } -} - func sectorsInfo(ctx context.Context, mapi api.StorageMiner) error { summary, err := mapi.SectorsSummary(ctx) if err != nil { @@ -471,17 +384,17 @@ func sectorsInfo(ctx context.Context, mapi api.StorageMiner) error { } buckets["Total"] = total - var sorted []stateMeta + var sorted []spcli.StateMeta for state, i := range buckets { - sorted = append(sorted, stateMeta{i: i, state: state}) + sorted = append(sorted, spcli.StateMeta{I: i, State: state}) } sort.Slice(sorted, func(i, j int) bool { - return stateOrder[sorted[i].state].i < stateOrder[sorted[j].state].i + return spcli.StateOrder[sorted[i].State].I < spcli.StateOrder[sorted[j].State].I }) for _, s := range sorted { - _, _ = color.New(stateOrder[s.state].col).Printf("\t%s: %d\n", s.state, s.i) + _, _ = color.New(spcli.StateOrder[s.State].Col).Printf("\t%s: %d\n", s.State, s.I) } return nil diff --git a/cmd/lotus-miner/info_all.go b/cmd/lotus-miner/info_all.go index 2cf07385c..5b83467a2 100644 --- a/cmd/lotus-miner/info_all.go +++ b/cmd/lotus-miner/info_all.go @@ -8,6 +8,7 @@ import ( "github.com/urfave/cli/v2" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" ) var _test = false @@ -82,17 +83,17 @@ var infoAllCmd = &cli.Command{ } fmt.Println("\n#: Proving Info") - if err := provingInfoCmd.Action(cctx); err != nil { + if err := spcli.ProvingInfoCmd(LMActorOrEnvGetter).Action(cctx); err != nil { fmt.Println("ERROR: ", err) } fmt.Println("\n#: Proving Deadlines") - if err := provingDeadlinesCmd.Action(cctx); err != nil { + if err := spcli.ProvingDeadlinesCmd(LMActorOrEnvGetter).Action(cctx); err != nil { fmt.Println("ERROR: ", err) } fmt.Println("\n#: Proving Faults") - if err := provingFaultsCmd.Action(cctx); err != nil { + if err := spcli.ProvingFaultsCmd(LMActorOrEnvGetter).Action(cctx); err != nil { fmt.Println("ERROR: ", err) } @@ -237,7 +238,7 @@ var infoAllCmd = &cli.Command{ fmt.Printf("\n##: Sector %d Status\n", s) fs := &flag.FlagSet{} - for _, f := range sectorsStatusCmd.Flags { + for _, f := range spcli.SectorsStatusCmd(LMActorOrEnvGetter, getOnDiskInfo).Flags { if err := f.Apply(fs); err != nil { fmt.Println("ERROR: ", err) } @@ -246,7 +247,7 @@ var infoAllCmd = &cli.Command{ fmt.Println("ERROR: ", err) } - if err := sectorsStatusCmd.Action(cli.NewContext(cctx.App, fs, cctx)); err != nil { + if err := spcli.SectorsStatusCmd(LMActorOrEnvGetter, getOnDiskInfo).Action(cli.NewContext(cctx.App, fs, cctx)); err != nil { fmt.Println("ERROR: ", err) } diff --git a/cmd/lotus-miner/main.go b/cmd/lotus-miner/main.go index 911e98e26..1fc7abfa8 100644 --- a/cmd/lotus-miner/main.go +++ b/cmd/lotus-miner/main.go @@ -197,3 +197,17 @@ func setHidden(cmd *cli.Command) *cli.Command { cmd.Hidden = true return cmd } + +func LMActorOrEnvGetter(cctx *cli.Context) (address.Address, error) { + return getActorAddress(cctx.Context, cctx) +} + +func LMActorGetter(cctx *cli.Context) (address.Address, error) { + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return address.Undef, err + } + defer closer() + + return minerApi.ActorAddress(cctx.Context) +} diff --git a/cmd/lotus-miner/precommits-info.go b/cmd/lotus-miner/precommits-info.go deleted file mode 100644 index 3f9e8c927..000000000 --- a/cmd/lotus-miner/precommits-info.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "fmt" - "sort" - - cbor "github.com/ipfs/go-ipld-cbor" - "github.com/urfave/cli/v2" - - "github.com/filecoin-project/specs-actors/v7/actors/util/adt" - - "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/types" - lcli "github.com/filecoin-project/lotus/cli" -) - -var sectorPreCommitsCmd = &cli.Command{ - Name: "precommits", - Usage: "Print on-chain precommit info", - Action: func(cctx *cli.Context) error { - ctx := lcli.ReqContext(cctx) - mapi, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - mact, err := mapi.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - store := adt.WrapStore(ctx, cbor.NewCborStore(blockstore.NewAPIBlockstore(mapi))) - mst, err := miner.Load(store, mact) - if err != nil { - return err - } - preCommitSector := make([]miner.SectorPreCommitOnChainInfo, 0) - err = mst.ForEachPrecommittedSector(func(info miner.SectorPreCommitOnChainInfo) error { - preCommitSector = append(preCommitSector, info) - return err - }) - less := func(i, j int) bool { - return preCommitSector[i].Info.SectorNumber <= preCommitSector[j].Info.SectorNumber - } - sort.Slice(preCommitSector, less) - for _, info := range preCommitSector { - fmt.Printf("%s: %s\n", info.Info.SectorNumber, info.PreCommitEpoch) - } - - return nil - }, -} diff --git a/cmd/lotus-miner/proving.go b/cmd/lotus-miner/proving.go index 575dded5a..9048da8e2 100644 --- a/cmd/lotus-miner/proving.go +++ b/cmd/lotus-miner/proving.go @@ -1,12 +1,10 @@ package main import ( - "bytes" "encoding/json" "fmt" "os" "strconv" - "strings" "sync" "text/tabwriter" "time" @@ -17,18 +15,13 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/dline" "github.com/filecoin-project/go-state-types/proof" - "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" - cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/cli/spcli" "github.com/filecoin-project/lotus/storage/sealer/storiface" ) @@ -36,10 +29,10 @@ var provingCmd = &cli.Command{ Name: "proving", Usage: "View proving information", Subcommands: []*cli.Command{ - provingInfoCmd, - provingDeadlinesCmd, - provingDeadlineInfoCmd, - provingFaultsCmd, + spcli.ProvingInfoCmd(LMActorOrEnvGetter), + spcli.ProvingDeadlinesCmd(LMActorOrEnvGetter), + spcli.ProvingDeadlineInfoCmd(LMActorOrEnvGetter), + spcli.ProvingFaultsCmd(LMActorOrEnvGetter), provingCheckProvableCmd, workersCmd(false), provingComputeCmd, @@ -47,422 +40,6 @@ var provingCmd = &cli.Command{ }, } -var provingFaultsCmd = &cli.Command{ - Name: "faults", - Usage: "View the currently known proving faulty sectors information", - Action: func(cctx *cli.Context) error { - api, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(api)) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - mact, err := api.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - mas, err := miner.Load(stor, mact) - if err != nil { - return err - } - - fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) - - tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - _, _ = fmt.Fprintln(tw, "deadline\tpartition\tsectors") - err = mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { - return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { - faults, err := part.FaultySectors() - if err != nil { - return err - } - return faults.ForEach(func(num uint64) error { - _, _ = fmt.Fprintf(tw, "%d\t%d\t%d\n", dlIdx, partIdx, num) - return nil - }) - }) - }) - if err != nil { - return err - } - return tw.Flush() - }, -} - -var provingInfoCmd = &cli.Command{ - Name: "info", - Usage: "View current state information", - Action: func(cctx *cli.Context) error { - api, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - head, err := api.ChainHead(ctx) - if err != nil { - return xerrors.Errorf("getting chain head: %w", err) - } - - mact, err := api.StateGetActor(ctx, maddr, head.Key()) - if err != nil { - return err - } - - stor := store.ActorStore(ctx, blockstore.NewAPIBlockstore(api)) - - mas, err := miner.Load(stor, mact) - if err != nil { - return err - } - - cd, err := api.StateMinerProvingDeadline(ctx, maddr, head.Key()) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) - - proving := uint64(0) - faults := uint64(0) - recovering := uint64(0) - curDeadlineSectors := uint64(0) - - if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { - return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { - if bf, err := part.LiveSectors(); err != nil { - return err - } else if count, err := bf.Count(); err != nil { - return err - } else { - proving += count - if dlIdx == cd.Index { - curDeadlineSectors += count - } - } - - if bf, err := part.FaultySectors(); err != nil { - return err - } else if count, err := bf.Count(); err != nil { - return err - } else { - faults += count - } - - if bf, err := part.RecoveringSectors(); err != nil { - return err - } else if count, err := bf.Count(); err != nil { - return err - } else { - recovering += count - } - - return nil - }) - }); err != nil { - return xerrors.Errorf("walking miner deadlines and partitions: %w", err) - } - - var faultPerc float64 - if proving > 0 { - faultPerc = float64(faults * 100 / proving) - } - - fmt.Printf("Current Epoch: %d\n", cd.CurrentEpoch) - - fmt.Printf("Proving Period Boundary: %d\n", cd.PeriodStart%cd.WPoStProvingPeriod) - fmt.Printf("Proving Period Start: %s\n", cliutil.EpochTimeTs(cd.CurrentEpoch, cd.PeriodStart, head)) - fmt.Printf("Next Period Start: %s\n\n", cliutil.EpochTimeTs(cd.CurrentEpoch, cd.PeriodStart+cd.WPoStProvingPeriod, head)) - - fmt.Printf("Faults: %d (%.2f%%)\n", faults, faultPerc) - fmt.Printf("Recovering: %d\n", recovering) - - fmt.Printf("Deadline Index: %d\n", cd.Index) - fmt.Printf("Deadline Sectors: %d\n", curDeadlineSectors) - fmt.Printf("Deadline Open: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Open)) - fmt.Printf("Deadline Close: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Close)) - fmt.Printf("Deadline Challenge: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.Challenge)) - fmt.Printf("Deadline FaultCutoff: %s\n", cliutil.EpochTime(cd.CurrentEpoch, cd.FaultCutoff)) - return nil - }, -} - -var provingDeadlinesCmd = &cli.Command{ - Name: "deadlines", - Usage: "View the current proving period deadlines information", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "all", - Usage: "Count all sectors (only live sectors are counted by default)", - Aliases: []string{"a"}, - }, - }, - Action: func(cctx *cli.Context) error { - api, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - deadlines, err := api.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting deadlines: %w", err) - } - - di, err := api.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting deadlines: %w", err) - } - - head, err := api.ChainHead(ctx) - if err != nil { - return err - } - - fmt.Printf("Miner: %s\n", color.BlueString("%s", maddr)) - - tw := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0) - _, _ = fmt.Fprintln(tw, "deadline\topen\tpartitions\tsectors (faults)\tproven partitions") - - for dlIdx, deadline := range deadlines { - partitions, err := api.StateMinerPartitions(ctx, maddr, uint64(dlIdx), types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting partitions for deadline %d: %w", dlIdx, err) - } - - provenPartitions, err := deadline.PostSubmissions.Count() - if err != nil { - return err - } - - sectors := uint64(0) - faults := uint64(0) - var partitionCount int - - for _, partition := range partitions { - if !cctx.Bool("all") { - sc, err := partition.LiveSectors.Count() - if err != nil { - return err - } - - if sc > 0 { - partitionCount++ - } - - sectors += sc - } else { - sc, err := partition.AllSectors.Count() - if err != nil { - return err - } - - partitionCount++ - sectors += sc - } - - fc, err := partition.FaultySectors.Count() - if err != nil { - return err - } - - faults += fc - } - - var cur string - if di.Index == uint64(dlIdx) { - cur += "\t(current)" - } - - _, _ = fmt.Fprintf(tw, "%d\t%s\t%d\t%d (%d)\t%d%s\n", dlIdx, deadlineOpenTime(head, uint64(dlIdx), di), - partitionCount, sectors, faults, provenPartitions, cur) - } - - return tw.Flush() - }, -} - -func deadlineOpenTime(ts *types.TipSet, dlIdx uint64, di *dline.Info) string { - gapIdx := dlIdx - di.Index - gapHeight := uint64(di.WPoStProvingPeriod) / di.WPoStPeriodDeadlines * gapIdx - - openHeight := di.Open + abi.ChainEpoch(gapHeight) - genesisBlockTimestamp := ts.MinTimestamp() - uint64(ts.Height())*build.BlockDelaySecs - - return time.Unix(int64(genesisBlockTimestamp+build.BlockDelaySecs*uint64(openHeight)), 0).Format(time.TimeOnly) -} - -var provingDeadlineInfoCmd = &cli.Command{ - Name: "deadline", - Usage: "View the current proving period deadline information by its index", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "sector-nums", - Aliases: []string{"n"}, - Usage: "Print sector/fault numbers belonging to this deadline", - }, - &cli.BoolFlag{ - Name: "bitfield", - Aliases: []string{"b"}, - Usage: "Print partition bitfield stats", - }, - }, - ArgsUsage: "", - Action: func(cctx *cli.Context) error { - - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - dlIdx, err := strconv.ParseUint(cctx.Args().Get(0), 10, 64) - if err != nil { - return xerrors.Errorf("could not parse deadline index: %w", err) - } - - api, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - deadlines, err := api.StateMinerDeadlines(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting deadlines: %w", err) - } - - di, err := api.StateMinerProvingDeadline(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting deadlines: %w", err) - } - - partitions, err := api.StateMinerPartitions(ctx, maddr, dlIdx, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting partitions for deadline %d: %w", dlIdx, err) - } - - head, err := api.ChainHead(ctx) - if err != nil { - return err - } - - provenPartitions, err := deadlines[dlIdx].PostSubmissions.Count() - if err != nil { - return err - } - - fmt.Printf("Deadline Index: %d\n", dlIdx) - fmt.Printf("Deadline Open: %s\n", deadlineOpenTime(head, dlIdx, di)) - fmt.Printf("Partitions: %d\n", len(partitions)) - fmt.Printf("Proven Partitions: %d\n", provenPartitions) - fmt.Printf("Current: %t\n\n", di.Index == dlIdx) - - for pIdx, partition := range partitions { - fmt.Printf("Partition Index: %d\n", pIdx) - - printStats := func(bf bitfield.BitField, name string) error { - count, err := bf.Count() - if err != nil { - return err - } - - rit, err := bf.RunIterator() - if err != nil { - return err - } - - if cctx.Bool("bitfield") { - var ones, zeros, oneRuns, zeroRuns, invalid uint64 - for rit.HasNext() { - r, err := rit.NextRun() - if err != nil { - return xerrors.Errorf("next run: %w", err) - } - if !r.Valid() { - invalid++ - } - if r.Val { - ones += r.Len - oneRuns++ - } else { - zeros += r.Len - zeroRuns++ - } - } - - var buf bytes.Buffer - if err := bf.MarshalCBOR(&buf); err != nil { - return err - } - sz := len(buf.Bytes()) - szstr := types.SizeStr(types.NewInt(uint64(sz))) - - fmt.Printf("\t%s Sectors:%s%d (bitfield - runs %d+%d=%d - %d 0s %d 1s - %d inv - %s %dB)\n", name, strings.Repeat(" ", 18-len(name)), count, zeroRuns, oneRuns, zeroRuns+oneRuns, zeros, ones, invalid, szstr, sz) - } else { - fmt.Printf("\t%s Sectors:%s%d\n", name, strings.Repeat(" ", 18-len(name)), count) - } - - if cctx.Bool("sector-nums") { - nums, err := bf.All(count) - if err != nil { - return err - } - fmt.Printf("\t%s Sector Numbers:%s%v\n", name, strings.Repeat(" ", 12-len(name)), nums) - } - - return nil - } - - if err := printStats(partition.AllSectors, "All"); err != nil { - return err - } - if err := printStats(partition.LiveSectors, "Live"); err != nil { - return err - } - if err := printStats(partition.ActiveSectors, "Active"); err != nil { - return err - } - if err := printStats(partition.FaultySectors, "Faulty"); err != nil { - return err - } - if err := printStats(partition.RecoveringSectors, "Recovering"); err != nil { - return err - } - } - return nil - }, -} - var provingCheckProvableCmd = &cli.Command{ Name: "check", Usage: "Check sectors provable", diff --git a/cmd/lotus-miner/sectors.go b/cmd/lotus-miner/sectors.go index 3e4439eb8..a3ffb8335 100644 --- a/cmd/lotus-miner/sectors.go +++ b/cmd/lotus-miner/sectors.go @@ -3,10 +3,7 @@ package main import ( "bufio" "encoding/csv" - "encoding/json" - "errors" "fmt" - "math" "os" "sort" "strconv" @@ -23,18 +20,16 @@ import ( "github.com/filecoin-project/go-bitfield" "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/network" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" - "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" - "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/filecoin-project/lotus/lib/result" "github.com/filecoin-project/lotus/lib/strle" @@ -48,16 +43,16 @@ var sectorsCmd = &cli.Command{ Name: "sectors", Usage: "interact with sector store", Subcommands: []*cli.Command{ - sectorsStatusCmd, + spcli.SectorsStatusCmd(LMActorOrEnvGetter, getOnDiskInfo), sectorsListCmd, sectorsRefsCmd, sectorsUpdateCmd, sectorsPledgeCmd, sectorsNumbersCmd, - sectorPreCommitsCmd, - sectorsCheckExpireCmd, + spcli.SectorPreCommitsCmd(LMActorOrEnvGetter), + spcli.SectorsCheckExpireCmd(LMActorOrEnvGetter), sectorsExpiredCmd, - sectorsExtendCmd, + spcli.SectorsExtendCmd(LMActorOrEnvGetter), sectorsTerminateCmd, sectorsRemoveCmd, sectorsSnapUpCmd, @@ -67,11 +62,20 @@ var sectorsCmd = &cli.Command{ sectorsCapacityCollateralCmd, sectorsBatching, sectorsRefreshPieceMatchingCmd, - sectorsCompactPartitionsCmd, + spcli.SectorsCompactPartitionsCmd(LMActorOrEnvGetter), sectorsUnsealCmd, }, } +func getOnDiskInfo(cctx *cli.Context, id abi.SectorNumber, onChainInfo bool) (api.SectorInfo, error) { + minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return api.SectorInfo{}, err + } + defer closer() + return minerApi.SectorsStatus(cctx.Context, id, onChainInfo) +} + var sectorsPledgeCmd = &cli.Command{ Name: "pledge", Usage: "store random data in a sector", @@ -94,187 +98,6 @@ var sectorsPledgeCmd = &cli.Command{ }, } -var sectorsStatusCmd = &cli.Command{ - Name: "status", - Usage: "Get the seal status of a sector by its number", - ArgsUsage: "", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "log", - Usage: "display event log", - Aliases: []string{"l"}, - }, - &cli.BoolFlag{ - Name: "on-chain-info", - Usage: "show sector on chain info", - Aliases: []string{"c"}, - }, - &cli.BoolFlag{ - Name: "partition-info", - Usage: "show partition related info", - Aliases: []string{"p"}, - }, - &cli.BoolFlag{ - Name: "proof", - Usage: "print snark proof bytes as hex", - }, - }, - Action: func(cctx *cli.Context) error { - minerApi, closer, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer closer() - ctx := lcli.ReqContext(cctx) - - if cctx.NArg() != 1 { - return lcli.IncorrectNumArgs(cctx) - } - - id, err := strconv.ParseUint(cctx.Args().First(), 10, 64) - if err != nil { - return err - } - - onChainInfo := cctx.Bool("on-chain-info") - status, err := minerApi.SectorsStatus(ctx, abi.SectorNumber(id), onChainInfo) - if err != nil { - return err - } - - fmt.Printf("SectorID:\t%d\n", status.SectorID) - fmt.Printf("Status:\t\t%s\n", status.State) - fmt.Printf("CIDcommD:\t%s\n", status.CommD) - fmt.Printf("CIDcommR:\t%s\n", status.CommR) - fmt.Printf("Ticket:\t\t%x\n", status.Ticket.Value) - fmt.Printf("TicketH:\t%d\n", status.Ticket.Epoch) - fmt.Printf("Seed:\t\t%x\n", status.Seed.Value) - fmt.Printf("SeedH:\t\t%d\n", status.Seed.Epoch) - fmt.Printf("Precommit:\t%s\n", status.PreCommitMsg) - fmt.Printf("Commit:\t\t%s\n", status.CommitMsg) - if cctx.Bool("proof") { - fmt.Printf("Proof:\t\t%x\n", status.Proof) - } - fmt.Printf("Deals:\t\t%v\n", status.Deals) - fmt.Printf("Retries:\t%d\n", status.Retries) - if status.LastErr != "" { - fmt.Printf("Last Error:\t\t%s\n", status.LastErr) - } - - if onChainInfo { - fmt.Printf("\nSector On Chain Info\n") - fmt.Printf("SealProof:\t\t%x\n", status.SealProof) - fmt.Printf("Activation:\t\t%v\n", status.Activation) - fmt.Printf("Expiration:\t\t%v\n", status.Expiration) - fmt.Printf("DealWeight:\t\t%v\n", status.DealWeight) - fmt.Printf("VerifiedDealWeight:\t\t%v\n", status.VerifiedDealWeight) - fmt.Printf("InitialPledge:\t\t%v\n", types.FIL(status.InitialPledge)) - fmt.Printf("\nExpiration Info\n") - fmt.Printf("OnTime:\t\t%v\n", status.OnTime) - fmt.Printf("Early:\t\t%v\n", status.Early) - } - - if cctx.Bool("partition-info") { - fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer nCloser() - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - mact, err := fullApi.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) - mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) - if err != nil { - return err - } - - errFound := errors.New("found") - if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { - return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { - pas, err := part.AllSectors() - if err != nil { - return err - } - - set, err := pas.IsSet(id) - if err != nil { - return err - } - if set { - fmt.Printf("\nDeadline:\t%d\n", dlIdx) - fmt.Printf("Partition:\t%d\n", partIdx) - - checkIn := func(name string, bg func() (bitfield.BitField, error)) error { - bf, err := bg() - if err != nil { - return err - } - - set, err := bf.IsSet(id) - if err != nil { - return err - } - setstr := "no" - if set { - setstr = "yes" - } - fmt.Printf("%s: \t%s\n", name, setstr) - return nil - } - - if err := checkIn("Unproven", part.UnprovenSectors); err != nil { - return err - } - if err := checkIn("Live", part.LiveSectors); err != nil { - return err - } - if err := checkIn("Active", part.ActiveSectors); err != nil { - return err - } - if err := checkIn("Faulty", part.FaultySectors); err != nil { - return err - } - if err := checkIn("Recovering", part.RecoveringSectors); err != nil { - return err - } - - return errFound - } - - return nil - }) - }); err != errFound { - if err != nil { - return err - } - - fmt.Println("\nNot found in any partition") - } - } - - if cctx.Bool("log") { - fmt.Printf("--------\nEvent Log:\n") - - for i, l := range status.Log { - fmt.Printf("%d.\t%s:\t[%s]\t%s\n", i, time.Unix(int64(l.Timestamp), 0), l.Kind, l.Message) - if l.Trace != "" { - fmt.Printf("\t%s\n", l.Trace) - } - } - } - return nil - }, -} - var sectorsListCmd = &cli.Command{ Name: "list", Usage: "List sectors", @@ -494,7 +317,7 @@ var sectorsListCmd = &cli.Command{ m := map[string]interface{}{ "ID": s, - "State": color.New(stateOrder[sealing.SectorState(st.State)].col).Sprint(st.State), + "State": color.New(spcli.StateOrder[sealing.SectorState(st.State)].Col).Sprint(st.State), "OnChain": yesno(inSSet), "Active": yesno(inASet), } @@ -778,654 +601,6 @@ var sectorsRefsCmd = &cli.Command{ }, } -var sectorsCheckExpireCmd = &cli.Command{ - Name: "check-expire", - Usage: "Inspect expiring sectors", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "cutoff", - Usage: "skip sectors whose current expiration is more than epochs from now, defaults to 60 days", - Value: 172800, - }, - }, - Action: func(cctx *cli.Context) error { - - fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer nCloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - head, err := fullApi.ChainHead(ctx) - if err != nil { - return err - } - currEpoch := head.Height() - - nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return err - } - - sectors, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - n := 0 - for _, s := range sectors { - if s.Expiration-currEpoch <= abi.ChainEpoch(cctx.Int64("cutoff")) { - sectors[n] = s - n++ - } - } - sectors = sectors[:n] - - sort.Slice(sectors, func(i, j int) bool { - if sectors[i].Expiration == sectors[j].Expiration { - return sectors[i].SectorNumber < sectors[j].SectorNumber - } - return sectors[i].Expiration < sectors[j].Expiration - }) - - tw := tablewriter.New( - tablewriter.Col("ID"), - tablewriter.Col("SealProof"), - tablewriter.Col("InitialPledge"), - tablewriter.Col("Activation"), - tablewriter.Col("Expiration"), - tablewriter.Col("MaxExpiration"), - tablewriter.Col("MaxExtendNow")) - - for _, sector := range sectors { - MaxExpiration := sector.Activation + policy.GetSectorMaxLifetime(sector.SealProof, nv) - maxExtension, err := policy.GetMaxSectorExpirationExtension(nv) - if err != nil { - return xerrors.Errorf("failed to get max extension: %w", err) - } - - MaxExtendNow := currEpoch + maxExtension - - if MaxExtendNow > MaxExpiration { - MaxExtendNow = MaxExpiration - } - - tw.Write(map[string]interface{}{ - "ID": sector.SectorNumber, - "SealProof": sector.SealProof, - "InitialPledge": types.FIL(sector.InitialPledge).Short(), - "Activation": cliutil.EpochTime(currEpoch, sector.Activation), - "Expiration": cliutil.EpochTime(currEpoch, sector.Expiration), - "MaxExpiration": cliutil.EpochTime(currEpoch, MaxExpiration), - "MaxExtendNow": cliutil.EpochTime(currEpoch, MaxExtendNow), - }) - } - - return tw.Flush(os.Stdout) - }, -} - -type PseudoExpirationExtension struct { - Deadline uint64 - Partition uint64 - Sectors string - NewExpiration abi.ChainEpoch -} - -type PseudoExtendSectorExpirationParams struct { - Extensions []PseudoExpirationExtension -} - -func NewPseudoExtendParams(p *miner.ExtendSectorExpiration2Params) (*PseudoExtendSectorExpirationParams, error) { - res := PseudoExtendSectorExpirationParams{} - for _, ext := range p.Extensions { - scount, err := ext.Sectors.Count() - if err != nil { - return nil, err - } - - sectors, err := ext.Sectors.All(scount) - if err != nil { - return nil, err - } - - res.Extensions = append(res.Extensions, PseudoExpirationExtension{ - Deadline: ext.Deadline, - Partition: ext.Partition, - Sectors: ArrayToString(sectors), - NewExpiration: ext.NewExpiration, - }) - } - return &res, nil -} - -// ArrayToString Example: {1,3,4,5,8,9} -> "1,3-5,8-9" -func ArrayToString(array []uint64) string { - sort.Slice(array, func(i, j int) bool { - return array[i] < array[j] - }) - - var sarray []string - s := "" - - for i, elm := range array { - if i == 0 { - s = strconv.FormatUint(elm, 10) - continue - } - if elm == array[i-1] { - continue // filter out duplicates - } else if elm == array[i-1]+1 { - s = strings.Split(s, "-")[0] + "-" + strconv.FormatUint(elm, 10) - } else { - sarray = append(sarray, s) - s = strconv.FormatUint(elm, 10) - } - } - - if s != "" { - sarray = append(sarray, s) - } - - return strings.Join(sarray, ",") -} - -func getSectorsFromFile(filePath string) ([]abi.SectorNumber, error) { - file, err := os.Open(filePath) - if err != nil { - return nil, err - } - - scanner := bufio.NewScanner(file) - sectors := make([]abi.SectorNumber, 0) - - for scanner.Scan() { - line := scanner.Text() - - id, err := strconv.ParseUint(line, 10, 64) - if err != nil { - return nil, xerrors.Errorf("could not parse %s as sector id: %s", line, err) - } - - sectors = append(sectors, abi.SectorNumber(id)) - } - - if err = file.Close(); err != nil { - return nil, err - } - - return sectors, nil -} - -func SectorNumsToBitfield(sectors []abi.SectorNumber) bitfield.BitField { - var numbers []uint64 - for _, sector := range sectors { - numbers = append(numbers, uint64(sector)) - } - - return bitfield.NewFromSet(numbers) -} - -var sectorsExtendCmd = &cli.Command{ - Name: "extend", - Usage: "Extend expiring sectors while not exceeding each sector's max life", - ArgsUsage: "", - Flags: []cli.Flag{ - &cli.Int64Flag{ - Name: "from", - Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 120 (1 hour)", - }, - &cli.Int64Flag{ - Name: "to", - Usage: "only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 92160 (32 days)", - }, - &cli.StringFlag{ - Name: "sector-file", - Usage: "provide a file containing one sector number in each line, ignoring above selecting criteria", - }, - &cli.StringFlag{ - Name: "exclude", - Usage: "optionally provide a file containing excluding sectors", - }, - &cli.Int64Flag{ - Name: "extension", - Usage: "try to extend selected sectors by this number of epochs, defaults to 540 days", - Value: 1555200, - }, - &cli.Int64Flag{ - Name: "new-expiration", - Usage: "try to extend selected sectors to this epoch, ignoring extension", - }, - &cli.BoolFlag{ - Name: "only-cc", - Usage: "only extend CC sectors (useful for making sector ready for snap upgrade)", - }, - &cli.BoolFlag{ - Name: "drop-claims", - Usage: "drop claims for sectors that can be extended, but only by dropping some of their verified power claims", - }, - &cli.Int64Flag{ - Name: "tolerance", - Usage: "don't try to extend sectors by fewer than this number of epochs, defaults to 7 days", - Value: 20160, - }, - &cli.StringFlag{ - Name: "max-fee", - Usage: "use up to this amount of FIL for one message. pass this flag to avoid message congestion.", - Value: "0", - }, - &cli.Int64Flag{ - Name: "max-sectors", - Usage: "the maximum number of sectors contained in each message", - }, - &cli.BoolFlag{ - Name: "really-do-it", - Usage: "pass this flag to really extend sectors, otherwise will only print out json representation of parameters", - }, - }, - Action: func(cctx *cli.Context) error { - mf, err := types.ParseFIL(cctx.String("max-fee")) - if err != nil { - return err - } - - spec := &api.MessageSendSpec{MaxFee: abi.TokenAmount(mf)} - - fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer nCloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - head, err := fullApi.ChainHead(ctx) - if err != nil { - return err - } - currEpoch := head.Height() - - nv, err := fullApi.StateNetworkVersion(ctx, types.EmptyTSK) - if err != nil { - return err - } - - activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - activeSectorsInfo := make(map[abi.SectorNumber]*miner.SectorOnChainInfo, len(activeSet)) - for _, info := range activeSet { - activeSectorsInfo[info.SectorNumber] = info - } - - mact, err := fullApi.StateGetActor(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) - adtStore := adt.WrapStore(ctx, cbor.NewCborStore(tbs)) - mas, err := miner.Load(adtStore, mact) - if err != nil { - return err - } - - activeSectorsLocation := make(map[abi.SectorNumber]*miner.SectorLocation, len(activeSet)) - - if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { - return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { - pas, err := part.ActiveSectors() - if err != nil { - return err - } - - return pas.ForEach(func(i uint64) error { - activeSectorsLocation[abi.SectorNumber(i)] = &miner.SectorLocation{ - Deadline: dlIdx, - Partition: partIdx, - } - return nil - }) - }) - }); err != nil { - return err - } - - excludeSet := make(map[abi.SectorNumber]struct{}) - if cctx.IsSet("exclude") { - excludeSectors, err := getSectorsFromFile(cctx.String("exclude")) - if err != nil { - return err - } - - for _, id := range excludeSectors { - excludeSet[id] = struct{}{} - } - } - - var sectors []abi.SectorNumber - if cctx.Args().Present() { - if cctx.IsSet("sector-file") { - return xerrors.Errorf("sector-file specified along with command line params") - } - - for i, s := range cctx.Args().Slice() { - id, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return xerrors.Errorf("could not parse sector %d: %w", i, err) - } - - sectors = append(sectors, abi.SectorNumber(id)) - } - } else if cctx.IsSet("sector-file") { - sectors, err = getSectorsFromFile(cctx.String("sector-file")) - if err != nil { - return err - } - } else { - from := currEpoch + 120 - to := currEpoch + 92160 - - if cctx.IsSet("from") { - from = abi.ChainEpoch(cctx.Int64("from")) - } - - if cctx.IsSet("to") { - to = abi.ChainEpoch(cctx.Int64("to")) - } - - for _, si := range activeSet { - if si.Expiration >= from && si.Expiration <= to { - sectors = append(sectors, si.SectorNumber) - } - } - } - - var sis []*miner.SectorOnChainInfo - for _, id := range sectors { - if _, exclude := excludeSet[id]; exclude { - continue - } - - si, found := activeSectorsInfo[id] - if !found { - return xerrors.Errorf("sector %d is not active", id) - } - if len(si.DealIDs) > 0 && cctx.Bool("only-cc") { - continue - } - - sis = append(sis, si) - } - - withinTolerance := func(a, b abi.ChainEpoch) bool { - diff := a - b - if diff < 0 { - diff = -diff - } - - return diff <= abi.ChainEpoch(cctx.Int64("tolerance")) - } - - extensions := map[miner.SectorLocation]map[abi.ChainEpoch][]abi.SectorNumber{} - for _, si := range sis { - extension := abi.ChainEpoch(cctx.Int64("extension")) - newExp := si.Expiration + extension - - if cctx.IsSet("new-expiration") { - newExp = abi.ChainEpoch(cctx.Int64("new-expiration")) - } - - maxExtension, err := policy.GetMaxSectorExpirationExtension(nv) - if err != nil { - return xerrors.Errorf("failed to get max extension: %w", err) - } - - maxExtendNow := currEpoch + maxExtension - if newExp > maxExtendNow { - newExp = maxExtendNow - } - - maxExp := si.Activation + policy.GetSectorMaxLifetime(si.SealProof, nv) - if newExp > maxExp { - newExp = maxExp - } - - if newExp <= si.Expiration || withinTolerance(newExp, si.Expiration) { - continue - } - - l, found := activeSectorsLocation[si.SectorNumber] - if !found { - return xerrors.Errorf("location for sector %d not found", si.SectorNumber) - } - - es, found := extensions[*l] - if !found { - ne := make(map[abi.ChainEpoch][]abi.SectorNumber) - ne[newExp] = []abi.SectorNumber{si.SectorNumber} - extensions[*l] = ne - } else { - added := false - for exp := range es { - if withinTolerance(newExp, exp) { - es[exp] = append(es[exp], si.SectorNumber) - added = true - break - } - } - - if !added { - es[newExp] = []abi.SectorNumber{si.SectorNumber} - } - } - } - - verifregAct, err := fullApi.StateGetActor(ctx, builtin.VerifiedRegistryActorAddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("failed to lookup verifreg actor: %w", err) - } - - verifregSt, err := verifreg.Load(adtStore, verifregAct) - if err != nil { - return xerrors.Errorf("failed to load verifreg state: %w", err) - } - - claimsMap, err := verifregSt.GetClaims(maddr) - if err != nil { - return xerrors.Errorf("failed to lookup claims for miner: %w", err) - } - - claimIdsBySector, err := verifregSt.GetClaimIdsBySector(maddr) - if err != nil { - return xerrors.Errorf("failed to lookup claim IDs by sector: %w", err) - } - - sectorsMax, err := policy.GetAddressedSectorsMax(nv) - if err != nil { - return err - } - - declMax, err := policy.GetDeclarationsMax(nv) - if err != nil { - return err - } - - addrSectors := sectorsMax - if cctx.Int("max-sectors") != 0 { - addrSectors = cctx.Int("max-sectors") - if addrSectors > sectorsMax { - return xerrors.Errorf("the specified max-sectors exceeds the maximum limit") - } - } - - var params []miner.ExtendSectorExpiration2Params - - p := miner.ExtendSectorExpiration2Params{} - scount := 0 - - for l, exts := range extensions { - for newExp, numbers := range exts { - sectorsWithoutClaimsToExtend := bitfield.New() - var sectorsWithClaims []miner.SectorClaim - for _, sectorNumber := range numbers { - claimIdsToMaintain := make([]verifreg.ClaimId, 0) - claimIdsToDrop := make([]verifreg.ClaimId, 0) - cannotExtendSector := false - claimIds, ok := claimIdsBySector[sectorNumber] - // Nothing to check, add to ccSectors - if !ok { - sectorsWithoutClaimsToExtend.Set(uint64(sectorNumber)) - } else { - for _, claimId := range claimIds { - claim, ok := claimsMap[claimId] - if !ok { - return xerrors.Errorf("failed to find claim for claimId %d", claimId) - } - claimExpiration := claim.TermStart + claim.TermMax - // can be maintained in the extended sector - if claimExpiration > newExp { - claimIdsToMaintain = append(claimIdsToMaintain, claimId) - } else { - sectorInfo, ok := activeSectorsInfo[sectorNumber] - if !ok { - return xerrors.Errorf("failed to find sector in active sector set: %w", err) - } - if !cctx.Bool("drop-claims") || - // FIP-0045 requires the claim minimum duration to have passed - currEpoch <= (claim.TermStart+claim.TermMin) || - // FIP-0045 requires the sector to be in its last 30 days of life - (currEpoch <= sectorInfo.Expiration-builtin.EndOfLifeClaimDropPeriod) { - fmt.Printf("skipping sector %d because claim %d does not live long enough \n", sectorNumber, claimId) - cannotExtendSector = true - break - } - - claimIdsToDrop = append(claimIdsToDrop, claimId) - } - } - if cannotExtendSector { - continue - } - - if len(claimIdsToMaintain)+len(claimIdsToDrop) != 0 { - sectorsWithClaims = append(sectorsWithClaims, miner.SectorClaim{ - SectorNumber: sectorNumber, - MaintainClaims: claimIdsToMaintain, - DropClaims: claimIdsToDrop, - }) - } - } - } - - sectorsWithoutClaimsCount, err := sectorsWithoutClaimsToExtend.Count() - if err != nil { - return xerrors.Errorf("failed to count cc sectors: %w", err) - } - - sectorsInDecl := int(sectorsWithoutClaimsCount) + len(sectorsWithClaims) - scount += sectorsInDecl - - if scount > addrSectors || len(p.Extensions) >= declMax { - params = append(params, p) - p = miner.ExtendSectorExpiration2Params{} - scount = sectorsInDecl - } - - p.Extensions = append(p.Extensions, miner.ExpirationExtension2{ - Deadline: l.Deadline, - Partition: l.Partition, - Sectors: SectorNumsToBitfield(numbers), - SectorsWithClaims: sectorsWithClaims, - NewExpiration: newExp, - }) - - } - } - - // if we have any sectors, then one last append is needed here - if scount != 0 { - params = append(params, p) - } - - if len(params) == 0 { - fmt.Println("nothing to extend") - return nil - } - - mi, err := fullApi.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return xerrors.Errorf("getting miner info: %w", err) - } - - stotal := 0 - - for i := range params { - scount := 0 - for _, ext := range params[i].Extensions { - count, err := ext.Sectors.Count() - if err != nil { - return err - } - scount += int(count) - } - fmt.Printf("Extending %d sectors: ", scount) - stotal += scount - - if !cctx.Bool("really-do-it") { - pp, err := NewPseudoExtendParams(¶ms[i]) - if err != nil { - return err - } - - data, err := json.MarshalIndent(pp, "", " ") - if err != nil { - return err - } - - fmt.Println("\n", string(data)) - continue - } - - sp, aerr := actors.SerializeParams(¶ms[i]) - if aerr != nil { - return xerrors.Errorf("serializing params: %w", err) - } - - smsg, err := fullApi.MpoolPushMessage(ctx, &types.Message{ - From: mi.Worker, - To: maddr, - Method: builtin.MethodsMiner.ExtendSectorExpiration2, - Value: big.Zero(), - Params: sp, - }, spec) - if err != nil { - return xerrors.Errorf("mpool push message: %w", err) - } - - fmt.Println(smsg.Cid()) - } - - fmt.Printf("%d sectors extended\n", stotal) - - return nil - }, -} - var sectorsTerminateCmd = &cli.Command{ Name: "terminate", Usage: "Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector)", @@ -2238,175 +1413,6 @@ func yesno(b bool) string { return color.RedString("NO") } -var sectorsCompactPartitionsCmd = &cli.Command{ - Name: "compact-partitions", - Usage: "removes dead sectors from partitions and reduces the number of partitions used if possible", - Flags: []cli.Flag{ - &cli.Uint64Flag{ - Name: "deadline", - Usage: "the deadline to compact the partitions in", - Required: true, - }, - &cli.Int64SliceFlag{ - Name: "partitions", - Usage: "list of partitions to compact sectors in", - Required: true, - }, - &cli.BoolFlag{ - Name: "really-do-it", - Usage: "Actually send transaction performing the action", - Value: false, - }, - &cli.StringFlag{ - Name: "actor", - Usage: "Specify the address of the miner to run this command", - }, - }, - Action: func(cctx *cli.Context) error { - fullNodeAPI, acloser, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer acloser() - - ctx := lcli.ReqContext(cctx) - - maddr, err := getActorAddress(ctx, cctx) - if err != nil { - return err - } - - minfo, err := fullNodeAPI.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - deadline := cctx.Uint64("deadline") - if deadline > miner.WPoStPeriodDeadlines { - return fmt.Errorf("deadline %d out of range", deadline) - } - - parts := cctx.Int64Slice("partitions") - if len(parts) <= 0 { - return fmt.Errorf("must include at least one partition to compact") - } - fmt.Printf("compacting %d partitions\n", len(parts)) - - var makeMsgForPartitions func(partitionsBf bitfield.BitField) ([]*types.Message, error) - makeMsgForPartitions = func(partitionsBf bitfield.BitField) ([]*types.Message, error) { - params := miner.CompactPartitionsParams{ - Deadline: deadline, - Partitions: partitionsBf, - } - - sp, aerr := actors.SerializeParams(¶ms) - if aerr != nil { - return nil, xerrors.Errorf("serializing params: %w", err) - } - - msg := &types.Message{ - From: minfo.Worker, - To: maddr, - Method: builtin.MethodsMiner.CompactPartitions, - Value: big.Zero(), - Params: sp, - } - - estimatedMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK) - if err != nil && xerrors.Is(err, &api.ErrOutOfGas{}) { - // the message is too big -- split into 2 - partitionsSlice, err := partitionsBf.All(math.MaxUint64) - if err != nil { - return nil, err - } - - partitions1 := bitfield.New() - for i := 0; i < len(partitionsSlice)/2; i++ { - partitions1.Set(uint64(i)) - } - - msgs1, err := makeMsgForPartitions(partitions1) - if err != nil { - return nil, err - } - - // time for the second half - partitions2 := bitfield.New() - for i := len(partitionsSlice) / 2; i < len(partitionsSlice); i++ { - partitions2.Set(uint64(i)) - } - - msgs2, err := makeMsgForPartitions(partitions2) - if err != nil { - return nil, err - } - - return append(msgs1, msgs2...), nil - } else if err != nil { - return nil, err - } - - return []*types.Message{estimatedMsg}, nil - } - - partitions := bitfield.New() - for _, partition := range parts { - partitions.Set(uint64(partition)) - } - - msgs, err := makeMsgForPartitions(partitions) - if err != nil { - return xerrors.Errorf("failed to make messages: %w", err) - } - - // Actually send the messages if really-do-it provided, simulate otherwise - if cctx.Bool("really-do-it") { - smsgs, err := fullNodeAPI.MpoolBatchPushMessage(ctx, msgs, nil) - if err != nil { - return xerrors.Errorf("mpool push: %w", err) - } - - if len(smsgs) == 1 { - fmt.Printf("Requested compact partitions in message %s\n", smsgs[0].Cid()) - } else { - fmt.Printf("Requested compact partitions in %d messages\n\n", len(smsgs)) - for _, v := range smsgs { - fmt.Println(v.Cid()) - } - } - - for _, v := range smsgs { - wait, err := fullNodeAPI.StateWaitMsg(ctx, v.Cid(), 2) - if err != nil { - return err - } - - // check it executed successfully - if wait.Receipt.ExitCode.IsError() { - fmt.Println(cctx.App.Writer, "compact partitions msg %s failed!", v.Cid()) - return err - } - } - - return nil - } - - for i, v := range msgs { - fmt.Printf("total of %d CompactPartitions msgs would be sent\n", len(msgs)) - - estMsg, err := fullNodeAPI.GasEstimateMessageGas(ctx, v, nil, types.EmptyTSK) - if err != nil { - return err - } - - fmt.Printf("msg %d would cost up to %s\n", i+1, types.FIL(estMsg.RequiredFunds())) - } - - return nil - - }, -} - var sectorsNumbersCmd = &cli.Command{ Name: "numbers", Usage: "manage sector number assignments", diff --git a/cmd/lotus-miner/storage.go b/cmd/lotus-miner/storage.go index fdd5b5696..b39fe2bf7 100644 --- a/cmd/lotus-miner/storage.go +++ b/cmd/lotus-miner/storage.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" "github.com/filecoin-project/lotus/lib/tablewriter" sealing "github.com/filecoin-project/lotus/storage/pipeline" "github.com/filecoin-project/lotus/storage/sealer/fsutil" @@ -803,7 +804,7 @@ var storageListSectorsCmd = &cli.Command{ "Storage": color.New(sc1).Sprint(e.storage), "Sector": e.id, "Type": e.ft.String(), - "State": color.New(stateOrder[sealing.SectorState(e.state)].col).Sprint(e.state), + "State": color.New(spcli.StateOrder[sealing.SectorState(e.state)].Col).Sprint(e.state), "Primary": maybeStr(e.primary, color.FgGreen, "primary") + maybeStr(e.copy, color.FgBlue, "copy") + maybeStr(e.main, color.FgRed, "main"), "Path use": maybeStr(e.seal, color.FgMagenta, "seal ") + maybeStr(e.store, color.FgCyan, "store"), "URLs": e.urls, @@ -995,7 +996,7 @@ var storageLocks = &cli.Command{ return xerrors.Errorf("getting sector status(%d): %w", lock.Sector.Number, err) } - lockstr := fmt.Sprintf("%d\t%s\t", lock.Sector.Number, color.New(stateOrder[sealing.SectorState(st.State)].col).Sprint(st.State)) + lockstr := fmt.Sprintf("%d\t%s\t", lock.Sector.Number, color.New(spcli.StateOrder[sealing.SectorState(st.State)].Col).Sprint(st.State)) for i := 0; i < storiface.FileTypes; i++ { if lock.Write[i] > 0 { diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 899e0f290..176f232fe 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -31,6 +31,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" "github.com/filecoin-project/lotus/lib/parmap" "github.com/filecoin-project/lotus/node/repo" "github.com/filecoin-project/lotus/storage/paths" @@ -44,7 +45,7 @@ var sectorsCmd = &cli.Command{ Usage: "Tools for interacting with sectors", Flags: []cli.Flag{}, Subcommands: []*cli.Command{ - terminateSectorCmd, + spcli.TerminateSectorCmd(shedGetActor), terminateSectorPenaltyEstimationCmd, visAllocatedSectorsCmd, dumpRLESectorCmd, @@ -53,138 +54,14 @@ var sectorsCmd = &cli.Command{ }, } -var terminateSectorCmd = &cli.Command{ - Name: "terminate", - Usage: "Forcefully terminate a sector (WARNING: This means losing power and pay a one-time termination penalty(including collateral) for the terminated sector)", - ArgsUsage: "[sectorNum1 sectorNum2 ...]", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "actor", - Usage: "specify the address of miner actor", - }, - &cli.BoolFlag{ - Name: "really-do-it", - Usage: "pass this flag if you know what you are doing", - }, - &cli.StringFlag{ - Name: "from", - Usage: "specify the address to send the terminate message from", - }, - }, - Action: func(cctx *cli.Context) error { - if cctx.NArg() < 1 { - return lcli.ShowHelp(cctx, fmt.Errorf("at least one sector must be specified")) - } +func shedGetActor(cctx *cli.Context) (address.Address, error) { + minerApi, acloser, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return address.Address{}, err + } + defer acloser() - 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) - } - } - - if !cctx.Bool("really-do-it") { - return fmt.Errorf("this is a command for advanced users, only use it if you are sure of what you are doing") - } - - nodeApi, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err - } - defer closer() - - ctx := lcli.ReqContext(cctx) - - if maddr.Empty() { - minerApi, acloser, err := lcli.GetStorageMinerAPI(cctx) - if err != nil { - return err - } - defer acloser() - - maddr, err = minerApi.ActorAddress(ctx) - if err != nil { - return err - } - } - - mi, err := nodeApi.StateMinerInfo(ctx, maddr, types.EmptyTSK) - if err != nil { - return err - } - - terminationDeclarationParams := []miner2.TerminationDeclaration{} - - for _, sn := range cctx.Args().Slice() { - sectorNum, err := strconv.ParseUint(sn, 10, 64) - if err != nil { - return fmt.Errorf("could not parse sector number: %w", err) - } - - sectorbit := bitfield.New() - sectorbit.Set(sectorNum) - - loca, err := nodeApi.StateSectorPartition(ctx, maddr, abi.SectorNumber(sectorNum), types.EmptyTSK) - if err != nil { - return fmt.Errorf("get state sector partition %s", err) - } - - para := miner2.TerminationDeclaration{ - Deadline: loca.Deadline, - Partition: loca.Partition, - Sectors: sectorbit, - } - - terminationDeclarationParams = append(terminationDeclarationParams, para) - } - - terminateSectorParams := &miner2.TerminateSectorsParams{ - Terminations: terminationDeclarationParams, - } - - sp, err := actors.SerializeParams(terminateSectorParams) - if err != nil { - return xerrors.Errorf("serializing params: %w", err) - } - - var fromAddr address.Address - if from := cctx.String("from"); from != "" { - var err error - fromAddr, err = address.NewFromString(from) - if err != nil { - return fmt.Errorf("parsing address %s: %w", from, err) - } - } else { - fromAddr = mi.Worker - } - - smsg, err := nodeApi.MpoolPushMessage(ctx, &types.Message{ - From: fromAddr, - To: maddr, - Method: builtin.MethodsMiner.TerminateSectors, - - Value: big.Zero(), - Params: sp, - }, nil) - if err != nil { - return xerrors.Errorf("mpool push message: %w", err) - } - - fmt.Println("sent termination message:", smsg.Cid()) - - wait, err := nodeApi.StateWaitMsg(ctx, smsg.Cid(), uint64(cctx.Int("confidence"))) - if err != nil { - return err - } - - if wait.Receipt.ExitCode.IsError() { - return fmt.Errorf("terminate sectors message returned exit %d", wait.Receipt.ExitCode) - } - - return nil - }, + return minerApi.ActorAddress(cctx.Context) } func findPenaltyInInternalExecutions(prefix string, trace []types.ExecutionTrace) { diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index 85324e466..fce9a6136 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -13,6 +13,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/clicommands" cliutil "github.com/filecoin-project/lotus/cli/util" "github.com/filecoin-project/lotus/lib/lotuslog" "github.com/filecoin-project/lotus/lib/tracing" @@ -112,7 +113,7 @@ func main() { return nil }, - Commands: append(local, lcli.Commands...), + Commands: append(local, clicommands.Commands...), } app.Setup() diff --git a/cmd/sptool/actor.go b/cmd/sptool/actor.go new file mode 100644 index 000000000..fb0d5e966 --- /dev/null +++ b/cmd/sptool/actor.go @@ -0,0 +1,140 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/fatih/color" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-address" + + builtin2 "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" + "github.com/filecoin-project/lotus/lib/tablewriter" +) + +var actorCmd = &cli.Command{ + Name: "actor", + Usage: "Manage Filecoin Miner Actor Metadata", + Subcommands: []*cli.Command{ + spcli.ActorSetAddrsCmd(SPTActorGetter), + spcli.ActorWithdrawCmd(SPTActorGetter), + spcli.ActorRepayDebtCmd(SPTActorGetter), + spcli.ActorSetPeeridCmd(SPTActorGetter), + spcli.ActorSetOwnerCmd(SPTActorGetter), + spcli.ActorControlCmd(SPTActorGetter, actorControlListCmd(SPTActorGetter)), + spcli.ActorProposeChangeWorkerCmd(SPTActorGetter), + spcli.ActorConfirmChangeWorkerCmd(SPTActorGetter), + spcli.ActorCompactAllocatedCmd(SPTActorGetter), + spcli.ActorProposeChangeBeneficiaryCmd(SPTActorGetter), + spcli.ActorConfirmChangeBeneficiaryCmd(SPTActorGetter), + }, +} + +func actorControlListCmd(getActor spcli.ActorAddressGetter) *cli.Command { + return &cli.Command{ + Name: "list", + Usage: "Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state.", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + }, + }, + Action: func(cctx *cli.Context) error { + api, acloser, err := lcli.GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer acloser() + + ctx := lcli.ReqContext(cctx) + + maddr, err := getActor(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"), + ) + + post := map[address.Address]struct{}{} + + for _, ca := range mi.ControlAddresses { + post[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")) + } + if _, ok := post[a]; ok { + uses = append(uses, color.GreenString("post")) + } + + tw.Write(map[string]interface{}{ + "name": name, + "ID": a, + "key": kstr, + "use": strings.Join(uses, " "), + "balance": bstr, + }) + } + + printKey("owner", mi.Owner) + printKey("worker", mi.Worker) + printKey("beneficiary", mi.Beneficiary) + for i, ca := range mi.ControlAddresses { + printKey(fmt.Sprintf("control-%d", i), ca) + } + + return tw.Flush(os.Stdout) + }, + } +} diff --git a/cmd/sptool/main.go b/cmd/sptool/main.go new file mode 100644 index 000000000..7970b8db3 --- /dev/null +++ b/cmd/sptool/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + + logging "github.com/ipfs/go-log/v2" + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/cli/spcli" +) + +var log = logging.Logger("sptool") + +func main() { + local := []*cli.Command{ + actorCmd, + spcli.InfoCmd(SPTActorGetter), + sectorsCmd, + provingCmd, + //multiSigCmd, + } + + app := &cli.App{ + Name: "sptool", + Usage: "Manage Filecoin Miner Actor", + Version: build.UserVersion(), + Commands: local, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + EnvVars: []string{"LOTUS_PATH"}, + Hidden: true, + Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME + }, + &cli.StringFlag{ + Name: "log-level", + Value: "info", + }, + &cli.StringFlag{ + Name: "actor", + Required: os.Getenv("LOTUS_DOCS_GENERATION") != "1", + Usage: "miner actor to manage", + EnvVars: []string{"SP_ADDRESS"}, + }, + }, + Before: func(cctx *cli.Context) error { + return logging.SetLogLevel("sptool", cctx.String("sptool")) + }, + } + + // terminate early on ctrl+c + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-c + cancel() + fmt.Println("Received interrupt, shutting down... Press CTRL+C again to force shutdown") + <-c + fmt.Println("Forcing stop") + os.Exit(1) + }() + + if err := app.RunContext(ctx, os.Args); err != nil { + log.Errorf("%+v", err) + os.Exit(1) + return + } + +} + +func SPTActorGetter(cctx *cli.Context) (address.Address, error) { + addr, err := address.NewFromString(cctx.String("actor")) + if err != nil { + return address.Undef, fmt.Errorf("parsing address: %w", err) + } + return addr, nil +} diff --git a/cmd/sptool/proving.go b/cmd/sptool/proving.go new file mode 100644 index 000000000..87c67b5f4 --- /dev/null +++ b/cmd/sptool/proving.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/cli/spcli" +) + +var provingCmd = &cli.Command{ + Name: "proving", + Usage: "View proving information", + Subcommands: []*cli.Command{ + spcli.ProvingInfoCmd(SPTActorGetter), + spcli.ProvingDeadlinesCmd(SPTActorGetter), + spcli.ProvingDeadlineInfoCmd(SPTActorGetter), + spcli.ProvingFaultsCmd(SPTActorGetter), + }, +} diff --git a/cmd/sptool/sector.go b/cmd/sptool/sector.go new file mode 100644 index 000000000..8f33053d3 --- /dev/null +++ b/cmd/sptool/sector.go @@ -0,0 +1,356 @@ +package main + +import ( + "fmt" + "os" + "sort" + + "github.com/docker/go-units" + "github.com/fatih/color" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/spcli" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/lib/tablewriter" +) + +var sectorsCmd = &cli.Command{ + Name: "sectors", + Usage: "interact with sector store", + Subcommands: []*cli.Command{ + spcli.SectorsStatusCmd(SPTActorGetter, nil), + sectorsListCmd, // in-house b/c chain-only is so different. Needs Curio *web* implementation + spcli.SectorPreCommitsCmd(SPTActorGetter), + spcli.SectorsCheckExpireCmd(SPTActorGetter), + sectorsExpiredCmd, // in-house b/c chain-only is so different + spcli.SectorsExtendCmd(SPTActorGetter), + //spcli.SectorsTerminateCmd(SPTActorGetter), // Could not trace through the state-machine + spcli.SectorsCompactPartitionsCmd(SPTActorGetter), + }} + +var sectorsExpiredCmd = &cli.Command{ + Name: "expired", + Usage: "Get or cleanup expired sectors", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "expired-epoch", + Usage: "epoch at which to check sector expirations", + DefaultText: "WinningPoSt lookback epoch", + }, + }, + Action: func(cctx *cli.Context) error { + fullApi, nCloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return xerrors.Errorf("getting fullnode api: %w", err) + } + defer nCloser() + ctx := lcli.ReqContext(cctx) + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("getting chain head: %w", err) + } + + lbEpoch := abi.ChainEpoch(cctx.Int64("expired-epoch")) + if !cctx.IsSet("expired-epoch") { + nv, err := fullApi.StateNetworkVersion(ctx, head.Key()) + if err != nil { + return xerrors.Errorf("getting network version: %w", err) + } + + lbEpoch = head.Height() - policy.GetWinningPoStSectorSetLookback(nv) + if lbEpoch < 0 { + return xerrors.Errorf("too early to terminate sectors") + } + } + + if cctx.IsSet("confirm-remove-count") && !cctx.IsSet("expired-epoch") { + return xerrors.Errorf("--expired-epoch must be specified with --confirm-remove-count") + } + + lbts, err := fullApi.ChainGetTipSetByHeight(ctx, lbEpoch, head.Key()) + if err != nil { + return xerrors.Errorf("getting lookback tipset: %w", err) + } + + maddr, err := SPTActorGetter(cctx) + if err != nil { + return xerrors.Errorf("getting actor address: %w", err) + } + + // toCheck is a working bitfield which will only contain terminated sectors + toCheck := bitfield.New() + { + sectors, err := fullApi.StateMinerSectors(ctx, maddr, nil, lbts.Key()) + if err != nil { + return xerrors.Errorf("getting sector on chain info: %w", err) + } + + for _, sector := range sectors { + if sector.Expiration <= lbts.Height() { + toCheck.Set(uint64(sector.SectorNumber)) + } + } + } + + mact, err := fullApi.StateGetActor(ctx, maddr, lbts.Key()) + if err != nil { + return err + } + + tbs := blockstore.NewTieredBstore(blockstore.NewAPIBlockstore(fullApi), blockstore.NewMemory()) + mas, err := miner.Load(adt.WrapStore(ctx, cbor.NewCborStore(tbs)), mact) + if err != nil { + return err + } + + alloc, err := mas.GetAllocatedSectors() + if err != nil { + return xerrors.Errorf("getting allocated sectors: %w", err) + } + + // only allocated sectors can be expired, + toCheck, err = bitfield.IntersectBitField(toCheck, *alloc) + if err != nil { + return xerrors.Errorf("intersecting bitfields: %w", err) + } + + if err := mas.ForEachDeadline(func(dlIdx uint64, dl miner.Deadline) error { + return dl.ForEachPartition(func(partIdx uint64, part miner.Partition) error { + live, err := part.LiveSectors() + if err != nil { + return err + } + + toCheck, err = bitfield.SubtractBitField(toCheck, live) + if err != nil { + return err + } + + unproven, err := part.UnprovenSectors() + if err != nil { + return err + } + + toCheck, err = bitfield.SubtractBitField(toCheck, unproven) + + return err + }) + }); err != nil { + return err + } + + err = mas.ForEachPrecommittedSector(func(pci miner.SectorPreCommitOnChainInfo) error { + toCheck.Unset(uint64(pci.Info.SectorNumber)) + return nil + }) + if err != nil { + return err + } + + // toCheck now only contains sectors which either failed to precommit or are expired/terminated + fmt.Printf("Sectors that either failed to precommit or are expired/terminated:\n") + + err = toCheck.ForEach(func(u uint64) error { + fmt.Println(abi.SectorNumber(u)) + + return nil + }) + if err != nil { + return err + } + + return nil + }, +} + +var sectorsListCmd = &cli.Command{ + Name: "list", + Usage: "List sectors", + Flags: []cli.Flag{ + /* + &cli.BoolFlag{ + Name: "show-removed", + Usage: "show removed sectors", + Aliases: []string{"r"}, + }, + &cli.BoolFlag{ + Name: "fast", + Usage: "don't show on-chain info for better performance", + Aliases: []string{"f"}, + }, + &cli.BoolFlag{ + Name: "events", + Usage: "display number of events the sector has received", + Aliases: []string{"e"}, + }, + &cli.BoolFlag{ + Name: "initial-pledge", + Usage: "display initial pledge", + Aliases: []string{"p"}, + }, + &cli.BoolFlag{ + Name: "seal-time", + Usage: "display how long it took for the sector to be sealed", + Aliases: []string{"t"}, + }, + &cli.StringFlag{ + Name: "states", + Usage: "filter sectors by a comma-separated list of states", + }, + &cli.BoolFlag{ + Name: "unproven", + Usage: "only show sectors which aren't in the 'Proving' state", + Aliases: []string{"u"}, + }, + */ + }, + Subcommands: []*cli.Command{ + //sectorsListUpgradeBoundsCmd, + }, + Action: func(cctx *cli.Context) error { + fullApi, closer2, err := lcli.GetFullNodeAPI(cctx) // TODO: consider storing full node address in config + if err != nil { + return err + } + defer closer2() + + ctx := lcli.ReqContext(cctx) + + maddr, err := SPTActorGetter(cctx) + if err != nil { + return err + } + + head, err := fullApi.ChainHead(ctx) + if err != nil { + return err + } + + activeSet, err := fullApi.StateMinerActiveSectors(ctx, maddr, head.Key()) + if err != nil { + return err + } + activeIDs := make(map[abi.SectorNumber]struct{}, len(activeSet)) + for _, info := range activeSet { + activeIDs[info.SectorNumber] = struct{}{} + } + + sset, err := fullApi.StateMinerSectors(ctx, maddr, nil, head.Key()) + if err != nil { + return err + } + commitedIDs := make(map[abi.SectorNumber]struct{}, len(sset)) + for _, info := range sset { + commitedIDs[info.SectorNumber] = struct{}{} + } + + sort.Slice(sset, func(i, j int) bool { + return sset[i].SectorNumber < sset[j].SectorNumber + }) + + tw := tablewriter.New( + tablewriter.Col("ID"), + tablewriter.Col("State"), + tablewriter.Col("OnChain"), + tablewriter.Col("Active"), + tablewriter.Col("Expiration"), + tablewriter.Col("SealTime"), + tablewriter.Col("Events"), + tablewriter.Col("Deals"), + tablewriter.Col("DealWeight"), + tablewriter.Col("VerifiedPower"), + tablewriter.Col("Pledge"), + tablewriter.NewLineCol("Error"), + tablewriter.NewLineCol("RecoveryTimeout")) + + fast := cctx.Bool("fast") + + for _, st := range sset { + s := st.SectorNumber + + _, inSSet := commitedIDs[s] + _, inASet := activeIDs[s] + + const verifiedPowerGainMul = 9 + dw, vp := .0, .0 + { + rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) + dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(verifiedPowerGainMul)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + } + + var deals int + for _, deal := range st.DealIDs { + if deal != 0 { + deals++ + } + } + + exp := st.Expiration + // if st.OnTime > 0 && st.OnTime < exp { + // exp = st.OnTime // Can be different when the sector was CC upgraded + // } + + m := map[string]interface{}{ + "ID": s, + //"State": color.New(spcli.StateOrder[sealing.SectorState(st.State)].Col).Sprint(st.State), + "OnChain": yesno(inSSet), + "Active": yesno(inASet), + } + + if deals > 0 { + m["Deals"] = color.GreenString("%d", deals) + } else { + m["Deals"] = color.BlueString("CC") + // if st.ToUpgrade { + // m["Deals"] = color.CyanString("CC(upgrade)") + // } + } + + if !fast { + if !inSSet { + m["Expiration"] = "n/a" + } else { + m["Expiration"] = cliutil.EpochTime(head.Height(), exp) + // if st.Early > 0 { + // m["RecoveryTimeout"] = color.YellowString(cliutil.EpochTime(head.Height(), st.Early)) + // } + } + if inSSet && cctx.Bool("initial-pledge") { + m["Pledge"] = types.FIL(st.InitialPledge).Short() + } + } + + if !fast && deals > 0 { + m["DealWeight"] = units.BytesSize(dw) + if vp > 0 { + m["VerifiedPower"] = color.GreenString(units.BytesSize(vp)) + } + } + + tw.Write(m) + } + + return tw.Flush(os.Stdout) + }, +} + +func yesno(b bool) string { + if b { + return color.GreenString("YES") + } + return color.RedString("NO") +} diff --git a/documentation/en/cli-curio.md b/documentation/en/cli-curio.md index 6976dfa2e..296a45c43 100644 --- a/documentation/en/cli-curio.md +++ b/documentation/en/cli-curio.md @@ -18,13 +18,12 @@ COMMANDS: web Start Curio web interface guided-setup Run the guided setup for migrating from lotus-miner to Curio seal Manage the sealing pipeline + auth Manage RPC permissions + log Manage logging + wait-api Wait for lotus api to come online + fetch-params Fetch proving parameters version Print version help, h Shows a list of commands or help for one command - DEVELOPER: - auth Manage RPC permissions - log Manage logging - wait-api Wait for lotus api to come online - fetch-params Fetch proving parameters GLOBAL OPTIONS: --color use color in display output (default: depends on output being a TTY) @@ -348,18 +347,6 @@ OPTIONS: --help, -h show help ``` -## curio version -``` -NAME: - curio version - Print version - -USAGE: - curio version [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - ## curio auth ``` NAME: @@ -487,9 +474,6 @@ NAME: USAGE: curio wait-api [command options] [arguments...] -CATEGORY: - DEVELOPER - OPTIONS: --timeout value duration to wait till fail (default: 30s) --help, -h show help @@ -503,8 +487,17 @@ NAME: USAGE: curio fetch-params [command options] [sectorSize] -CATEGORY: - DEVELOPER +OPTIONS: + --help, -h show help +``` + +## curio version +``` +NAME: + curio version - Print version + +USAGE: + curio version [command options] [arguments...] OPTIONS: --help, -h show help diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index 0f6704275..ed068624e 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -10,21 +10,20 @@ VERSION: 1.27.0-dev COMMANDS: - init Initialize a lotus miner repo - run Start a lotus miner process - stop Stop a running lotus miner - config Manage node config - backup Create node metadata backup - version Print version - help, h Shows a list of commands or help for one command + init Initialize a lotus miner repo + run Start a lotus miner process + stop Stop a running lotus miner + config Manage node config + backup Create node metadata backup + auth Manage RPC permissions + log Manage logging + wait-api Wait for lotus api to come online + fetch-params Fetch proving parameters + version Print version + help, h Shows a list of commands or help for one command CHAIN: actor manipulate the miner actor info Print miner info - DEVELOPER: - auth Manage RPC permissions - log Manage logging - wait-api Wait for lotus api to come online - fetch-params Fetch proving parameters STORAGE: sectors interact with sector store proving View proving information @@ -194,6 +193,150 @@ OPTIONS: --help, -h show help ``` +## lotus-miner auth +``` +NAME: + lotus-miner auth - Manage RPC permissions + +USAGE: + lotus-miner auth command [command options] [arguments...] + +COMMANDS: + create-token Create token + api-info Get token with API info required to connect to this node + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +### lotus-miner auth create-token +``` +NAME: + lotus-miner auth create-token - Create token + +USAGE: + lotus-miner auth create-token [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help +``` + +### lotus-miner auth api-info +``` +NAME: + lotus-miner auth api-info - Get token with API info required to connect to this node + +USAGE: + lotus-miner auth api-info [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help +``` + +## lotus-miner log +``` +NAME: + lotus-miner log - Manage logging + +USAGE: + lotus-miner log command [command options] [arguments...] + +COMMANDS: + list List log systems + set-level Set log level + alerts Get alert states + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +### lotus-miner log list +``` +NAME: + lotus-miner log list - List log systems + +USAGE: + lotus-miner log list [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` + +### lotus-miner log set-level +``` +NAME: + lotus-miner log set-level - Set log level + +USAGE: + lotus-miner log set-level [command options] [level] + +DESCRIPTION: + Set the log level for logging systems: + + The system flag can be specified multiple times. + + eg) log set-level --system chain --system chainxchg debug + + Available Levels: + debug + info + warn + error + + Environment Variables: + GOLOG_LOG_LEVEL - Default log level for all log systems + GOLOG_LOG_FMT - Change output log format (json, nocolor) + GOLOG_FILE - Write logs to file + GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr + + +OPTIONS: + --system value [ --system value ] limit to log system + --help, -h show help +``` + +### lotus-miner log alerts +``` +NAME: + lotus-miner log alerts - Get alert states + +USAGE: + lotus-miner log alerts [command options] [arguments...] + +OPTIONS: + --all get all (active and inactive) alerts (default: false) + --help, -h show help +``` + +## lotus-miner wait-api +``` +NAME: + lotus-miner wait-api - Wait for lotus api to come online + +USAGE: + lotus-miner wait-api [command options] [arguments...] + +OPTIONS: + --timeout value duration to wait till fail (default: 30s) + --help, -h show help +``` + +## lotus-miner fetch-params +``` +NAME: + lotus-miner fetch-params - Fetch proving parameters + +USAGE: + lotus-miner fetch-params [command options] [sectorSize] + +OPTIONS: + --help, -h show help +``` + ## lotus-miner version ``` NAME: @@ -444,156 +587,6 @@ OPTIONS: --help, -h show help ``` -## lotus-miner auth -``` -NAME: - lotus-miner auth - Manage RPC permissions - -USAGE: - lotus-miner auth command [command options] [arguments...] - -COMMANDS: - create-token Create token - api-info Get token with API info required to connect to this node - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-miner auth create-token -``` -NAME: - lotus-miner auth create-token - Create token - -USAGE: - lotus-miner auth create-token [command options] [arguments...] - -OPTIONS: - --perm value permission to assign to the token, one of: read, write, sign, admin - --help, -h show help -``` - -### lotus-miner auth api-info -``` -NAME: - lotus-miner auth api-info - Get token with API info required to connect to this node - -USAGE: - lotus-miner auth api-info [command options] [arguments...] - -OPTIONS: - --perm value permission to assign to the token, one of: read, write, sign, admin - --help, -h show help -``` - -## lotus-miner log -``` -NAME: - lotus-miner log - Manage logging - -USAGE: - lotus-miner log command [command options] [arguments...] - -COMMANDS: - list List log systems - set-level Set log level - alerts Get alert states - help, h Shows a list of commands or help for one command - -OPTIONS: - --help, -h show help -``` - -### lotus-miner log list -``` -NAME: - lotus-miner log list - List log systems - -USAGE: - lotus-miner log list [command options] [arguments...] - -OPTIONS: - --help, -h show help -``` - -### lotus-miner log set-level -``` -NAME: - lotus-miner log set-level - Set log level - -USAGE: - lotus-miner log set-level [command options] [level] - -DESCRIPTION: - Set the log level for logging systems: - - The system flag can be specified multiple times. - - eg) log set-level --system chain --system chainxchg debug - - Available Levels: - debug - info - warn - error - - Environment Variables: - GOLOG_LOG_LEVEL - Default log level for all log systems - GOLOG_LOG_FMT - Change output log format (json, nocolor) - GOLOG_FILE - Write logs to file - GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr - - -OPTIONS: - --system value [ --system value ] limit to log system - --help, -h show help -``` - -### lotus-miner log alerts -``` -NAME: - lotus-miner log alerts - Get alert states - -USAGE: - lotus-miner log alerts [command options] [arguments...] - -OPTIONS: - --all get all (active and inactive) alerts (default: false) - --help, -h show help -``` - -## lotus-miner wait-api -``` -NAME: - lotus-miner wait-api - Wait for lotus api to come online - -USAGE: - lotus-miner wait-api [command options] [arguments...] - -CATEGORY: - DEVELOPER - -OPTIONS: - --timeout value duration to wait till fail (default: 30s) - --help, -h show help -``` - -## lotus-miner fetch-params -``` -NAME: - lotus-miner fetch-params - Fetch proving parameters - -USAGE: - lotus-miner fetch-params [command options] [sectorSize] - -CATEGORY: - DEVELOPER - -OPTIONS: - --help, -h show help -``` - ## lotus-miner sectors ``` NAME: @@ -1039,7 +1032,6 @@ OPTIONS: --deadline value the deadline to compact the partitions in (default: 0) --partitions value [ --partitions value ] list of partitions to compact sectors in --really-do-it Actually send transaction performing the action (default: false) - --actor value Specify the address of the miner to run this command --help, -h show help ``` diff --git a/documentation/en/cli-sptool.md b/documentation/en/cli-sptool.md new file mode 100644 index 000000000..7b888a884 --- /dev/null +++ b/documentation/en/cli-sptool.md @@ -0,0 +1,441 @@ +# sptool +``` +NAME: + sptool - Manage Filecoin Miner Actor + +USAGE: + sptool [global options] command [command options] [arguments...] + +VERSION: + 1.27.0-dev + +COMMANDS: + actor Manage Filecoin Miner Actor Metadata + info Print miner actor info + sectors interact with sector store + proving View proving information + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --log-level value (default: "info") + --actor value miner actor to manage [$SP_ADDRESS] + --help, -h show help + --version, -v print the version +``` + +## sptool actor +``` +NAME: + sptool actor - Manage Filecoin Miner Actor Metadata + +USAGE: + sptool actor command [command options] [arguments...] + +COMMANDS: + set-addresses, set-addrs set addresses that your miner can be publicly dialed on + withdraw withdraw available balance to beneficiary + repay-debt pay down a miner's debt + set-peer-id set the peer id of your miner + set-owner Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) + control Manage control addresses + propose-change-worker Propose a worker address change + confirm-change-worker Confirm a worker address change + compact-allocated compact allocated sectors bitfield + propose-change-beneficiary Propose a beneficiary address change + confirm-change-beneficiary Confirm a beneficiary address change + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +### sptool actor set-addresses +``` +NAME: + sptool actor set-addresses - set addresses that your miner can be publicly dialed on + +USAGE: + sptool actor set-addresses [command options] + +OPTIONS: + --from value optionally specify the account to send the message from + --gas-limit value set gas limit (default: 0) + --unset unset address (default: false) + --help, -h show help +``` + +### sptool actor withdraw +``` +NAME: + sptool actor withdraw - withdraw available balance to beneficiary + +USAGE: + sptool actor withdraw [command options] [amount (FIL)] + +OPTIONS: + --confidence value number of block confirmations to wait for (default: 5) + --beneficiary send withdraw message from the beneficiary address (default: false) + --help, -h show help +``` + +### sptool actor repay-debt +``` +NAME: + sptool actor repay-debt - pay down a miner's debt + +USAGE: + sptool actor repay-debt [command options] [amount (FIL)] + +OPTIONS: + --from value optionally specify the account to send funds from + --help, -h show help +``` + +### sptool actor set-peer-id +``` +NAME: + sptool actor set-peer-id - set the peer id of your miner + +USAGE: + sptool actor set-peer-id [command options] + +OPTIONS: + --gas-limit value set gas limit (default: 0) + --help, -h show help +``` + +### sptool actor set-owner +``` +NAME: + sptool actor set-owner - Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) + +USAGE: + sptool actor set-owner [command options] [newOwnerAddress senderAddress] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +### sptool actor control +``` +NAME: + sptool actor control - Manage control addresses + +USAGE: + sptool actor control command [command options] [arguments...] + +COMMANDS: + list Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state. + set Set control address(-es) + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +#### sptool actor control list +``` +NAME: + sptool actor control list - Get currently set control addresses. Note: This excludes most roles as they are not known to the immediate chain state. + +USAGE: + sptool actor control list [command options] [arguments...] + +OPTIONS: + --verbose (default: false) + --help, -h show help +``` + +#### sptool actor control set +``` +NAME: + sptool actor control set - Set control address(-es) + +USAGE: + sptool actor control set [command options] [...address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +### sptool actor propose-change-worker +``` +NAME: + sptool actor propose-change-worker - Propose a worker address change + +USAGE: + sptool actor propose-change-worker [command options] [address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +### sptool actor confirm-change-worker +``` +NAME: + sptool actor confirm-change-worker - Confirm a worker address change + +USAGE: + sptool actor confirm-change-worker [command options] [address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +### sptool actor compact-allocated +``` +NAME: + sptool actor compact-allocated - compact allocated sectors bitfield + +USAGE: + sptool actor compact-allocated [command options] [arguments...] + +OPTIONS: + --mask-last-offset value Mask sector IDs from 0 to 'highest_allocated - offset' (default: 0) + --mask-upto-n value Mask sector IDs from 0 to 'n' (default: 0) + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +### sptool actor propose-change-beneficiary +``` +NAME: + sptool actor propose-change-beneficiary - Propose a beneficiary address change + +USAGE: + sptool actor propose-change-beneficiary [command options] [beneficiaryAddress quota expiration] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --overwrite-pending-change Overwrite the current beneficiary change proposal (default: false) + --actor value specify the address of miner actor + --help, -h show help +``` + +### sptool actor confirm-change-beneficiary +``` +NAME: + sptool actor confirm-change-beneficiary - Confirm a beneficiary address change + +USAGE: + sptool actor confirm-change-beneficiary [command options] [minerID] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --existing-beneficiary send confirmation from the existing beneficiary address (default: false) + --new-beneficiary send confirmation from the new beneficiary address (default: false) + --help, -h show help +``` + +## sptool info +``` +NAME: + sptool info - Print miner actor info + +USAGE: + sptool info [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` + +## sptool sectors +``` +NAME: + sptool sectors - interact with sector store + +USAGE: + sptool sectors command [command options] [arguments...] + +COMMANDS: + status Get the seal status of a sector by its number + list List sectors + precommits Print on-chain precommit info + check-expire Inspect expiring sectors + expired Get or cleanup expired sectors + extend Extend expiring sectors while not exceeding each sector's max life + compact-partitions removes dead sectors from partitions and reduces the number of partitions used if possible + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +### sptool sectors status +``` +NAME: + sptool sectors status - Get the seal status of a sector by its number + +USAGE: + sptool sectors status [command options] + +OPTIONS: + --log, -l display event log (default: false) + --on-chain-info, -c show sector on chain info (default: false) + --partition-info, -p show partition related info (default: false) + --proof print snark proof bytes as hex (default: false) + --help, -h show help +``` + +### sptool sectors list +``` +NAME: + sptool sectors list - List sectors + +USAGE: + sptool sectors list [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` + +### sptool sectors precommits +``` +NAME: + sptool sectors precommits - Print on-chain precommit info + +USAGE: + sptool sectors precommits [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` + +### sptool sectors check-expire +``` +NAME: + sptool sectors check-expire - Inspect expiring sectors + +USAGE: + sptool sectors check-expire [command options] [arguments...] + +OPTIONS: + --cutoff value skip sectors whose current expiration is more than epochs from now, defaults to 60 days (default: 172800) + --help, -h show help +``` + +### sptool sectors expired +``` +NAME: + sptool sectors expired - Get or cleanup expired sectors + +USAGE: + sptool sectors expired [command options] [arguments...] + +OPTIONS: + --expired-epoch value epoch at which to check sector expirations (default: WinningPoSt lookback epoch) + --help, -h show help +``` + +### sptool sectors extend +``` +NAME: + sptool sectors extend - Extend expiring sectors while not exceeding each sector's max life + +USAGE: + sptool sectors extend [command options] + +OPTIONS: + --from value only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 120 (1 hour) (default: 0) + --to value only consider sectors whose current expiration epoch is in the range of [from, to], defaults to: now + 92160 (32 days) (default: 0) + --sector-file value provide a file containing one sector number in each line, ignoring above selecting criteria + --exclude value optionally provide a file containing excluding sectors + --extension value try to extend selected sectors by this number of epochs, defaults to 540 days (default: 1555200) + --new-expiration value try to extend selected sectors to this epoch, ignoring extension (default: 0) + --only-cc only extend CC sectors (useful for making sector ready for snap upgrade) (default: false) + --drop-claims drop claims for sectors that can be extended, but only by dropping some of their verified power claims (default: false) + --tolerance value don't try to extend sectors by fewer than this number of epochs, defaults to 7 days (default: 20160) + --max-fee value use up to this amount of FIL for one message. pass this flag to avoid message congestion. (default: "0") + --max-sectors value the maximum number of sectors contained in each message (default: 0) + --really-do-it pass this flag to really extend sectors, otherwise will only print out json representation of parameters (default: false) + --help, -h show help +``` + +### sptool sectors compact-partitions +``` +NAME: + sptool sectors compact-partitions - removes dead sectors from partitions and reduces the number of partitions used if possible + +USAGE: + sptool sectors compact-partitions [command options] [arguments...] + +OPTIONS: + --deadline value the deadline to compact the partitions in (default: 0) + --partitions value [ --partitions value ] list of partitions to compact sectors in + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help +``` + +## sptool proving +``` +NAME: + sptool proving - View proving information + +USAGE: + sptool proving command [command options] [arguments...] + +COMMANDS: + info View current state information + deadlines View the current proving period deadlines information + deadline View the current proving period deadline information by its index + faults View the currently known proving faulty sectors information + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help +``` + +### sptool proving info +``` +NAME: + sptool proving info - View current state information + +USAGE: + sptool proving info [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` + +### sptool proving deadlines +``` +NAME: + sptool proving deadlines - View the current proving period deadlines information + +USAGE: + sptool proving deadlines [command options] [arguments...] + +OPTIONS: + --all, -a Count all sectors (only live sectors are counted by default) (default: false) + --help, -h show help +``` + +### sptool proving deadline +``` +NAME: + sptool proving deadline - View the current proving period deadline information by its index + +USAGE: + sptool proving deadline [command options] + +OPTIONS: + --sector-nums, -n Print sector/fault numbers belonging to this deadline (default: false) + --bitfield, -b Print partition bitfield stats (default: false) + --help, -h show help +``` + +### sptool proving faults +``` +NAME: + sptool proving faults - View the currently known proving faulty sectors information + +USAGE: + sptool proving faults [command options] [arguments...] + +OPTIONS: + --help, -h show help +``` diff --git a/itests/cli_test.go b/itests/cli_test.go index a323c0863..d2a087635 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/clicommands" "github.com/filecoin-project/lotus/itests/kit" ) @@ -23,5 +23,5 @@ func TestClient(t *testing.T) { blockTime := 5 * time.Millisecond client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC()) ens.InterconnectAll().BeginMining(blockTime) - kit.RunClientTest(t, cli.Commands, client) + kit.RunClientTest(t, clicommands.Commands, client) } diff --git a/itests/gateway_test.go b/itests/gateway_test.go index d20b3bd1a..2dc4e1034 100644 --- a/itests/gateway_test.go +++ b/itests/gateway_test.go @@ -24,7 +24,7 @@ import ( "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/clicommands" "github.com/filecoin-project/lotus/gateway" "github.com/filecoin-project/lotus/itests/kit" "github.com/filecoin-project/lotus/itests/multisig" @@ -231,7 +231,7 @@ func TestGatewayCLIDealFlow(t *testing.T) { ctx := context.Background() nodes := startNodesWithFunds(ctx, t, blocktime, maxLookbackCap, maxStateWaitLookbackLimit) - kit.RunClientTest(t, cli.Commands, nodes.lite) + kit.RunClientTest(t, clicommands.Commands, nodes.lite) } type testNodes struct { diff --git a/itests/multisig/suite.go b/itests/multisig/suite.go index 9a81d0bf9..61ca68d5e 100644 --- a/itests/multisig/suite.go +++ b/itests/multisig/suite.go @@ -13,14 +13,14 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/clicommands" "github.com/filecoin-project/lotus/itests/kit" ) func RunMultisigTests(t *testing.T, client *kit.TestFullNode) { // Create mock CLI ctx := context.Background() - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) + mockCLI := kit.NewMockCLI(ctx, t, clicommands.Commands, api.NodeFull) clientCLI := mockCLI.Client(client.ListenAddr) // Create some wallets on the node to use for testing multisig diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index f86f5d8de..1079aade9 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -25,7 +25,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/cli/clicommands" "github.com/filecoin-project/lotus/itests/kit" ) @@ -51,7 +51,7 @@ func TestPaymentChannelsBasic(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) + mockCLI := kit.NewMockCLI(ctx, t, clicommands.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -126,7 +126,7 @@ func TestPaymentChannelStatus(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) + mockCLI := kit.NewMockCLI(ctx, t, clicommands.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych status-by-from-to @@ -212,7 +212,7 @@ func TestPaymentChannelVouchers(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) + mockCLI := kit.NewMockCLI(ctx, t, clicommands.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) receiverCLI := mockCLI.Client(paymentReceiver.ListenAddr) @@ -350,7 +350,7 @@ func TestPaymentChannelVoucherCreateShortfall(t *testing.T) { creatorAddr, receiverAddr := startPaychCreatorReceiverMiner(ctx, t, &paymentCreator, &paymentReceiver, blocktime) // Create mock CLI - mockCLI := kit.NewMockCLI(ctx, t, cli.Commands, api.NodeFull) + mockCLI := kit.NewMockCLI(ctx, t, clicommands.Commands, api.NodeFull) creatorCLI := mockCLI.Client(paymentCreator.ListenAddr) // creator: paych add-funds diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 90248a355..bd4824940 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -1377,15 +1377,15 @@ func (sm *StorageMinerAPI) RuntimeSubsystems(context.Context) (res api.MinerSubs } func (sm *StorageMinerAPI) ActorWithdrawBalance(ctx context.Context, amount abi.TokenAmount) (cid.Cid, error) { - return sm.withdrawBalance(ctx, amount, true) + return WithdrawBalance(ctx, sm.Full, sm.Miner.Address(), amount, true) } func (sm *StorageMinerAPI) BeneficiaryWithdrawBalance(ctx context.Context, amount abi.TokenAmount) (cid.Cid, error) { - return sm.withdrawBalance(ctx, amount, false) + return WithdrawBalance(ctx, sm.Full, sm.Miner.Address(), amount, false) } -func (sm *StorageMinerAPI) withdrawBalance(ctx context.Context, amount abi.TokenAmount, fromOwner bool) (cid.Cid, error) { - available, err := sm.Full.StateMinerAvailableBalance(ctx, sm.Miner.Address(), types.EmptyTSK) +func WithdrawBalance(ctx context.Context, full api.FullNode, maddr address.Address, amount abi.TokenAmount, fromOwner bool) (cid.Cid, error) { + available, err := full.StateMinerAvailableBalance(ctx, maddr, types.EmptyTSK) if err != nil { return cid.Undef, xerrors.Errorf("Error getting miner balance: %w", err) } @@ -1405,7 +1405,7 @@ func (sm *StorageMinerAPI) withdrawBalance(ctx context.Context, amount abi.Token return cid.Undef, err } - mi, err := sm.Full.StateMinerInfo(ctx, sm.Miner.Address(), types.EmptyTSK) + mi, err := full.StateMinerInfo(ctx, maddr, types.EmptyTSK) if err != nil { return cid.Undef, xerrors.Errorf("Error getting miner's owner address: %w", err) } @@ -1417,8 +1417,8 @@ func (sm *StorageMinerAPI) withdrawBalance(ctx context.Context, amount abi.Token sender = mi.Beneficiary } - smsg, err := sm.Full.MpoolPushMessage(ctx, &types.Message{ - To: sm.Miner.Address(), + smsg, err := full.MpoolPushMessage(ctx, &types.Message{ + To: maddr, From: sender, Value: types.NewInt(0), Method: builtintypes.MethodsMiner.WithdrawBalance, diff --git a/scripts/generate-lotus-cli.py b/scripts/generate-lotus-cli.py index 305716b09..c06e2f2a5 100644 --- a/scripts/generate-lotus-cli.py +++ b/scripts/generate-lotus-cli.py @@ -51,8 +51,12 @@ if __name__ == "__main__": for e in [ "LOTUS_PATH", "LOTUS_MARKETS_PATH", "LOTUS_MINER_PATH", "LOTUS_STORAGE_PATH", "LOTUS_WORKER_PATH", "WORKER_PATH", "LOTUS_PANIC_REPORT_PATH", "WALLET_PATH" ]: os.environ.pop(e, None) + # Set env var telling the binaries that we're generating docs + os.putenv("LOTUS_DOCS_GENERATION", "1") + os.putenv("LOTUS_VERSION_IGNORE_COMMIT", "1") generate_lotus_cli('lotus') generate_lotus_cli('lotus-miner') generate_lotus_cli('lotus-worker') - generate_lotus_cli('curio') \ No newline at end of file + generate_lotus_cli('curio') + generate_lotus_cli('sptool') \ No newline at end of file