Skip to content

Commit 18d1078

Browse files
authored
feat(shed): miner locked-vested, and miner list-vested <miner> (#12828)
Ref: filecoin-project/builtin-actors#1594
1 parent 1c2e5f7 commit 18d1078

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

cmd/lotus-shed/miner.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"bytes"
66
"context"
7+
"encoding/json"
78
"fmt"
89
"io"
910
"os"
@@ -26,8 +27,10 @@ import (
2627
"github.com/filecoin-project/go-state-types/big"
2728
"github.com/filecoin-project/go-state-types/builtin"
2829
"github.com/filecoin-project/go-state-types/builtin/v11/util/adt"
30+
miner15 "github.com/filecoin-project/go-state-types/builtin/v15/miner"
2931
miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner"
3032
"github.com/filecoin-project/go-state-types/crypto"
33+
"github.com/filecoin-project/go-state-types/manifest"
3134
power7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/power"
3235
"github.com/filecoin-project/specs-actors/v7/actors/runtime/proof"
3336

@@ -36,6 +39,7 @@ import (
3639
"github.com/filecoin-project/lotus/chain/actors"
3740
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
3841
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
42+
"github.com/filecoin-project/lotus/chain/state"
3943
"github.com/filecoin-project/lotus/chain/types"
4044
lcli "github.com/filecoin-project/lotus/cli"
4145
)
@@ -50,6 +54,8 @@ var minerCmd = &cli.Command{
5054
sendInvalidWindowPoStCmd,
5155
generateAndSendConsensusFaultCmd,
5256
sectorInfoCmd,
57+
minerLockedVestedCmd,
58+
minerListVestingCmd,
5359
},
5460
}
5561

@@ -688,3 +694,176 @@ var generateAndSendConsensusFaultCmd = &cli.Command{
688694
return nil
689695
},
690696
}
697+
698+
// TODO: LoadVestingFunds isn't exposed on the miner wrappers in Lotus so we have to go decoding the
699+
// miner state manually. This command will continue to work as long as the hard-coded go-state-types
700+
// miner version matches the schema of the current miner actor. It will need to be updated if the
701+
// miner actor schema changes; or we could expose LoadVestingFunds.
702+
var minerLockedVestedCmd = &cli.Command{
703+
Name: "locked-vested",
704+
Usage: "Search through all miners for VestingFunds that are still locked even though the epoch has passed",
705+
Flags: []cli.Flag{
706+
&cli.BoolFlag{
707+
Name: "details",
708+
Usage: "orint details of locked funds; which miners and how much",
709+
},
710+
},
711+
Action: func(cctx *cli.Context) error {
712+
n, acloser, err := lcli.GetFullNodeAPI(cctx)
713+
if err != nil {
714+
return err
715+
}
716+
defer acloser()
717+
ctx := lcli.ReqContext(cctx)
718+
719+
bs := ReadOnlyAPIBlockstore{n}
720+
adtStore := adt.WrapStore(ctx, ipldcbor.NewCborStore(&bs))
721+
722+
head, err := n.ChainHead(ctx)
723+
if err != nil {
724+
return err
725+
}
726+
727+
tree, err := state.LoadStateTree(adtStore, head.ParentState())
728+
if err != nil {
729+
return err
730+
}
731+
nv, err := n.StateNetworkVersion(ctx, head.Key())
732+
if err != nil {
733+
return err
734+
}
735+
actorCodeCids, err := n.StateActorCodeCIDs(ctx, nv)
736+
if err != nil {
737+
return err
738+
}
739+
minerCode := actorCodeCids[manifest.MinerKey]
740+
741+
// The epoch at which we _expect_ that vested funds to have been unlocked by (the delay
742+
// is due to cron offsets). The protocol dictates that funds should be unlocked automatically
743+
// by cron, so anything we find that's not unlocked is a bug.
744+
staleEpoch := head.Height() - abi.ChainEpoch((uint64(miner15.WPoStProvingPeriod) / miner15.WPoStPeriodDeadlines))
745+
746+
var totalCount int
747+
miners := make(map[address.Address]abi.TokenAmount)
748+
var lockedCount int
749+
var lockedFunds abi.TokenAmount = big.Zero()
750+
_, _ = fmt.Fprintf(cctx.App.ErrWriter, "Scanning actors at epoch %d", head.Height())
751+
err = tree.ForEach(func(addr address.Address, act *types.Actor) error {
752+
totalCount++
753+
if totalCount%10000 == 0 {
754+
_, _ = fmt.Fprintf(cctx.App.ErrWriter, ".")
755+
}
756+
if act.Code == minerCode {
757+
m15 := miner15.State{}
758+
if err := adtStore.Get(ctx, act.Head, &m15); err != nil {
759+
return xerrors.Errorf("failed to load miner state (using miner15, try a newer version?): %w", err)
760+
}
761+
vf, err := m15.LoadVestingFunds(adtStore)
762+
if err != nil {
763+
return err
764+
}
765+
var locked bool
766+
for _, f := range vf.Funds {
767+
if f.Epoch < staleEpoch {
768+
if _, ok := miners[addr]; !ok {
769+
miners[addr] = f.Amount
770+
} else {
771+
miners[addr] = big.Add(miners[addr], f.Amount)
772+
}
773+
lockedFunds = big.Add(lockedFunds, f.Amount)
774+
locked = true
775+
}
776+
}
777+
if locked {
778+
lockedCount++
779+
}
780+
}
781+
return nil
782+
})
783+
if err != nil {
784+
return xerrors.Errorf("failed to loop over actors: %w", err)
785+
}
786+
787+
fmt.Println()
788+
_, _ = fmt.Fprintf(cctx.App.Writer, "Total actors: %d\n", totalCount)
789+
_, _ = fmt.Fprintf(cctx.App.Writer, "Total miners: %d\n", len(miners))
790+
_, _ = fmt.Fprintf(cctx.App.Writer, "Miners with locked vested funds: %d\n", lockedCount)
791+
if cctx.Bool("details") {
792+
for addr, amt := range miners {
793+
_, _ = fmt.Fprintf(cctx.App.Writer, " %s: %s\n", addr, types.FIL(amt))
794+
}
795+
}
796+
_, _ = fmt.Fprintf(cctx.App.Writer, "Total locked vested funds: %s\n", types.FIL(lockedFunds))
797+
798+
return nil
799+
},
800+
}
801+
802+
var minerListVestingCmd = &cli.Command{
803+
Name: "list-vesting",
804+
Usage: "List the vesting schedule for a miner",
805+
ArgsUsage: "[minerAddress]",
806+
Flags: []cli.Flag{
807+
&cli.BoolFlag{
808+
Name: "json",
809+
Usage: "output in json format (also don't convert from attoFIL to FIL)",
810+
},
811+
},
812+
Action: func(cctx *cli.Context) error {
813+
if cctx.Args().Len() != 1 {
814+
return fmt.Errorf("must pass miner address")
815+
}
816+
817+
maddr, err := address.NewFromString(cctx.Args().First())
818+
if err != nil {
819+
return err
820+
}
821+
822+
n, acloser, err := lcli.GetFullNodeAPI(cctx)
823+
if err != nil {
824+
return err
825+
}
826+
defer acloser()
827+
ctx := lcli.ReqContext(cctx)
828+
829+
bs := ReadOnlyAPIBlockstore{n}
830+
adtStore := adt.WrapStore(ctx, ipldcbor.NewCborStore(&bs))
831+
832+
head, err := n.ChainHead(ctx)
833+
if err != nil {
834+
return err
835+
}
836+
837+
tree, err := state.LoadStateTree(adtStore, head.ParentState())
838+
if err != nil {
839+
return err
840+
}
841+
842+
act, err := tree.GetActor(maddr)
843+
if err != nil {
844+
return xerrors.Errorf("failed to load actor: %w", err)
845+
}
846+
847+
m15 := miner15.State{}
848+
if err := adtStore.Get(ctx, act.Head, &m15); err != nil {
849+
return xerrors.Errorf("failed to load miner state (using miner15, try a newer version?): %w", err)
850+
}
851+
vf, err := m15.LoadVestingFunds(adtStore)
852+
if err != nil {
853+
return err
854+
}
855+
856+
if cctx.Bool("json") {
857+
jb, err := json.Marshal(vf)
858+
if err != nil {
859+
return xerrors.Errorf("failed to marshal vesting funds: %w", err)
860+
}
861+
_, _ = fmt.Fprintln(cctx.App.Writer, string(jb))
862+
} else {
863+
for _, f := range vf.Funds {
864+
_, _ = fmt.Fprintf(cctx.App.Writer, "Epoch %d: %s\n", f.Epoch, types.FIL(f.Amount))
865+
}
866+
}
867+
return nil
868+
},
869+
}

0 commit comments

Comments
 (0)