diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 0e6fe5122a7..bcc93b70ea4 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -3,11 +3,23 @@ package stmgr import ( "context" + "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/build" + "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/util/adt" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" ) -var ForksAtHeight = map[abi.ChainEpoch]func(context.Context, *StateManager, types.StateTree) error{} +var ForksAtHeight = map[abi.ChainEpoch]func(context.Context, *StateManager, types.StateTree) error{ + 42000: UpgradeFaucetBurnRecovery, +} func (sm *StateManager) handleStateForks(ctx context.Context, st types.StateTree, height abi.ChainEpoch) (err error) { f, ok := ForksAtHeight[height] @@ -20,3 +32,291 @@ func (sm *StateManager) handleStateForks(ctx context.Context, st types.StateTree return nil } + +type forEachTree interface { + ForEach(func(address.Address, *types.Actor) error) error +} + +func doTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmount) error { + fromAct, err := tree.GetActor(from) + if err != nil { + return xerrors.Errorf("failed to get 'from' actor for transfer: %w", err) + } + + fromAct.Balance = types.BigSub(fromAct.Balance, amt) + if fromAct.Balance.Sign() < 0 { + return xerrors.Errorf("(sanity) deducted more funds from target account than it had (%s, %s)", from, types.FIL(amt)) + } + + if err := tree.SetActor(from, fromAct); err != nil { + return xerrors.Errorf("failed to persist from actor: %w", err) + } + + toAct, err := tree.GetActor(to) + if err != nil { + return xerrors.Errorf("failed to get 'to' actor for transfer: %w", err) + } + + toAct.Balance = types.BigAdd(toAct.Balance, amt) + + if err := tree.SetActor(to, toAct); err != nil { + return xerrors.Errorf("failed to persist to actor: %w", err) + } + + return nil +} + +func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, tree types.StateTree) error { + // Some initial parameters + FundsForMiners := types.FromFil(1_000_000) + LookbackEpoch := abi.ChainEpoch(32000) + AccountCap := types.FromFil(0) + BaseMinerBalance := types.FromFil(20) + DesiredReimbursementBalance := types.FromFil(5_000_000) + + isSystemAccount := func(addr address.Address) (bool, error) { + id, err := address.IDFromAddress(addr) + if err != nil { + return false, xerrors.Errorf("id address: %w", err) + } + + if id < 1000 { + return true, nil + } + return false, nil + } + + minerFundsAlloc := func(pow, tpow abi.StoragePower) abi.TokenAmount { + return types.BigDiv(types.BigMul(pow, FundsForMiners), tpow) + } + + // Grab lookback state for account checks + lbts, err := sm.ChainStore().GetTipsetByHeight(ctx, LookbackEpoch, nil, false) + if err != nil { + return xerrors.Errorf("failed to get tipset at lookback height: %w", err) + } + + var lbtree *state.StateTree + if err = sm.WithStateTree(lbts.ParentState(), func(state *state.StateTree) error { + lbtree = state + return nil + }); err != nil { + return xerrors.Errorf("loading state tree failed: %w") + } + + ReserveAddress, err := address.NewFromString("t090") + if err != nil { + return xerrors.Errorf("failed to parse reserve address: %w", err) + } + + fetree, ok := tree.(forEachTree) + if !ok { + return xerrors.Errorf("fork transition state tree doesnt support ForEach (%T)", tree) + } + + type transfer struct { + From address.Address + To address.Address + Amt abi.TokenAmount + } + + var transfers []transfer + + // Take all excess funds away, put them into the reserve account + err = fetree.ForEach(func(addr address.Address, act *types.Actor) error { + switch act.Code { + case builtin.AccountActorCodeID, builtin.MultisigActorCodeID, builtin.PaymentChannelActorCodeID: + sysAcc, err := isSystemAccount(addr) + if err != nil { + return xerrors.Errorf("checking system account: %w", err) + } + + if !sysAcc { + transfers = append(transfers, transfer{ + From: addr, + To: ReserveAddress, + Amt: act.Balance, + }) + } + case builtin.StorageMinerActorCodeID: + var st miner.State + if err := sm.WithActorState(ctx, &st)(act); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var available abi.TokenAmount + { + defer func() { + if err := recover(); err != nil { + log.Warnf("Get available balance failed (%s, %s, %s): %s", addr, act.Head, act.Balance, err) + } + available = abi.NewTokenAmount(0) + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available = st.GetAvailableBalance(act.Balance) + } + + transfers = append(transfers, transfer{ + From: addr, + To: ReserveAddress, + Amt: available, + }) + } + return nil + }) + if err != nil { + return xerrors.Errorf("foreach over state tree failed: %w", err) + } + + // Execute transfers from previous step + for _, t := range transfers { + if err := doTransfer(tree, t.From, t.To, t.Amt); err != nil { + return xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // pull up power table to give miners back some funds proportional to their power + var ps power.State + powAct, err := tree.GetActor(builtin.StoragePowerActorAddr) + if err != nil { + return xerrors.Errorf("failed to load power actor: %w", err) + } + + cst := cbor.NewCborStore(sm.ChainStore().Blockstore()) + if err := cst.Get(ctx, powAct.Head, &ps); err != nil { + return xerrors.Errorf("failed to get power actor state: %w", err) + } + + totalPower := ps.TotalBytesCommitted + + var transfersBack []transfer + // Now, we return some funds to places where they are needed + err = fetree.ForEach(func(addr address.Address, act *types.Actor) error { + lbact, err := lbtree.GetActor(addr) + if err != nil { + if !xerrors.Is(err, types.ErrActorNotFound) { + return xerrors.Errorf("failed to get actor in lookback state") + } + } + + prevBalance := abi.NewTokenAmount(0) + if lbact != nil { + prevBalance = lbact.Balance + } + + switch act.Code { + case builtin.AccountActorCodeID, builtin.MultisigActorCodeID, builtin.PaymentChannelActorCodeID: + nbalance := big.Min(prevBalance, AccountCap) + if nbalance.Sign() != 0 { + transfersBack = append(transfersBack, transfer{ + From: ReserveAddress, + To: addr, + Amt: nbalance, + }) + } + case builtin.StorageMinerActorCodeID: + var st miner.State + if err := sm.WithActorState(ctx, &st)(act); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var minfo miner.MinerInfo + if err := cst.Get(ctx, st.Info, &minfo); err != nil { + return xerrors.Errorf("failed to get miner info: %w", err) + } + + sectorsArr, err := adt.AsArray(sm.ChainStore().Store(ctx), st.Sectors) + if err != nil { + return xerrors.Errorf("failed to load sectors array: %w", err) + } + + slen := sectorsArr.Length() + + power := types.BigMul(types.NewInt(slen), types.NewInt(uint64(minfo.SectorSize))) + + mfunds := minerFundsAlloc(power, totalPower) + transfersBack = append(transfersBack, transfer{ + From: ReserveAddress, + To: minfo.Worker, + Amt: mfunds, + }) + + // Now make sure to give each miner who had power at the lookback some FIL + lbact, err := lbtree.GetActor(addr) + if err == nil { + var lbst miner.State + if err := sm.WithActorState(ctx, &lbst)(lbact); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + lbsectors, err := adt.AsArray(sm.ChainStore().Store(ctx), lbst.Sectors) + if err != nil { + return xerrors.Errorf("failed to load lb sectors array: %w", err) + } + + if lbsectors.Length() > 0 { + transfersBack = append(transfersBack, transfer{ + From: ReserveAddress, + To: minfo.Worker, + Amt: BaseMinerBalance, + }) + } + + } else { + log.Warnf("failed to get miner in lookback state: %s", err) + } + } + return nil + }) + if err != nil { + return xerrors.Errorf("foreach over state tree failed: %w", err) + } + + for _, t := range transfersBack { + if err := doTransfer(tree, t.From, t.To, t.Amt); err != nil { + return xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // transfer all burnt funds back to the reserve account + burntAct, err := tree.GetActor(builtin.BurntFundsActorAddr) + if err != nil { + return xerrors.Errorf("failed to load burnt funds actor: %w", err) + } + if err := doTransfer(tree, builtin.BurntFundsActorAddr, ReserveAddress, burntAct.Balance); err != nil { + return xerrors.Errorf("failed to unburn funds: %w", err) + } + + // Top up the reimbursement service + reimbAddr, err := address.NewFromString("t0111") + if err != nil { + return xerrors.Errorf("failed to parse reimbursement service address") + } + + reimb, err := tree.GetActor(reimbAddr) + if err != nil { + return xerrors.Errorf("failed to load reimbursement account actor: %w", err) + } + + difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) + if err := doTransfer(tree, ReserveAddress, reimbAddr, difference); err != nil { + return xerrors.Errorf("failed to top up reimbursement account: %w", err) + } + + // Now, a final sanity check to make sure the balances all check out + total := abi.NewTokenAmount(0) + err = fetree.ForEach(func(addr address.Address, act *types.Actor) error { + total = types.BigAdd(total, act.Balance) + return nil + }) + if err != nil { + return xerrors.Errorf("checking final state balance failed: %w", err) + } + + exp := types.FromFil(build.FilBase) + if !exp.Equals(total) { + return xerrors.Errorf("resultant state tree account balance was not correct: %s", total) + } + + return nil +} diff --git a/cmd/lotus-shed/balances.go b/cmd/lotus-shed/balances.go new file mode 100644 index 00000000000..297129eb5ed --- /dev/null +++ b/cmd/lotus-shed/balances.go @@ -0,0 +1,242 @@ +package main + +import ( + "context" + "fmt" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "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/lib/blockstore" + "github.com/filecoin-project/lotus/node/repo" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/abi/big" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/miner" + "github.com/filecoin-project/specs-actors/actors/util/adt" +) + +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 +} + +var auditsCmd = &cli.Command{ + Name: "audits", + Description: "a collection of utilities for auditing the filecoin chain", + Subcommands: []*cli.Command{ + chainBalanceCmd, + chainBalanceStateCmd, + }, +} + +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 act.Code == builtin.StorageMinerActorCodeID { + 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) + } + + fmt.Printf("Address,Balance,Type,Power,Worker,Owner\n") + for _, acc := range infos { + fmt.Printf("%s,%s,%s,%s,%s,%s\n", acc.Address, acc.Balance, acc.Type, acc.Power, acc.Worker, acc.Owner) + } + 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", + }, + }, + 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 + + ds, err := lkrepo.Datastore("/chain") + if err != nil { + return err + } + + mds, err := lkrepo.Datastore("/metadata") + if err != nil { + return err + } + + bs := blockstore.NewBlockstore(ds) + + cs := store.NewChainStore(bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier)) + + cst := cbor.NewCborStore(bs) + + sm := stmgr.NewStateManager(cs) + + tree, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err + } + + minerInfo := cctx.Bool("miner-info") + + 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)), + } + + if act.Code == builtin.StorageMinerActorCodeID && minerInfo { + pow, _, err := stmgr.GetPowerRaw(ctx, sm, sroot, addr) + if err != nil { + return xerrors.Errorf("failed to get power: %w", err) + } + + ai.Power = pow.RawBytePower + + var st miner.State + if err := cst.Get(ctx, act.Head, &st); err != nil { + return xerrors.Errorf("failed to read miner state: %w", err) + } + + sectors, err := adt.AsArray(cs.Store(ctx), st.Sectors) + if err != nil { + return xerrors.Errorf("failed to load sector set: %w", err) + } + + ai.InitialPledge = types.FIL(st.InitialPledgeRequirement) + ai.LockedFunds = types.FIL(st.LockedFunds) + ai.PreCommits = types.FIL(st.PreCommitDeposits) + ai.Sectors = sectors.Length() + + var minfo miner.MinerInfo + if err := cst.Get(ctx, st.Info, &minfo); err != nil { + return xerrors.Errorf("failed to read miner info: %w", err) + } + + ai.Worker = minfo.Worker + ai.Owner = minfo.Owner + } + infos = append(infos, ai) + return nil + }) + + if minerInfo { + fmt.Printf("Address,Balance,Type,Sectors,Worker,Owner,InitialPledge,Locked,PreCommits\n") + for _, acc := range infos { + fmt.Printf("%s,%s,%s,%d,%s,%s,%s,%s,%s\n", acc.Address, acc.Balance, acc.Type, acc.Sectors, acc.Worker, acc.Owner, acc.InitialPledge, acc.LockedFunds, acc.PreCommits) + } + } else { + fmt.Printf("Address,Balance,Type\n") + for _, acc := range infos { + fmt.Printf("%s,%s,%s\n", acc.Address, acc.Balance, acc.Type) + } + } + + return nil + }, +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index fb931decfc1..2d0d7c3a0f5 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -23,6 +23,7 @@ func main() { noncefix, bigIntParseCmd, staterootCmd, + auditsCmd, importCarCmd, commpToCidCmd, fetchParamCmd, diff --git a/go.sum b/go.sum index afd23c0c52e..dc9267c4e49 100644 --- a/go.sum +++ b/go.sum @@ -278,6 +278,7 @@ github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZO github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/lotus v0.4.3-0.20200820203717-d1718369a182/go.mod h1:biFZPQ/YyQGfkHUmHMiaNf2hnD6zm1+OAXPQYQ61Zkg= +github.com/filecoin-project/lotus v0.5.8-0.20200902130912-0962292f920e/go.mod h1:OkZ5aUqs+fFnJOq9243WJDsTa9c3/Ae67NIAwVhAB+0= github.com/filecoin-project/lotus v0.5.8-0.20200903221953-ada5e6ae68cf/go.mod h1:wxuzS4ozpCFThia18G+J5P0Jp/DSiq9ezzJF1yvZuP4= github.com/filecoin-project/sector-storage v0.0.0-20200712023225-1d67dcfa3c15/go.mod h1:salgVdX7qeXFo/xaiEQE29J4pPkjn71T0kt0n+VDBzo= github.com/filecoin-project/sector-storage v0.0.0-20200730050024-3ee28c3b6d9a/go.mod h1:oOawOl9Yk+qeytLzzIryjI8iRbqo+qzS6EEeElP4PWA=