From fbcfa7c1c0c897460b61b586750f059143dc18da 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 circulating supply --- api/api_full.go | 8 ++- api/apistruct/struct.go | 9 ++- chain/actors/builtin/builtin.go | 15 ++++- chain/actors/builtin/miner/miner.go | 5 ++ chain/actors/builtin/miner/v0.go | 12 +++- chain/actors/builtin/miner/v2.go | 12 +++- chain/stmgr/forks.go | 19 +++--- chain/stmgr/stmgr.go | 90 ++++++++++++++++++++++++++++- cli/state.go | 37 ++++++++---- cmd/lotus-chainwatch/syncer/sync.go | 2 +- cmd/tvx/extract.go | 2 +- documentation/en/api-methods.md | 47 +++++++++++---- node/impl/full/state.go | 19 +++++- 13 files changed, 225 insertions(+), 52 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index a2fe94ee969..c660bc6c80d 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -414,8 +414,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(context.Context, types.TipSetKey) (CirculatingSupply, error) + // StateCirculatingSupply 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. + StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error) + // StateVMCirculatingSupply returns an approximation of the circulating supply of Filecoin at the given tipset. + // This is the value reported by the runtime interface to actors code. + StateVMCirculatingSupply(context.Context, types.TipSetKey) (CirculatingSupply, 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 22d50e72624..2f548462ad8 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -210,7 +210,8 @@ type FullNodeStruct struct { StateVerifiedClientStatus func(context.Context, address.Address, types.TipSetKey) (*abi.StoragePower, error) `perm:"read"` 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"` + StateCirculatingSupply func(context.Context, types.TipSetKey) (abi.TokenAmount, error) `perm:"read"` + StateVMCirculatingSupply func(context.Context, types.TipSetKey) (api.CirculatingSupply, 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"` @@ -963,10 +964,14 @@ func (c *FullNodeStruct) StateDealProviderCollateralBounds(ctx context.Context, return c.Internal.StateDealProviderCollateralBounds(ctx, size, verified, tsk) } -func (c *FullNodeStruct) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { +func (c *FullNodeStruct) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (abi.TokenAmount, error) { return c.Internal.StateCirculatingSupply(ctx, tsk) } +func (c *FullNodeStruct) StateVMCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { + return c.Internal.StateVMCirculatingSupply(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 cb24a2c333d..6725f5c0c7a 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -2,28 +2,37 @@ package builtin import ( "github.com/filecoin-project/go-address" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" "github.com/ipfs/go-cid" "golang.org/x/xerrors" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/cbor" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/types" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" proof0 "github.com/filecoin-project/specs-actors/actors/runtime/proof" smoothing0 "github.com/filecoin-project/specs-actors/actors/util/smoothing" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" - smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" ) var SystemActorAddr = builtin0.SystemActorAddr var BurntFundsActorAddr = builtin0.BurntFundsActorAddr +var CronActorAddr = builtin0.CronActorAddr +var SaftAddress = makeAddress("t0122") var ReserveAddress = makeAddress("t090") var RootVerifierAddress = makeAddress("t080") +type Version int + +const ( + Version0 = iota +) + // TODO: Why does actors have 2 different versions of this? type SectorInfo = proof0.SectorInfo type PoStProof = proof0.PoStProof diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 8649d4351e2..a8217e07823 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" @@ -182,3 +183,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 7e71c7611e9..14718d0027e 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -47,8 +47,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/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index eed82257fc1..9e599f8913a 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -45,8 +45,16 @@ type partition2 struct { store adt.Store } -func (s *state2) AvailableBalance(bal abi.TokenAmount) (abi.TokenAmount, error) { - return s.GetAvailableBalance(bal) +func (s *state2) 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, err = s.GetAvailableBalance(bal) + return available, err } func (s *state2) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index fba92ee3fac..a95b5c2a026 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -262,11 +262,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) @@ -292,7 +287,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, }) } @@ -316,7 +311,7 @@ func UpgradeFaucetBurnRecovery(ctx context.Context, sm *StateManager, cb ExecCal transfers = append(transfers, transfer{ From: addr, - To: ReserveAddress, + To: builtin.ReserveAddress, Amt: available, }) } @@ -367,7 +362,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, }) @@ -394,7 +389,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, }) @@ -414,7 +409,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, }) @@ -441,7 +436,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) } @@ -457,7 +452,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 d6b6f436043..f4bf659ea4b 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -7,6 +7,11 @@ import ( "sync" "github.com/filecoin-project/lotus/chain/actors/builtin" + "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" @@ -1357,12 +1362,91 @@ func (sm *StateManager) GetCirculatingSupplyDetailed(ctx context.Context, height } func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { - csi, err := sm.GetCirculatingSupplyDetailed(ctx, height, st) + circ := big.Zero() + unCirc := big.Zero() + err := st.ForEach(func(a address.Address, actor *types.Actor) error { + switch { + case actor.Balance.IsZero(): + // Do nothing for zero-balance actors + break + 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 builtin.IsAccountActor(actor.Code) || builtin.IsPaymentChannelActor(actor.Code): + circ = big.Add(circ, actor.Balance) + + case builtin.IsStorageMinerActor(actor.Code): + 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 builtin.IsMultisigActor(actor.Code): + 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 big.Zero(), err + 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 csi.FilCirculating, nil + return circ, nil } func (sm *StateManager) GetNtwkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { diff --git a/cli/state.go b/cli/state.go index 453cde77f0d..5acdf940f9a 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1688,7 +1688,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 { @@ -1703,16 +1710,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.StateVMCirculatingSupply(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.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)) + fmt.Println("Exact circulating supply: ", types.FIL(circ)) + return nil + } return nil }, diff --git a/cmd/lotus-chainwatch/syncer/sync.go b/cmd/lotus-chainwatch/syncer/sync.go index 609b7108867..d0931a978d0 100644 --- a/cmd/lotus-chainwatch/syncer/sync.go +++ b/cmd/lotus-chainwatch/syncer/sync.go @@ -316,7 +316,7 @@ limit 1 } func (s *Syncer) storeCirculatingSupply(ctx context.Context, tipset *types.TipSet) error { - supply, err := s.node.StateCirculatingSupply(ctx, tipset.Key()) + supply, err := s.node.StateVMCirculatingSupply(ctx, tipset.Key()) if err != nil { return err } diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index b0ed574df87..cc4494d0f4b 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -135,7 +135,7 @@ func doExtract(ctx context.Context, fapi api.FullNode, opts extractOpts) error { } // get the circulating supply before the message was executed. - circSupplyDetail, err := fapi.StateCirculatingSupply(ctx, incTs.Key()) + circSupplyDetail, err := fapi.StateVMCirculatingSupply(ctx, incTs.Key()) if err != nil { return fmt.Errorf("failed while fetching circulating supply: %w", err) } diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index ec8071b57a6..3276f77a80f 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -165,6 +165,7 @@ * [StateSectorGetInfo](#StateSectorGetInfo) * [StateSectorPartition](#StateSectorPartition) * [StateSectorPreCommitInfo](#StateSectorPreCommitInfo) + * [StateVMCirculatingSupply](#StateVMCirculatingSupply) * [StateVerifiedClientStatus](#StateVerifiedClientStatus) * [StateVerifiedRegistryRootKey](#StateVerifiedRegistryRootKey) * [StateVerifierStatus](#StateVerifierStatus) @@ -3094,7 +3095,8 @@ Response: ``` ### StateCirculatingSupply -StateCirculatingSupply returns the circulating supply of Filecoin at the given tipset +StateCirculatingSupply 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. Perms: read @@ -3113,16 +3115,7 @@ Inputs: ] ``` -Response: -```json -{ - "FilVested": "0", - "FilMined": "0", - "FilBurnt": "0", - "FilLocked": "0", - "FilCirculating": "0" -} -``` +Response: `"0"` ### StateCompute StateCompute is a flexible command that applies the given messages on the given tipset. @@ -4265,6 +4258,38 @@ Response: } ``` +### StateVMCirculatingSupply +StateVMCirculatingSupply returns an approximation of the circulating supply of Filecoin at the given tipset. +This is the value reported by the runtime interface to actors code. + + +Perms: read + +Inputs: +```json +[ + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: +```json +{ + "FilVested": "0", + "FilMined": "0", + "FilBurnt": "0", + "FilLocked": "0", + "FilCirculating": "0" +} +``` + ### StateVerifiedClientStatus StateVerifiedClientStatus returns the data cap for the given address. Returns nil if there is no entry in the data cap table for the diff --git a/node/impl/full/state.go b/node/impl/full/state.go index db91433aaf4..13a968507a1 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -1049,7 +1049,7 @@ func (a *StateAPI) StateMinerInitialPledgeCollateral(ctx context.Context, maddr return types.EmptyInt, xerrors.Errorf("loading reward actor state: %w", err) } - circSupply, err := a.StateCirculatingSupply(ctx, ts.Key()) + circSupply, err := a.StateVMCirculatingSupply(ctx, ts.Key()) if err != nil { return big.Zero(), xerrors.Errorf("getting circulating supply: %w", err) } @@ -1203,7 +1203,7 @@ func (a *StateAPI) StateDealProviderCollateralBounds(ctx context.Context, size a return api.DealCollateralBounds{}, xerrors.Errorf("failed to load reward actor state: %w", err) } - circ, err := a.StateCirculatingSupply(ctx, ts.Key()) + circ, err := a.StateVMCirculatingSupply(ctx, ts.Key()) if err != nil { return api.DealCollateralBounds{}, xerrors.Errorf("getting total circulating supply: %w", err) } @@ -1231,7 +1231,20 @@ func (a *StateAPI) StateDealProviderCollateralBounds(ctx context.Context, size a }, nil } -func (a *StateAPI) StateCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { +func (a *StateAPI) StateCirculatingSupply(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.GetCirculatingSupply(ctx, ts.Height(), sTree) +} + +func (a *StateAPI) StateVMCirculatingSupply(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { ts, err := a.Chain.GetTipSetFromKey(tsk) if err != nil { return api.CirculatingSupply{}, xerrors.Errorf("loading tipset %s: %w", tsk, err)