665 lines
16 KiB
Go
665 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/filecoin-project/lotus/build"
|
|
|
|
"github.com/filecoin-project/lotus/chain/gen/genesis"
|
|
|
|
_init "github.com/filecoin-project/lotus/chain/actors/builtin/init"
|
|
|
|
"github.com/docker/go-units"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/multisig"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/power"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/reward"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
cbor "github.com/ipfs/go-ipld-cbor"
|
|
logging "github.com/ipfs/go-log/v2"
|
|
"github.com/urfave/cli/v2"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-state-types/abi"
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
|
|
"github.com/filecoin-project/lotus/chain/actors/adt"
|
|
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
|
|
"github.com/filecoin-project/lotus/chain/state"
|
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/chain/vm"
|
|
lcli "github.com/filecoin-project/lotus/cli"
|
|
"github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper"
|
|
"github.com/filecoin-project/lotus/node/repo"
|
|
)
|
|
|
|
type accountInfo struct {
|
|
Address address.Address
|
|
Balance types.FIL
|
|
Type string
|
|
Power abi.StoragePower
|
|
Worker address.Address
|
|
Owner address.Address
|
|
InitialPledge types.FIL
|
|
PreCommits types.FIL
|
|
LockedFunds types.FIL
|
|
Sectors uint64
|
|
VestingStart abi.ChainEpoch
|
|
VestingDuration abi.ChainEpoch
|
|
VestingAmount types.FIL
|
|
}
|
|
|
|
var auditsCmd = &cli.Command{
|
|
Name: "audits",
|
|
Description: "a collection of utilities for auditing the filecoin chain",
|
|
Subcommands: []*cli.Command{
|
|
chainBalanceCmd,
|
|
chainBalanceSanityCheckCmd,
|
|
chainBalanceStateCmd,
|
|
chainPledgeCmd,
|
|
fillBalancesCmd,
|
|
},
|
|
}
|
|
|
|
var chainBalanceSanityCheckCmd = &cli.Command{
|
|
Name: "chain-balance-sanity",
|
|
Description: "Confirms that the total balance of every actor in state is still 2 billion",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset to start from",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer closer()
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
ts, err := lcli.LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tsk := ts.Key()
|
|
actors, err := api.StateListActors(ctx, tsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bal := big.Zero()
|
|
for _, addr := range actors {
|
|
act, err := api.StateGetActor(ctx, addr, tsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bal = big.Add(bal, act.Balance)
|
|
}
|
|
|
|
attoBase := big.Mul(big.NewInt(int64(build.FilBase)), big.NewInt(int64(build.FilecoinPrecision)))
|
|
|
|
if big.Cmp(attoBase, bal) != 0 {
|
|
return xerrors.Errorf("sanity check failed (expected %s, actual %s)", attoBase, bal)
|
|
}
|
|
|
|
fmt.Println("sanity check successful")
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var chainBalanceCmd = &cli.Command{
|
|
Name: "chain-balances",
|
|
Description: "Produces a csv file of all account balances",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "tipset",
|
|
Usage: "specify tipset to start from",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer closer()
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
ts, err := lcli.LoadTipSet(ctx, cctx, api)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tsk := ts.Key()
|
|
actors, err := api.StateListActors(ctx, tsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var infos []accountInfo
|
|
for _, addr := range actors {
|
|
act, err := api.StateGetActor(ctx, addr, tsk)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ai := accountInfo{
|
|
Address: addr,
|
|
Balance: types.FIL(act.Balance),
|
|
Type: string(act.Code.Hash()[2:]),
|
|
}
|
|
|
|
if builtin.IsStorageMinerActor(act.Code) {
|
|
pow, err := api.StateMinerPower(ctx, addr, tsk)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get power: %w", err)
|
|
}
|
|
|
|
ai.Power = pow.MinerPower.RawBytePower
|
|
info, err := api.StateMinerInfo(ctx, addr, tsk)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get miner info: %w", err)
|
|
}
|
|
ai.Worker = info.Worker
|
|
ai.Owner = info.Owner
|
|
|
|
}
|
|
infos = append(infos, ai)
|
|
}
|
|
|
|
printAccountInfos(infos, false)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var chainBalanceStateCmd = &cli.Command{
|
|
Name: "stateroot-balances",
|
|
Description: "Produces a csv file of all account balances from a given stateroot",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "repo",
|
|
Value: "~/.lotus",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "miner-info",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "robust-addresses",
|
|
},
|
|
},
|
|
Action: func(cctx *cli.Context) error {
|
|
ctx := context.TODO()
|
|
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass state root")
|
|
}
|
|
|
|
sroot, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse input: %w", err)
|
|
}
|
|
|
|
fsrepo, err := repo.NewFS(cctx.String("repo"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lkrepo, err := fsrepo.Lock(repo.FullNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer lkrepo.Close() //nolint:errcheck
|
|
|
|
bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open blockstore: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if c, ok := bs.(io.Closer); ok {
|
|
if err := c.Close(); err != nil {
|
|
log.Warnf("failed to close blockstore: %s", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
mds, err := lkrepo.Datastore(context.Background(), "/metadata")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil)
|
|
defer cs.Close() //nolint:errcheck
|
|
|
|
cst := cbor.NewCborStore(bs)
|
|
store := adt.WrapStore(ctx, cst)
|
|
|
|
sm := stmgr.NewStateManager(cs)
|
|
|
|
tree, err := state.LoadStateTree(cst, sroot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
minerInfo := cctx.Bool("miner-info")
|
|
|
|
robustMap := make(map[address.Address]address.Address)
|
|
if cctx.Bool("robust-addresses") {
|
|
iact, err := tree.GetActor(_init.Address)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to load init actor: %w", err)
|
|
}
|
|
|
|
ist, err := _init.Load(store, iact)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to load init actor state: %w", err)
|
|
}
|
|
|
|
err = ist.ForEachActor(func(id abi.ActorID, addr address.Address) error {
|
|
idAddr, err := address.NewIDAddress(uint64(id))
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to write to addr map: %w", err)
|
|
}
|
|
|
|
robustMap[idAddr] = addr
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to invert init address map: %w", err)
|
|
}
|
|
}
|
|
|
|
var infos []accountInfo
|
|
err = tree.ForEach(func(addr address.Address, act *types.Actor) error {
|
|
|
|
ai := accountInfo{
|
|
Address: addr,
|
|
Balance: types.FIL(act.Balance),
|
|
Type: string(act.Code.Hash()[2:]),
|
|
Power: big.NewInt(0),
|
|
LockedFunds: types.FIL(big.NewInt(0)),
|
|
InitialPledge: types.FIL(big.NewInt(0)),
|
|
PreCommits: types.FIL(big.NewInt(0)),
|
|
VestingAmount: types.FIL(big.NewInt(0)),
|
|
}
|
|
|
|
if cctx.Bool("robust-addresses") {
|
|
robust, found := robustMap[addr]
|
|
if found {
|
|
ai.Address = robust
|
|
} else {
|
|
id, err := address.IDFromAddress(addr)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get ID address: %w", err)
|
|
}
|
|
|
|
// TODO: This is not the correctest way to determine whether a robust address should exist
|
|
if id >= genesis.MinerStart {
|
|
return xerrors.Errorf("address doesn't have a robust address: %s", addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
if minerInfo && builtin.IsStorageMinerActor(act.Code) {
|
|
pow, _, _, err := stmgr.GetPowerRaw(ctx, sm, sroot, addr)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get power: %w", err)
|
|
}
|
|
|
|
ai.Power = pow.RawBytePower
|
|
|
|
st, err := miner.Load(store, act)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to read miner state: %w", err)
|
|
}
|
|
|
|
liveSectorCount, err := st.NumLiveSectors()
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to compute live sector count: %w", err)
|
|
}
|
|
|
|
lockedFunds, err := st.LockedFunds()
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to compute locked funds: %w", err)
|
|
}
|
|
|
|
ai.InitialPledge = types.FIL(lockedFunds.InitialPledgeRequirement)
|
|
ai.LockedFunds = types.FIL(lockedFunds.VestingFunds)
|
|
ai.PreCommits = types.FIL(lockedFunds.PreCommitDeposits)
|
|
ai.Sectors = liveSectorCount
|
|
|
|
minfo, err := st.Info()
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to get miner info: %w", err)
|
|
}
|
|
|
|
ai.Worker = minfo.Worker
|
|
ai.Owner = minfo.Owner
|
|
}
|
|
|
|
if builtin.IsMultisigActor(act.Code) {
|
|
mst, err := multisig.Load(store, act)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ai.VestingStart, err = mst.StartEpoch()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ib, err := mst.InitialBalance()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ai.VestingAmount = types.FIL(ib)
|
|
|
|
ai.VestingDuration, err = mst.UnlockDuration()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
infos = append(infos, ai)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to loop over actors: %w", err)
|
|
}
|
|
|
|
printAccountInfos(infos, minerInfo)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func printAccountInfos(infos []accountInfo, minerInfo bool) {
|
|
if minerInfo {
|
|
fmt.Printf("Address,Balance,Type,Sectors,Worker,Owner,InitialPledge,Locked,PreCommits,VestingStart,VestingDuration,VestingAmount\n")
|
|
for _, acc := range infos {
|
|
fmt.Printf("%s,%s,%s,%d,%s,%s,%s,%s,%s,%d,%d,%s\n", acc.Address, acc.Balance.Unitless(), acc.Type, acc.Sectors, acc.Worker, acc.Owner, acc.InitialPledge.Unitless(), acc.LockedFunds.Unitless(), acc.PreCommits.Unitless(), acc.VestingStart, acc.VestingDuration, acc.VestingAmount.Unitless())
|
|
}
|
|
} else {
|
|
fmt.Printf("Address,Balance,Type\n")
|
|
for _, acc := range infos {
|
|
fmt.Printf("%s,%s,%s\n", acc.Address, acc.Balance.Unitless(), acc.Type)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
var chainPledgeCmd = &cli.Command{
|
|
Name: "stateroot-pledge",
|
|
Description: "Calculate sector pledge numbers",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "repo",
|
|
Value: "~/.lotus",
|
|
},
|
|
},
|
|
ArgsUsage: "[stateroot epoch]",
|
|
Action: func(cctx *cli.Context) error {
|
|
logging.SetLogLevel("badger", "ERROR")
|
|
ctx := context.TODO()
|
|
|
|
if !cctx.Args().Present() {
|
|
return fmt.Errorf("must pass state root")
|
|
}
|
|
|
|
sroot, err := cid.Decode(cctx.Args().First())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse input: %w", err)
|
|
}
|
|
|
|
epoch, err := strconv.ParseInt(cctx.Args().Get(1), 10, 64)
|
|
if err != nil {
|
|
return xerrors.Errorf("parsing epoch arg: %w", err)
|
|
}
|
|
|
|
fsrepo, err := repo.NewFS(cctx.String("repo"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lkrepo, err := fsrepo.Lock(repo.FullNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer lkrepo.Close() //nolint:errcheck
|
|
|
|
bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to open blockstore: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
if c, ok := bs.(io.Closer); ok {
|
|
if err := c.Close(); err != nil {
|
|
log.Warnf("failed to close blockstore: %s", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
mds, err := lkrepo.Datastore(context.Background(), "/metadata")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil)
|
|
defer cs.Close() //nolint:errcheck
|
|
|
|
cst := cbor.NewCborStore(bs)
|
|
store := adt.WrapStore(ctx, cst)
|
|
|
|
sm := stmgr.NewStateManager(cs)
|
|
|
|
state, err := state.LoadStateTree(cst, sroot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
powerSmoothed builtin.FilterEstimate
|
|
pledgeCollateral abi.TokenAmount
|
|
)
|
|
if act, err := state.GetActor(power.Address); err != nil {
|
|
return xerrors.Errorf("loading miner actor: %w", err)
|
|
} else if s, err := power.Load(store, act); err != nil {
|
|
return xerrors.Errorf("loading power actor state: %w", err)
|
|
} else if p, err := s.TotalPowerSmoothed(); err != nil {
|
|
return xerrors.Errorf("failed to determine total power: %w", err)
|
|
} else if c, err := s.TotalLocked(); err != nil {
|
|
return xerrors.Errorf("failed to determine pledge collateral: %w", err)
|
|
} else {
|
|
powerSmoothed = p
|
|
pledgeCollateral = c
|
|
}
|
|
|
|
circ, err := sm.GetVMCirculatingSupplyDetailed(ctx, abi.ChainEpoch(epoch), state)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Println("(real) circulating supply: ", types.FIL(circ.FilCirculating))
|
|
if circ.FilCirculating.LessThan(big.Zero()) {
|
|
circ.FilCirculating = big.Zero()
|
|
}
|
|
|
|
rewardActor, err := state.GetActor(reward.Address)
|
|
if err != nil {
|
|
return xerrors.Errorf("loading miner actor: %w", err)
|
|
}
|
|
|
|
rewardState, err := reward.Load(store, rewardActor)
|
|
if err != nil {
|
|
return xerrors.Errorf("loading reward actor state: %w", err)
|
|
}
|
|
|
|
fmt.Println("FilVested", types.FIL(circ.FilVested))
|
|
fmt.Println("FilMined", types.FIL(circ.FilMined))
|
|
fmt.Println("FilBurnt", types.FIL(circ.FilBurnt))
|
|
fmt.Println("FilLocked", types.FIL(circ.FilLocked))
|
|
fmt.Println("FilCirculating", types.FIL(circ.FilCirculating))
|
|
|
|
for _, sectorWeight := range []abi.StoragePower{
|
|
types.NewInt(32 << 30),
|
|
types.NewInt(64 << 30),
|
|
types.NewInt(32 << 30 * 10),
|
|
types.NewInt(64 << 30 * 10),
|
|
} {
|
|
initialPledge, err := rewardState.InitialPledgeForPower(
|
|
sectorWeight,
|
|
pledgeCollateral,
|
|
&powerSmoothed,
|
|
circ.FilCirculating,
|
|
)
|
|
if err != nil {
|
|
return xerrors.Errorf("calculating initial pledge: %w", err)
|
|
}
|
|
|
|
fmt.Println("IP ", units.HumanSize(float64(sectorWeight.Uint64())), types.FIL(initialPledge))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
const dateFmt = "1/02/06"
|
|
|
|
func parseCsv(inp string) ([]time.Time, []address.Address, error) {
|
|
fi, err := os.Open(inp)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
r := csv.NewReader(fi)
|
|
recs, err := r.ReadAll()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var addrs []address.Address
|
|
for _, rec := range recs[1:] {
|
|
a, err := address.NewFromString(rec[0])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
addrs = append(addrs, a)
|
|
}
|
|
|
|
var dates []time.Time
|
|
for _, d := range recs[0][1:] {
|
|
if len(d) == 0 {
|
|
continue
|
|
}
|
|
p := strings.Split(d, " ")
|
|
t, err := time.Parse(dateFmt, p[len(p)-1])
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
dates = append(dates, t)
|
|
}
|
|
|
|
return dates, addrs, nil
|
|
}
|
|
|
|
func heightForDate(d time.Time, ts *types.TipSet) abi.ChainEpoch {
|
|
secs := d.Unix()
|
|
gents := ts.Blocks()[0].Timestamp
|
|
gents -= uint64(30 * ts.Height())
|
|
return abi.ChainEpoch((secs - int64(gents)) / 30)
|
|
}
|
|
|
|
var fillBalancesCmd = &cli.Command{
|
|
Name: "fill-balances",
|
|
Description: "fill out balances for addresses on dates in given spreadsheet",
|
|
Flags: []cli.Flag{},
|
|
Action: func(cctx *cli.Context) error {
|
|
api, closer, err := lcli.GetFullNodeAPI(cctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer closer()
|
|
ctx := lcli.ReqContext(cctx)
|
|
|
|
dates, addrs, err := parseCsv(cctx.Args().First())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ts, err := api.ChainHead(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var tipsets []*types.TipSet
|
|
for _, d := range dates {
|
|
h := heightForDate(d, ts)
|
|
hts, err := api.ChainGetTipSetByHeight(ctx, h, ts.Key())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tipsets = append(tipsets, hts)
|
|
}
|
|
|
|
var balances [][]abi.TokenAmount
|
|
for _, a := range addrs {
|
|
var b []abi.TokenAmount
|
|
for _, hts := range tipsets {
|
|
act, err := api.StateGetActor(ctx, a, hts.Key())
|
|
if err != nil {
|
|
if !strings.Contains(err.Error(), "actor not found") {
|
|
return fmt.Errorf("error for %s at %s: %w", a, hts.Key(), err)
|
|
}
|
|
b = append(b, types.NewInt(0))
|
|
continue
|
|
}
|
|
b = append(b, act.Balance)
|
|
}
|
|
balances = append(balances, b)
|
|
}
|
|
|
|
var datestrs []string
|
|
for _, d := range dates {
|
|
datestrs = append(datestrs, "Balance at "+d.Format(dateFmt))
|
|
}
|
|
|
|
w := csv.NewWriter(os.Stdout)
|
|
w.Write(append([]string{"Wallet Address"}, datestrs...)) // nolint:errcheck
|
|
for i := 0; i < len(addrs); i++ {
|
|
row := []string{addrs[i].String()}
|
|
for _, b := range balances[i] {
|
|
row = append(row, types.FIL(b).String())
|
|
}
|
|
w.Write(row) // nolint:errcheck
|
|
}
|
|
w.Flush()
|
|
return nil
|
|
},
|
|
}
|