only forbid Call* at expensive forks

This commit is contained in:
Steven Allen 2020-10-07 16:14:11 -07:00
parent a4e954197c
commit e8253d22c6
6 changed files with 38 additions and 25 deletions

View File

@ -18,7 +18,7 @@ import (
"github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/chain/vm"
) )
var ErrWouldFork = errors.New("refusing explicit call due to state fork at epoch") var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch")
func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.Call") ctx, span := trace.StartSpan(ctx, "statemanager.Call")
@ -29,7 +29,7 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
ts = sm.cs.GetHeaviestTipSet() ts = sm.cs.GetHeaviestTipSet()
// Search back till we find a height with no fork, or we reach the beginning. // Search back till we find a height with no fork, or we reach the beginning.
for ts.Height() > 0 && sm.hasStateFork(ctx, ts.Height()-1) { for ts.Height() > 0 && sm.hasExpensiveFork(ctx, ts.Height()-1) {
var err error var err error
ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil { if err != nil {
@ -41,14 +41,15 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
bstate := ts.ParentState() bstate := ts.ParentState()
bheight := ts.Height() bheight := ts.Height()
// If we have to run a migration, and we're not at genesis, return an // If we have to run an expensive migration, and we're not at genesis,
// error because the migration will take too long. // return an error because the migration will take too long.
// //
// We allow this at height 0 for at-genesis migrations (for testing). // We allow this at height 0 for at-genesis migrations (for testing).
if bheight-1 > 0 && sm.hasStateFork(ctx, bheight-1) { if bheight-1 > 0 && sm.hasExpensiveFork(ctx, bheight-1) {
return nil, ErrWouldFork return nil, ErrExpensiveFork
} }
// Run the (not expensive) migration.
bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts) bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to handle fork: %w", err) return nil, fmt.Errorf("failed to handle fork: %w", err)
@ -133,7 +134,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
// run the fork logic in `sm.TipSetState`. We need the _current_ // run the fork logic in `sm.TipSetState`. We need the _current_
// height to have no fork, because we'll run it inside this // height to have no fork, because we'll run it inside this
// function before executing the given message. // function before executing the given message.
for ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { for ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) {
var err error var err error
ts, err = sm.cs.GetTipSetFromKey(ts.Parents()) ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil { if err != nil {
@ -142,9 +143,9 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
} }
} }
// When we're not at the genesis block, make sure we're at a migration height. // When we're not at the genesis block, make sure we don't have an expensive migration.
if ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) { if ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) {
return nil, ErrWouldFork return nil, ErrExpensiveFork
} }
state, _, err := sm.TipSetState(ctx, ts) state, _, err := sm.TipSetState(ctx, ts)

View File

@ -45,6 +45,7 @@ type UpgradeFunc func(ctx context.Context, sm *StateManager, cb ExecCallback, ol
type Upgrade struct { type Upgrade struct {
Height abi.ChainEpoch Height abi.ChainEpoch
Network network.Version Network network.Version
Expensive bool
Migration UpgradeFunc Migration UpgradeFunc
} }
@ -68,6 +69,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule {
}, { }, {
Height: build.UpgradeActorsV2Height, Height: build.UpgradeActorsV2Height,
Network: network.Version4, Network: network.Version4,
Expensive: true,
Migration: UpgradeActorsV2, Migration: UpgradeActorsV2,
}, { }, {
Height: build.UpgradeLiftoffHeight, Height: build.UpgradeLiftoffHeight,
@ -124,8 +126,8 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig
return retCid, nil return retCid, nil
} }
func (sm *StateManager) hasStateFork(ctx context.Context, height abi.ChainEpoch) bool { func (sm *StateManager) hasExpensiveFork(ctx context.Context, height abi.ChainEpoch) bool {
_, ok := sm.stateMigrations[height] _, ok := sm.expensiveUpgrades[height]
return ok return ok
} }

View File

