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"
)
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) {
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()
// 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
ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
@ -41,14 +41,15 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
bstate := ts.ParentState()
bheight := ts.Height()
// If we have to run a migration, and we're not at genesis, return an
// error because the migration will take too long.
// If we have to run an expensive migration, and we're not at genesis,
// return an error because the migration will take too long.
//
// We allow this at height 0 for at-genesis migrations (for testing).
if bheight-1 > 0 && sm.hasStateFork(ctx, bheight-1) {
return nil, ErrWouldFork
if bheight-1 > 0 && sm.hasExpensiveFork(ctx, bheight-1) {
return nil, ErrExpensiveFork
}
// Run the (not expensive) migration.
bstate, err := sm.handleStateForks(ctx, bstate, bheight-1, nil, ts)
if err != nil {
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_
// height to have no fork, because we'll run it inside this
// 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
ts, err = sm.cs.GetTipSetFromKey(ts.Parents())
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.
if ts.Height() > 0 && (sm.hasStateFork(ctx, ts.Height()) || sm.hasStateFork(ctx, ts.Height()-1)) {
return nil, ErrWouldFork
// When we're not at the genesis block, make sure we don't have an expensive migration.
if ts.Height() > 0 && (sm.hasExpensiveFork(ctx, ts.Height()) || sm.hasExpensiveFork(ctx, ts.Height()-1)) {
return nil, ErrExpensiveFork
}
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 {
Height abi.ChainEpoch
Network network.Version
Expensive bool
Migration UpgradeFunc
}
@ -68,6 +69,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule {
}, {
Height: build.UpgradeActorsV2Height,
Network: network.Version4,
Expensive: true,
Migration: UpgradeActorsV2,
}, {
Height: build.UpgradeLiftoffHeight,
@ -124,8 +126,8 @@ func (sm *StateManager) handleStateForks(ctx context.Context, root cid.Cid, heig
return retCid, nil
}
func (sm *StateManager) hasStateFork(ctx context.Context, height abi.ChainEpoch) bool {
_, ok := sm.stateMigrations[height]
func (sm *StateManager) hasExpensiveFork(ctx context.Context, height abi.ChainEpoch) bool {
_, ok := sm.expensiveUpgrades[height]
return ok
}

View File

@ -247,8 +247,9 @@ func TestForkRefuseCall(t *testing.T) {
sm, err := NewStateManagerWithUpgradeSchedule(
cg.ChainStore(), UpgradeSchedule{{
Network: 1,
Height: testForkHeight,
Network: 1,
Expensive: true,
Height: testForkHeight,
Migration: func(ctx context.Context, sm *StateManager, cb ExecCallback,
root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) {
return root, nil
@ -297,7 +298,7 @@ func TestForkRefuseCall(t *testing.T) {
switch ts.TipSet.TipSet().Height() {
case testForkHeight, testForkHeight + 1:
// 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:
require.NoError(t, err)
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())
switch ts.TipSet.TipSet().Height() {
case testForkHeight + 1:
require.Equal(t, ErrWouldFork, err)
require.Equal(t, ErrExpensiveFork, err)
default:
require.NoError(t, err)
require.True(t, ret.MsgRct.ExitCode.IsSuccess())

View File

@ -53,6 +53,10 @@ type StateManager struct {
// Maps chain epochs to upgrade functions.
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
compWait map[string]chan struct{}
@ -78,6 +82,7 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
}
stateMigrations := make(map[abi.ChainEpoch]UpgradeFunc, len(us))
expensiveUpgrades := make(map[abi.ChainEpoch]struct{}, len(us))
var networkVersions []versionSpec
lastVersion := network.Version0
if len(us) > 0 {
@ -87,6 +92,9 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
if upgrade.Migration != nil {
stateMigrations[upgrade.Height] = upgrade.Migration
}
if upgrade.Expensive {
expensiveUpgrades[upgrade.Height] = struct{}{}
}
networkVersions = append(networkVersions, versionSpec{
networkVersion: lastVersion,
atOrBelow: upgrade.Height,
@ -99,13 +107,14 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule
}
return &StateManager{
networkVersions: networkVersions,
latestVersion: lastVersion,
stateMigrations: stateMigrations,
newVM: vm.NewVM,
cs: cs,
stCache: make(map[string][]cid.Cid),
compWait: make(map[string]chan struct{}),
networkVersions: networkVersions,
latestVersion: lastVersion,
stateMigrations: stateMigrations,
expensiveUpgrades: expensiveUpgrades,
newVM: vm.NewVM,
cs: cs,
stCache: make(map[string][]cid.Cid),
compWait: make(map[string]chan struct{}),
}, nil
}

View File

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