Merge pull request #4545 from filecoin-project/steb/refactor-consistent-tipset-methods

Make state tipset usage consistent in the API
This commit is contained in:
Łukasz Magiera 2021-02-05 21:57:02 +01:00 committed by GitHub
commit ec58099cc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 105 deletions

View File

@ -331,10 +331,14 @@ type FullNode interface {
// MethodGroup: State // MethodGroup: State
// The State methods are used to query, inspect, and interact with chain state. // The State methods are used to query, inspect, and interact with chain state.
// Most methods take a TipSetKey as a parameter. The state looked up is the state at that tipset. // Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset.
// A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used. // A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used.
// StateCall runs the given message and returns its result without any persisted changes. // StateCall runs the given message and returns its result without any persisted changes.
//
// StateCall applies the message to the tipset's parent state. The
// message is not applied on-top-of the messages in the passed-in
// tipset.
StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error) StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error)
// StateReplay replays a given message, assuming it was included in a block in the specified tipset. // StateReplay replays a given message, assuming it was included in a block in the specified tipset.
// If no tipset key is provided, the appropriate tipset is looked up. // If no tipset key is provided, the appropriate tipset is looked up.

View File

@ -89,7 +89,7 @@ func VersionForType(nodeType NodeType) (Version, error) {
// semver versions of the rpc api exposed // semver versions of the rpc api exposed
var ( var (
FullAPIVersion = newVer(1, 0, 0) FullAPIVersion = newVer(1, 1, 0)
MinerAPIVersion = newVer(1, 0, 1) MinerAPIVersion = newVer(1, 0, 1)
WorkerAPIVersion = newVer(1, 0, 0) WorkerAPIVersion = newVer(1, 0, 0)
) )

View File

@ -145,20 +145,6 @@ func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Addres
return mas.GetSector(sid) return mas.GetSector(sid)
} }
func GetMinerSectorSet(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address, snos *bitfield.BitField) ([]*miner.SectorOnChainInfo, error) {
act, err := sm.LoadActor(ctx, maddr, ts)
if err != nil {
return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err)
}
mas, err := miner.Load(sm.cs.Store(ctx), act)
if err != nil {
return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err)
}
return mas.LoadSectors(snos)
}
func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) { func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.SectorInfo, error) {
act, err := sm.LoadActorRaw(ctx, maddr, st) act, err := sm.LoadActorRaw(ctx, maddr, st)
if err != nil { if err != nil {

View File

@ -573,11 +573,14 @@ func TestDuplicateNonce(t *testing.T) {
base := tu.g.CurTipset base := tu.g.CurTipset
// Get the banker from computed tipset state, not the parent.
st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet())
require.NoError(t, err)
ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st)
require.NoError(t, err)
// Produce a message from the banker to the rcvr // Produce a message from the banker to the rcvr
makeMsg := func(rcvr address.Address) *types.SignedMessage { makeMsg := func(rcvr address.Address) *types.SignedMessage {
ba, err := tu.nds[0].StateGetActor(context.TODO(), tu.g.Banker(), base.TipSet().Key())
require.NoError(t, err)
msg := types.Message{ msg := types.Message{
To: rcvr, To: rcvr,
From: tu.g.Banker(), From: tu.g.Banker(),

View File

@ -168,7 +168,7 @@ Response:
```json ```json
{ {
"Version": "string value", "Version": "string value",
"APIVersion": 65536, "APIVersion": 65792,
"BlockDelay": 42 "BlockDelay": 42
} }
``` ```

View File

@ -146,7 +146,7 @@ Perms: admin
Inputs: `null` Inputs: `null`
Response: `65536` Response: `65792`
## Add ## Add

View File

@ -254,7 +254,7 @@ Response:
```json ```json
{ {
"Version": "string value", "Version": "string value",
"APIVersion": 65536, "APIVersion": 65792,
"BlockDelay": 42 "BlockDelay": 42
} }
``` ```
@ -3309,7 +3309,7 @@ Response:
## State ## State
The State methods are used to query, inspect, and interact with chain state. The State methods are used to query, inspect, and interact with chain state.
Most methods take a TipSetKey as a parameter. The state looked up is the state at that tipset. Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset.
A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used. A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used.
@ -3362,6 +3362,10 @@ Response: `null`
### StateCall ### StateCall
StateCall runs the given message and returns its result without any persisted changes. StateCall runs the given message and returns its result without any persisted changes.
StateCall applies the message to the tipset's parent state. The
message is not applied on-top-of the messages in the passed-in
tipset.
Perms: read Perms: read

View File

@ -21,7 +21,6 @@ import (
"github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/gen"
"github.com/filecoin-project/lotus/chain/messagepool/gasguess"
"github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/journal"
@ -506,33 +505,3 @@ func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *type
WinningPoStProof: wpostProof, WinningPoStProof: wpostProof,
}) })
} }
type actCacheEntry struct {
act *types.Actor
err error
}
type cachedActorLookup struct {
tsk types.TipSetKey
cache map[address.Address]actCacheEntry
fallback gasguess.ActorLookup
}
func (c *cachedActorLookup) StateGetActor(ctx context.Context, a address.Address, tsk types.TipSetKey) (*types.Actor, error) {
if c.tsk == tsk {
e, has := c.cache[a]
if has {
return e.act, e.err
}
}
e, err := c.fallback(ctx, a, tsk)
if c.tsk == tsk {
c.cache[a] = actCacheEntry{
act: e, err: err,
}
}
return e, err
}
type ActorLookup func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error)