@ -248,6 +248,7 @@ func TestForkRefuseCall(t *testing.T) {
sm, err := NewStateManagerWithUpgradeSchedule( sm, err := NewStateManagerWithUpgradeSchedule(
cg.ChainStore(), UpgradeSchedule{{ cg.ChainStore(), UpgradeSchedule{{
Network: 1, Network: 1,
Expensive: true,
Height: testForkHeight, Height: testForkHeight,
Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback, Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback,
root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
@ -297,7 +298,7 @@ func TestForkRefuseCall(t *testing.T) {
switch ts.TipSet.TipSet().Height() { switch ts.TipSet.TipSet().Height() {
case testForkHeight, testForkHeight + 1: case testForkHeight, testForkHeight + 1:
// If I had a fork, or I _will_ have a fork, it should fail. // If I had a fork, or I _will_ have a fork, it should fail.
require.Equal(t, ErrWouldFork, err) require.Equal(t, ErrExpensiveFork, err)
default: default:
require.NoError(t, err) require.NoError(t, err)
require.True(t, ret.MsgRct.ExitCode.IsSuccess()) require.True(t, ret.MsgRct.ExitCode.IsSuccess())
@ -307,7 +308,7 @@ func TestForkRefuseCall(t *testing.T) {
ret, err = sm.Call(ctx, m, ts.TipSet.TipSet()) ret, err = sm.Call(ctx, m, ts.TipSet.TipSet())
switch ts.TipSet.TipSet().Height() { switch ts.TipSet.TipSet().Height() {
case testForkHeight + 1: case testForkHeight + 1:
require.Equal(t, ErrWouldFork, err) require.Equal(t, ErrExpensiveFork, err)
default: default:
require.NoError(t, err) require.NoError(t, err)
require.True(t, ret.MsgRct.ExitCode.IsSuccess()) require.True(t, ret.MsgRct.ExitCode.IsSuccess())

View File

@ -53,6 +53,10 @@ type StateManager struct {
// Maps chain epochs to upgrade functions. // Maps chain epochs to upgrade functions.
stateMigrations map[abi.ChainEpoch]UpgradeFunc stateMigrations map[abi.ChainEpoch]UpgradeFunc
// A set of potentially expensive/time consuming upgrades. Explicit
// calls for, e.g., gas estimation fail against this epoch with
// ErrExpensiveFork.
expensiveUpgrades map[abi.ChainEpoch]struct{}
stCache map[string][]cid.Cid stCache map[string][]cid.Cid
compWait map[string]chan struct{} compWait map[string]chan struct{}
@ -78,6 +82,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
} }
stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us)) stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us))
expensiveUpgrades := make(map[abi.ChainEpoch]struct{}, len(us))
var networkVersions []versionSpec var networkVersions []versionSpec
lastVersion := network.Version0 lastVersion := network.Version0
if len(us) > 0 { if len(us) > 0 {
@ -87,6 +92,9 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
if upgrade.Migration != nil { if upgrade.Migration != nil {
stateMigrations[upgrade.Height] = upgrade.Migration stateMigrations[upgrade.Height] = upgrade.Migration
} }
if upgrade.Expensive {
expensiveUpgrades[upgrade.Height] = struct{}{}
}
networkVersions = append(networkVersions, versionSpec{ networkVersions = append(networkVersions, versionSpec{
networkVersion: lastVersion, networkVersion: lastVersion,
atOrBelow: upgrade.Height, atOrBelow: upgrade.Height,
@ -102,6 +110,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
networkVersions: networkVersions, networkVersions: networkVersions,
latestVersion: lastVersion, latestVersion: lastVersion,
stateMigrations: stateMigrations, stateMigrations: stateMigrations,
expensiveUpgrades: expensiveUpgrades,
newVM: vm.NewVM, newVM: vm.NewVM,
cs: cs, cs: cs,
stCache: make(map[string][]cid.Cid), stCache: make(map[string][]cid.Cid),

View File

@ -164,7 +164,7 @@ func (a *GasAPI) GasEstimateGasLimit(ctx context.Context, msgIn *types.Message,
var res *api.InvocResult var res *api.InvocResult
for { for {
res, err = a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts) res, err = a.Stmgr.CallWithGas(ctx, &msg, priorMsgs, ts)
if err != stmgr.ErrWouldFork { if err != stmgr.ErrExpensiveFork {
break break
} }
ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) ts, err = a.Chain.GetTipSetFromKey(ts.Parents())

View File

@ -314,7 +314,7 @@ func (a *StateAPI) StateCall(ctx context.Context, msg *types.Message, tsk types.
} }
for { for {
res, err = a.StateManager.Call(ctx, msg, ts) res, err = a.StateManager.Call(ctx, msg, ts)
if err != stmgr.ErrWouldFork { if err != stmgr.ErrExpensiveFork {
break break
} }
ts, err = a.Chain.GetTipSetFromKey(ts.Parents()) ts, err = a.Chain.GetTipSetFromKey(ts.Parents())