From 09b9e6eb902b19621eb416f723dfaf5fb8695daf Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 14 Jan 2025 11:53:50 +1100 Subject: [PATCH] feat(shed): lotus-shed miner locked-vested to find erroneously locked funds Ref: https://github.com/filecoin-project/builtin-actors/issues/1594 --- cmd/lotus-shed/miner.go | 88 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/cmd/lotus-shed/miner.go b/cmd/lotus-shed/miner.go index 3e7be3b1f30..de518d548ac 100644 --- a/cmd/lotus-shed/miner.go +++ b/cmd/lotus-shed/miner.go @@ -26,8 +26,10 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + miner15 "github.com/filecoin-project/go-state-types/builtin/v15/miner" miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner" "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/manifest" power7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/power" "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" @@ -36,6 +38,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" ) @@ -50,6 +53,7 @@ var minerCmd = &cli.Command{ sendInvalidWindowPoStCmd, generateAndSendConsensusFaultCmd, sectorInfoCmd, + minerLockedVestedCmd, }, } @@ -688,3 +692,87 @@ var generateAndSendConsensusFaultCmd = &cli.Command{ return nil }, } + +// TODO: LoadVestingFunds isn't exposed on the miner wrappers in Lotus so we have to go decoding the +// miner state manually. This command will continue to work as long as the hard-coded go-state-types +// miner version matches the schema of the current miner actor. It will need to be updated if the +// miner actor schema changes; or we could expose LoadVestingFunds. +var minerLockedVestedCmd = &cli.Command{ + Name: "locked-vested", + Usage: "Search through all miners for VestingFunds that are still locked even though the epoch has passed", + Action: func(cctx *cli.Context) error { + n, acloser, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer acloser() + ctx := lcli.ReqContext(cctx) + + bs := ReadOnlyAPIBlockstore{n} + adtStore := adt.WrapStore(ctx, ipldcbor.NewCborStore(&bs)) + + head, err := n.ChainHead(ctx) + if err != nil { + return err + } + + tree, err := state.LoadStateTree(adtStore, head.ParentState()) + if err != nil { + return err + } + nv, err := n.StateNetworkVersion(ctx, head.Key()) + if err != nil { + return err + } + actorCodeCids, err := n.StateActorCodeCIDs(ctx, nv) + if err != nil { + return err + } + minerCode := actorCodeCids[manifest.MinerKey] + + var totalCount int + var totalMiners int + var lockedCount int + var lockedFunds abi.TokenAmount = big.Zero() + fmt.Printf("Scanning actors (looking for v%d miners [%s] at epoch %d)", nv, minerCode, head.Height()) + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + totalCount++ + if totalCount%10000 == 0 { + fmt.Printf(".") + } + if act.Code == minerCode { + totalMiners++ + m15 := miner15.State{} + if err := adtStore.Get(ctx, act.Head, &m15); err != nil { + return xerrors.Errorf("failed to load miner state (using miner15, try a newer version?): %w", err) + } + vf, err := m15.LoadVestingFunds(adtStore) + if err != nil { + return err + } + var locked bool + for _, f := range vf.Funds { + if f.Epoch < head.Height() { + lockedFunds = big.Add(lockedFunds, f.Amount) + locked = true + } + } + if locked { + lockedCount++ + } + } + return nil + }) + if err != nil { + return xerrors.Errorf("failed to loop over actors: %w", err) + } + + fmt.Println() + fmt.Printf("Total actors: %d\n", totalCount) + fmt.Printf("Total miners: %d\n", totalMiners) + fmt.Printf("Miners with locked funds: %d\n", lockedCount) + fmt.Printf("Total locked funds: %s\n", types.FIL(lockedFunds)) + + return nil + }, +}