323 lines
9.5 KiB
Go
323 lines
9.5 KiB
Go
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, *types.TipSet) error{
|
|
build.UpgradeBreezeHeight: UpgradeFaucetBurnRecovery,
|
|
}
|
|
|
|
func (sm *StateManager) handleStateForks(ctx context.Context, st types.StateTree, height abi.ChainEpoch, ts *types.TipSet) (err error) {
|
|
f, ok := ForksAtHeight[height]
|
|
if ok {
|
|
err := f(ctx, sm, st, ts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
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, ts *types.TipSet) 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, ts, 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", err)
|
|
}
|
|
|
|
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
|
|
}
|