4
4
"bufio"
5
5
"bytes"
6
6
"context"
7
+ "encoding/json"
7
8
"fmt"
8
9
"io"
9
10
"os"
@@ -26,8 +27,10 @@ import (
26
27
"github.com/filecoin-project/go-state-types/big"
27
28
"github.com/filecoin-project/go-state-types/builtin"
28
29
"github.com/filecoin-project/go-state-types/builtin/v11/util/adt"
30
+ miner15 "github.com/filecoin-project/go-state-types/builtin/v15/miner"
29
31
miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner"
30
32
"github.com/filecoin-project/go-state-types/crypto"
33
+ "github.com/filecoin-project/go-state-types/manifest"
31
34
power7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/power"
32
35
"github.com/filecoin-project/specs-actors/v7/actors/runtime/proof"
33
36
@@ -36,6 +39,7 @@ import (
36
39
"github.com/filecoin-project/lotus/chain/actors"
37
40
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
38
41
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
42
+ "github.com/filecoin-project/lotus/chain/state"
39
43
"github.com/filecoin-project/lotus/chain/types"
40
44
lcli "github.com/filecoin-project/lotus/cli"
41
45
)
@@ -50,6 +54,8 @@ var minerCmd = &cli.Command{
50
54
sendInvalidWindowPoStCmd ,
51
55
generateAndSendConsensusFaultCmd ,
52
56
sectorInfoCmd ,
57
+ minerLockedVestedCmd ,
58
+ minerListVestingCmd ,
53
59
},
54
60
}
55
61
@@ -688,3 +694,176 @@ var generateAndSendConsensusFaultCmd = &cli.Command{
688
694
return nil
689
695
},
690
696
}
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