Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement faucet funds reallocation logic #3632

Merged
merged 2 commits into from
Sep 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 301 additions & 1 deletion chain/stmgr/forks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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", 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
}
Loading