View File

@ -6,7 +6,6 @@ import (
"strconv" "strconv"
cid "github.com/ipfs/go-cid" cid "github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
"go.uber.org/fx" "go.uber.org/fx"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@ -35,7 +34,6 @@ import (
"github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/chain/wallet"
"github.com/filecoin-project/lotus/lib/bufbstore"
"github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/modules/dtypes"
) )
@ -94,19 +92,20 @@ func (a *StateAPI) StateNetworkName(ctx context.Context) (dtypes.NetworkName, er
} }
func (a *StateAPI) StateMinerSectors(ctx context.Context, addr address.Address, sectorNos *bitfield.BitField, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { func (a *StateAPI) StateMinerSectors(ctx context.Context, addr address.Address, sectorNos *bitfield.BitField, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) {
ts, err := a.Chain.GetTipSetFromKey(tsk) act, err := a.StateManager.LoadActorTsk(ctx, addr, tsk)
if err != nil { if err != nil {
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) return nil, xerrors.Errorf("failed to load miner actor: %w", err)
} }
return stmgr.GetMinerSectorSet(ctx, a.StateManager, ts, addr, sectorNos)
mas, err := miner.Load(a.StateManager.ChainStore().Store(ctx), act)
if err != nil {
return nil, xerrors.Errorf("failed to load miner actor state: %w", err)
}
return mas.LoadSectors(sectorNos)
} }
func (a *StateAPI) StateMinerActiveSectors(ctx context.Context, maddr address.Address, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { // TODO: only used in cli func (a *StateAPI) StateMinerActiveSectors(ctx context.Context, maddr address.Address, tsk types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { // TODO: only used in cli
ts, err := a.Chain.GetTipSetFromKey(tsk)
if err != nil {
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err)
}
act, err := a.StateManager.LoadActorTsk(ctx, maddr, tsk) act, err := a.StateManager.LoadActorTsk(ctx, maddr, tsk)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to load miner actor: %w", err) return nil, xerrors.Errorf("failed to load miner actor: %w", err)
@ -122,7 +121,7 @@ func (a *StateAPI) StateMinerActiveSectors(ctx context.Context, maddr address.Ad
return nil, xerrors.Errorf("merge partition active sets: %w", err) return nil, xerrors.Errorf("merge partition active sets: %w", err)
} }
return stmgr.GetMinerSectorSet(ctx, a.StateManager, ts, maddr, &activeSectors) return mas.LoadSectors(&activeSectors)
} }
func (m *StateModule) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error) { func (m *StateModule) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error) {
@ -426,38 +425,12 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid.
}, nil }, nil
} }
func stateForTs(ctx context.Context, ts *types.TipSet, cstore *store.ChainStore, smgr *stmgr.StateManager) (*state.StateTree, error) {
if ts == nil {
ts = cstore.GetHeaviestTipSet()
}
st, _, err := smgr.TipSetState(ctx, ts)
if err != nil {
return nil, err
}
buf := bufbstore.NewBufferedBstore(cstore.Blockstore())
cst := cbor.NewCborStore(buf)
return state.LoadStateTree(cst, st)
}
func (a *StateAPI) stateForTs(ctx context.Context, ts *types.TipSet) (*state.StateTree, error) {
return stateForTs(ctx, ts, a.Chain, a.StateManager)
}
func (m *StateModule) stateForTs(ctx context.Context, ts *types.TipSet) (*state.StateTree, error) {
return stateForTs(ctx, ts, m.Chain, m.StateManager)
}
func (m *StateModule) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { func (m *StateModule) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) {
ts, err := m.Chain.GetTipSetFromKey(tsk) ts, err := m.Chain.GetTipSetFromKey(tsk)
if err != nil { if err != nil {
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
state, err := m.stateForTs(ctx, ts) return m.StateManager.LoadActor(ctx, actor, ts)
if err != nil {
return nil, xerrors.Errorf("computing tipset state failed: %w", err)
}
return state.GetActor(actor)
} }
func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) {
@ -483,17 +456,12 @@ func (a *StateAPI) StateReadState(ctx context.Context, actor address.Address, ts
if err != nil { if err != nil {
return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
state, err := a.stateForTs(ctx, ts) act, err := a.StateManager.LoadActor(ctx, actor, ts)
if err != nil {
return nil, xerrors.Errorf("getting state for tipset: %w", err)
}
act, err := state.GetActor(actor)
if err != nil { if err != nil {
return nil, xerrors.Errorf("getting actor: %w", err) return nil, xerrors.Errorf("getting actor: %w", err)
} }
blk, err := state.Store.(*cbor.BasicIpldStore).Blocks.Get(act.Head) blk, err := a.Chain.Blockstore().Get(act.Head)
if err != nil { if err != nil {
return nil, xerrors.Errorf("getting actor head: %w", err) return nil, xerrors.Errorf("getting actor head: %w", err)
} }
@ -529,6 +497,7 @@ func (a *StateAPI) StateDecodeParams(ctx context.Context, toAddr address.Address
// This is on StateAPI because miner.Miner requires this, and MinerAPI requires miner.Miner // This is on StateAPI because miner.Miner requires this, and MinerAPI requires miner.Miner
func (a *StateAPI) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) { func (a *StateAPI) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) {
// XXX: Gets the state by computing the tipset state, instead of looking at the parent.
return stmgr.MinerGetBaseInfo(ctx, a.StateManager, a.Beacon, tsk, epoch, maddr, a.ProofVerifier) return stmgr.MinerGetBaseInfo(ctx, a.StateManager, a.Beacon, tsk, epoch, maddr, a.ProofVerifier)
} }
@ -1372,11 +1341,11 @@ func (a *StateAPI) StateCirculatingSupply(ctx context.Context, tsk types.TipSetK
return types.EmptyInt, xerrors.Errorf("loading tipset %s: %w", tsk, err) return types.EmptyInt, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
sTree, err := a.stateForTs(ctx, ts) sTree, err := a.StateManager.ParentState(ts)
if err != nil { if err != nil {
return types.EmptyInt, err return types.EmptyInt, err
} }
return a.StateManager.GetCirculatingSupply(ctx, ts.Height(), sTree) return a.StateManager.GetCirculatingSupply(ctx, ts.Height()-1, sTree)
} }
func (a *StateAPI) StateVMCirculatingSupplyInternal(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) { func (a *StateAPI) StateVMCirculatingSupplyInternal(ctx context.Context, tsk types.TipSetKey) (api.CirculatingSupply, error) {
@ -1393,7 +1362,7 @@ func stateVMCirculatingSupplyInternal(
return api.CirculatingSupply{}, xerrors.Errorf("loading tipset %s: %w", tsk, err) return api.CirculatingSupply{}, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
sTree, err := stateForTs(ctx, ts, cstore, smgr) sTree, err := smgr.ParentState(ts)
if err != nil { if err != nil {
return api.CirculatingSupply{}, err return api.CirculatingSupply{}, err
} }
@ -1407,5 +1376,7 @@ func (m *StateModule) StateNetworkVersion(ctx context.Context, tsk types.TipSetK
return network.VersionMax, xerrors.Errorf("loading tipset %s: %w", tsk, err) return network.VersionMax, xerrors.Errorf("loading tipset %s: %w", tsk, err)
} }
// TODO: Height-1 to be consistent with the rest of the APIs?
// But that's likely going to break a bunch of stuff.
return m.StateManager.GetNtwkVersion(ctx, ts.Height()), nil return m.StateManager.GetNtwkVersion(ctx, ts.Height()), nil
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"go.uber.org/fx" "go.uber.org/fx"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/impl/full"
@ -21,13 +22,41 @@ type MpoolNonceAPI struct {
StateAPI full.StateAPI StateAPI full.StateAPI
} }
// GetNonce gets the nonce from actor state // GetNonce gets the nonce from current chain head.
func (a *MpoolNonceAPI) GetNonce(addr address.Address) (uint64, error) { func (a *MpoolNonceAPI) GetNonce(addr address.Address) (uint64, error) {
act, err := a.StateAPI.StateGetActor(context.Background(), addr, types.EmptyTSK) ts := a.StateAPI.Chain.GetHeaviestTipSet()
// make sure we have a key address so we can compare with messages
keyAddr, err := a.StateAPI.StateManager.ResolveToKeyAddress(context.TODO(), addr, ts)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return act.Nonce, nil
// Load the last nonce from the state, if it exists.
highestNonce := uint64(0)
if baseActor, err := a.StateAPI.StateManager.LoadActorRaw(context.TODO(), addr, ts.ParentState()); err != nil {
if !xerrors.Is(err, types.ErrActorNotFound) {
return 0, err
}
} else {
highestNonce = baseActor.Nonce
}
// Otherwise, find the highest nonce in the tipset.
msgs, err := a.StateAPI.Chain.MessagesForTipset(ts)
if err != nil {
return 0, err
}
for _, msg := range msgs {
vmmsg := msg.VMMessage()
if vmmsg.From != keyAddr {
continue
}
if vmmsg.Nonce >= highestNonce {
highestNonce = vmmsg.Nonce + 1
}
}
return highestNonce, nil
} }
var _ messagesigner.MpoolNonceAPI = (*MpoolNonceAPI)(nil) var _ messagesigner.MpoolNonceAPI = (*MpoolNonceAPI)(nil)

View File

@ -92,6 +92,10 @@ func TestAPIDealFlowReal(t *testing.T) {
} }
func TestDealMining(t *testing.T) { func TestDealMining(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
logging.SetLogLevel("miner", "ERROR") logging.SetLogLevel("miner", "ERROR")
logging.SetLogLevel("chainstore", "ERROR") logging.SetLogLevel("chainstore", "ERROR")
logging.SetLogLevel("chain", "ERROR") logging.SetLogLevel("chain", "ERROR")