From c9384245ae9de373f6e35465c036d4525a2b27c6 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Fri, 2 Oct 2020 17:40:15 -0400 Subject: [PATCH] Add an endpoint for precise circularting supply --- api/api_full.go | 6 +- api/apistruct/struct.go | 5 ++ chain/actors/builtin/builtin.go | 19 ++++++ chain/actors/builtin/miner/miner.go | 5 ++ chain/actors/builtin/miner/v0.go | 12 +++- chain/stmgr/forks.go | 21 +++---- chain/stmgr/stmgr.go | 91 +++++++++++++++++++++++++++++ cli/state.go | 37 ++++++++---- documentation/en/api-methods.md | 24 ++++++++ node/impl/full/state.go | 13 +++++ 10 files changed, 208 insertions(+), 25 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 88f18943c0b..9bbfe31db3f 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -407,8 +407,12 @@ type FullNode interface { // can issue. It takes the deal size and verified status as parameters. StateDealProviderCollateralBounds(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (DealCollateralBounds, error) - // StateCirculatingSupply returns the circulating supply of Filecoin at the given tipset + // StateCirculatingSupply returns an approximation of the circulating supply of Filecoin at the given tipset. + // This is the value used by the protocol when calculating pledge collateral. StateCirculatingSupply(context.Context, types.TipSetKey) (CirculatingSupply, error) + // StateExactCirculatingSupply returns the exact circulating supply of Filecoin at the given tipset. + // This is not used anywhere in the protocol itself, and is only for external consumption. + StateExactCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error) // StateNetworkVersion returns the network version at the given tipset StateNetworkVersion(context.Context, types.TipSetKey) (network.Version, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index a09700eb956..1a06e4eeb06 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -209,6 +209,7 @@ type FullNodeStruct struct { StateVerifiedRegistryRootKey func(ctx context.Context, tsk types.TipSetKey) (address.Address, error) `perm:"read"` StateDealProviderCollateralBounds func(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (api.DealCollateralBounds, error) `perm:"read"` StateCirculatingSupply func(context.Context, types.TipSetKey) (api.CirculatingSupply, error) `perm:"read"` + StateExactCirculatingSupply func(context.Context, types.TipSetKey) (abi.TokenAmount, error) `perm:"read"` StateNetworkVersion func(context.Context, types.TipSetKey) (stnetwork.Version, error) `perm:"read"` MsigGetAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"` @@ -925,6 +926,10 @@ func (c *FullNodeStruct) StateCirculatingSupply(ctx context.Context, tsk types.T return c.Internal.StateCirculatingSupply(ctx, tsk) } +func (c *FullNodeStruct) StateExactCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) { + return c.Internal.StateExactCirculatingSupply(ctx, tsk) +} + func (c *FullNodeStruct) StateNetworkVersion(ctx context.Context, tsk types.TipSetKey) (stnetwork.Version, error) { return c.Internal.StateNetworkVersion(ctx, tsk) } diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index a85b4da65ef..f393bac6eb9 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -3,6 +3,10 @@ package builtin import ( "fmt" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" proof0 "github.com/filecoin-project/specs-actors/actors/runtime/proof" @@ -12,6 +16,12 @@ import ( "github.com/filecoin-project/go-state-types/network" ) +var SystemActorAddr = builtin0.SystemActorAddr +var BurntFundsActorAddr = builtin0.BurntFundsActorAddr +var CronActorAddr = builtin0.CronActorAddr +var SaftAddress = makeAddress("t0122") +var ReserveAddress = makeAddress("t090") + type Version int const ( @@ -41,3 +51,12 @@ func FromV0FilterEstimate(v0 smoothing0.FilterEstimate) FilterEstimate { func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { return miner0.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) } + +func makeAddress(addr string) address.Address { + ret, err := address.NewFromString(addr) + if err != nil { + panic(err) + } + + return ret +} diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 5ea5cfc81e1..7db47fc02b3 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -1,6 +1,7 @@ package miner import ( + "github.com/filecoin-project/go-state-types/big" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p-core/peer" cbg "github.com/whyrusleeping/cbor-gen" @@ -171,3 +172,7 @@ type LockedFunds struct { InitialPledgeRequirement abi.TokenAmount PreCommitDeposits abi.TokenAmount } + +func (lf LockedFunds) TotalLockedFunds() abi.TokenAmount { + return big.Add(lf.VestingFunds, big.Add(lf.InitialPledgeRequirement, lf.PreCommitDeposits)) +} diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index f5aa7849d16..1313cda7e9d 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -35,8 +35,16 @@ type partition0 struct { store adt.Store } -func (s *state0) AvailableBalance(bal abi.TokenAmount) (abi.TokenAmount, error) { - return s.GetAvailableBalance(bal), nil +func (s *state0) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available = s.GetAvailableBalance(bal) + return available, err } func (s *state0) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 252b731d713..030dd59a6e6 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -6,6 +6,8 @@ import ( "encoding/binary" "math" + "github.com/filecoin-project/lotus/chain/actors/builtin" + multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" @@ -150,11 +152,6 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal return cid.Undef, xerrors.Errorf("loading state tree failed: %w", err) } - ReserveAddress, err := address.NewFromString("t090") - if err != nil { - return cid.Undef, xerrors.Errorf("failed to parse reserve address: %w", err) - } - tree, err := sm.StateTree(root) if err != nil { return cid.Undef, xerrors.Errorf("getting state tree: %w", err) @@ -180,7 +177,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal if !sysAcc { transfers = append(transfers, transfer{ From: addr, - To: ReserveAddress, + To: builtin.ReserveAddress, Amt: act.Balance, }) } @@ -204,7 +201,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal transfers = append(transfers, transfer{ From: addr, - To: ReserveAddress, + To: builtin.ReserveAddress, Amt: available, }) } @@ -255,7 +252,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal nbalance := big.Min(prevBalance, AccountCap) if nbalance.Sign() != 0 { transfersBack = append(transfersBack, transfer{ - From: ReserveAddress, + From: builtin.ReserveAddress, To: addr, Amt: nbalance, }) @@ -282,7 +279,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal mfunds := minerFundsAlloc(power, totalPower) transfersBack = append(transfersBack, transfer{ - From: ReserveAddress, + From: builtin.ReserveAddress, To: minfo.Worker, Amt: mfunds, }) @@ -302,7 +299,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal if lbsectors.Length() > 0 { transfersBack = append(transfersBack, transfer{ - From: ReserveAddress, + From: builtin.ReserveAddress, To: minfo.Worker, Amt: BaseMinerBalance, }) @@ -329,7 +326,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal if err != nil { return cid.Undef, xerrors.Errorf("failed to load burnt funds actor: %w", err) } - if err := doTransfer(cb, tree, builtin0.BurntFundsActorAddr, ReserveAddress, burntAct.Balance); err != nil { + if err := doTransfer(cb, tree, builtin0.BurntFundsActorAddr, builtin.ReserveAddress, burntAct.Balance); err != nil { return cid.Undef, xerrors.Errorf("failed to unburn funds: %w", err) } @@ -345,7 +342,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal } difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) - if err := doTransfer(cb, tree, ReserveAddress, reimbAddr, difference); err != nil { + if err := doTransfer(cb, tree, builtin.ReserveAddress, reimbAddr, difference); err != nil { return cid.Undef, xerrors.Errorf("failed to top up reimbursement account: %w", err) } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 90e43ebbcf9..974897daa62 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -5,6 +5,12 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" @@ -1257,6 +1263,91 @@ func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.Cha return csi.FilCirculating, nil } +func (sm *StateManager) GetExactCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + circ := big.Zero() + unCirc := big.Zero() + err := st.ForEach(func(a address.Address, actor *types.Actor) error { + switch { + case a == _init.Address || + a == reward.Address || + a == verifreg.Address || + // The power actor itself should never receive funds + a == power.Address || + a == builtin.SystemActorAddr || + a == builtin.CronActorAddr || + a == builtin.BurntFundsActorAddr || + a == builtin.SaftAddress || + a == builtin.ReserveAddress: + + unCirc = big.Add(unCirc, actor.Balance) + + case a == market.Address: + mst, err := market.Load(sm.cs.Store(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.TotalLocked() + if err != nil { + return err + } + + circ = big.Add(circ, big.Sub(actor.Balance, lb)) + unCirc = big.Add(unCirc, lb) + + case actor.IsAccountActor() || actor.IsPaymentChannelActor(): + circ = big.Add(circ, actor.Balance) + + case actor.IsStorageMinerActor(): + mst, err := miner.Load(sm.cs.Store(ctx), actor) + if err != nil { + return err + } + + ab, err := mst.AvailableBalance(actor.Balance) + + if err == nil { + circ = big.Add(circ, ab) + unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) + } else { + // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) + // In this case, the actor's entire balance is considered "uncirculating" + unCirc = big.Add(unCirc, actor.Balance) + } + + case actor.IsMultisigActor(): + mst, err := multisig.Load(sm.cs.Store(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.LockedBalance(height) + if err != nil { + return err + } + + ab := big.Sub(actor.Balance, lb) + circ = big.Add(circ, big.Max(ab, big.Zero())) + unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) + default: + return xerrors.Errorf("unexpected actor: %s", a) + } + + return nil + }) + + if err != nil { + return types.EmptyInt, err + } + + total := big.Add(circ, unCirc) + if !total.Equals(types.TotalFilecoinInt) { + return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) + } + + return circ, nil +} + func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { // TODO: move hard fork epoch checks to a schedule defined in build/ diff --git a/cli/state.go b/cli/state.go index 7baf57df269..1526ea6a443 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1608,7 +1608,14 @@ func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, er var stateCircSupplyCmd = &cli.Command{ Name: "circulating-supply", - Usage: "Get the current circulating supply of filecoin", + Usage: "Get the exact current circulating supply of Filecoin", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "vm-supply", + Usage: "calculates the approximation of the circulating supply used internally by the VM (instead of the exact amount)", + Value: false, + }, + }, Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) if err != nil { @@ -1623,16 +1630,26 @@ var stateCircSupplyCmd = &cli.Command{ return err } - circ, err := api.StateCirculatingSupply(ctx, ts.Key()) - if err != nil { - return err - } + if cctx.IsSet("vm-supply") { + circ, err := api.StateCirculatingSupply(ctx, ts.Key()) + if err != nil { + return err + } + + fmt.Println("Circulating supply: ", types.FIL(circ.FilCirculating)) + fmt.Println("Mined: ", types.FIL(circ.FilMined)) + fmt.Println("Vested: ", types.FIL(circ.FilVested)) + fmt.Println("Burnt: ", types.FIL(circ.FilBurnt)) + fmt.Println("Locked: ", types.FIL(circ.FilLocked)) + } else { + circ, err := api.StateExactCirculatingSupply(ctx, ts.Key()) + if err != nil { + return err + } - fmt.Println("Circulating supply: ", types.FIL(circ.FilCirculating)) - fmt.Println("Mined: ", types.FIL(circ.FilMined)) - fmt.Println("Vested: ", types.FIL(circ.FilVested)) - fmt.Println("Burnt: ", types.FIL(circ.FilBurnt)) - fmt.Println("Locked: ", types.FIL(circ.FilLocked)) + fmt.Println("Exact circulating supply: ", types.FIL(circ)) + return nil + } return nil }, diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index 810e6ffa463..226e5edd3e0 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -129,6 +129,7 @@ * [StateCirculatingSupply](#StateCirculatingSupply) * [StateCompute](#StateCompute) * [StateDealProviderCollateralBounds](#StateDealProviderCollateralBounds) + * [StateExactCirculatingSupply](#StateExactCirculatingSupply) * [StateGetActor](#StateGetActor) * [StateGetReceipt](#StateGetReceipt) * [StateListActors](#StateListActors) @@ -3100,6 +3101,29 @@ Response: } ``` +### StateExactCirculatingSupply +TODO: Remove StateCirculatingSupply maybe? +StateExactCirculatingSupply returns the exact circulating supply of Filecoin at the given tipset + + +Perms: read + +Inputs: +```json +[ + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: `"0"` + ### StateGetActor StateGetActor returns the indicated actor's nonce and balance. diff --git a/node/impl/full/state.go b/node/impl/full/state.go index e5bd9f9d88c..fbd8f1642c9 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -1169,6 +1169,19 @@ func (a *StateAPI) StateCirculatingSupply(ctx context.Context, tsk types.TipSetK return a.StateManager.GetCirculatingSupplyDetailed(ctx, ts.Height(), sTree) } +func (a *StateAPI) StateExactCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) { + ts, err := a.Chain.GetTipSetFromKey(tsk) + if err != nil { + return types.EmptyInt, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + + sTree, err := a.stateForTs(ctx, ts) + if err != nil { + return types.EmptyInt, err + } + return a.StateManager.GetExactCirculatingSupply(ctx, ts.Height(), sTree) +} + func (a *StateAPI) StateNetworkVersion(ctx context.Context, tsk types.TipSetKey) (network.Version, error) { ts, err := a.Chain.GetTipSetFromKey(tsk) if err != nil {