feat: SPTool (#11788)

* sptool: Initial structure

* sptool: Port lotus-miner actor withdraw

* sptool: Make cli docsgen happy

* actors are done

* info

* proving

* sptool the rest

* fixed gitignore

* lints

* oops

* 2

* terminate

* fixes

* sptool: improve sectors list

---------

Co-authored-by: Łukasz Magiera <magik6k@gmail.com>
This commit is contained in:
Andrew Jackson (Ajax)
2024-04-01 10:30:35 -05:00
committed by GitHub
co-authored by Łukasz Magiera
parent 8062f200bd
commit b95e95f4d6
44 changed files with 4765 additions and 3383 deletions
+1240
View File
File diff suppressed because it is too large Load Diff
+121
View File
@@ -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
},
}
}
+451
View File
@@ -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: "<deadlineIdx>",
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()
},
}
}
+1398
View File
File diff suppressed because it is too large Load Diff
+95
View File
@@ -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,
}
}
}
+9
View File
@@ -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)