lotus/chain/stmgr/call.go

288 lines
8.0 KiB
Go
Raw Normal View History

package stmgr
import (
"context"
"errors"
2019-09-19 20:25:18 +00:00
"fmt"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/crypto"
2019-10-15 04:33:29 +00:00
"github.com/ipfs/go-cid"
"go.opencensus.io/trace"
2019-10-15 04:33:29 +00:00
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
)
2020-10-07 23:14:11 +00:00
var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch")
// Call applies the given message to the given tipset's parent state, at the epoch following the
// tipset's parent. In the presence of null blocks, the height at which the message is invoked may
// be less than the specified tipset.
//
// - If no tipset is specified, the first tipset without an expensive migration is used.
// - If executing a message at a given tipset would trigger an expensive migration, the call will
// fail with ErrExpensiveFork.
2020-09-30 21:56:16 +00:00
func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.Call")
defer span.End()
var pheight abi.ChainEpoch = -1
// If no tipset is provided, try to find one without a fork.
2020-09-30 21:56:16 +00:00
if ts == nil {
ts = sm.cs.GetHeaviestTipSet()
// Search back till we find a height with no fork, or we reach the beginning.
for ts.Height() > 0 {
pts, err := sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
}
if !sm.hasExpensiveFork(pts.Height()) {
pheight = pts.Height()
break
}
ts = pts
}
} else if ts.Height() > 0 {
pts, err := sm.cs.LoadTipSet(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to load parent tipset: %w", err)
}
pheight = pts.Height()
if sm.hasExpensiveFork(pheight) {
return nil, ErrExpensiveFork
}
} else {
// We can't get the parent tipset in this case.
pheight = ts.Height() - 1
2020-09-30 21:56:16 +00:00
}
bstate := ts.ParentState()
2020-10-07 23:14:11 +00:00
// Run the (not expensive) migration.
2021-09-02 16:07:23 +00:00
bstate, err := sm.HandleStateForks(ctx, bstate, pheight, nil, ts)
2020-09-30 21:56:16 +00:00
if err != nil {
2020-09-30 22:51:11 +00:00
return nil, fmt.Errorf("failed to handle fork: %w", err)
2020-09-30 21:56:16 +00:00
}
vmopt := &vm.VMOpts{
StateBase: bstate,
2021-06-03 20:10:15 +00:00
Epoch: pheight + 1,
2020-09-30 21:56:16 +00:00
Rand: store.NewChainRand(sm.cs, ts.Cids()),
Bstore: sm.cs.StateBlockstore(),
2021-09-02 16:07:23 +00:00
Actors: sm.tsExec.NewActorRegistry(),
Syscalls: sm.Syscalls,
CircSupplyCalc: sm.GetVMCirculatingSupply,
NtwkVersion: sm.GetNtwkVersion,
BaseFee: types.NewInt(0),
LookbackState: LookbackStateGetterForTipset(sm, ts),
}
vmi, err := sm.newVM(ctx, vmopt)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
2020-03-18 20:45:37 +00:00
if msg.GasLimit == 0 {
msg.GasLimit = build.BlockGasLimit
}
if msg.GasFeeCap == types.EmptyInt {
msg.GasFeeCap = types.NewInt(0)
}
if msg.GasPremium == types.EmptyInt {
msg.GasPremium = types.NewInt(0)
}
if msg.Value == types.EmptyInt {
msg.Value = types.NewInt(0)
}
if span.IsRecordingEvents() {
span.AddAttributes(
2020-03-18 20:45:37 +00:00
trace.Int64Attribute("gas_limit", msg.GasLimit),
trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()),
trace.StringAttribute("value", msg.Value.String()),
)
}
fromActor, err := vmi.StateTree().GetActor(msg.From)
if err != nil {
2019-09-30 23:55:35 +00:00
return nil, xerrors.Errorf("call raw get actor: %s", err)
}
msg.Nonce = fromActor.Nonce
// TODO: maybe just use the invoker directly?
ret, err := vmi.ApplyImplicitMessage(ctx, msg)
if err != nil {
return nil, xerrors.Errorf("apply message failed: %w", err)
}
var errs string
if ret.ActorErr != nil {
errs = ret.ActorErr.Error()
log.Warnf("chain call failed: %s", ret.ActorErr)
}
return &api.InvocResult{
2020-10-14 03:45:47 +00:00
MsgCid: msg.Cid(),
Msg: msg,
MsgRct: &ret.MessageReceipt,
ExecutionTrace: ret.ExecutionTrace,
Error: errs,
Duration: ret.Duration,
}, nil
}
func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet) (*api.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.CallWithGas")
defer span.End()
if ts == nil {
ts = sm.cs.GetHeaviestTipSet()
// Search back till we find a height with no fork, or we reach the beginning.
// We need the _previous_ height to have no fork, because we'll
// 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 {
pts, err := sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
}
if !sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
break
}
ts = pts
}
} else if ts.Height() > 0 {
pts, err := sm.cs.GetTipSetFromKey(ts.Parents())
if err != nil {
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
}
if sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
return nil, ErrExpensiveFork
}
}
state, _, err := sm.TipSetState(ctx, ts)
if err != nil {
return nil, xerrors.Errorf("computing tipset state: %w", err)
}
// Technically, the tipset we're passing in here should be ts+1, but that may not exist.
2021-09-02 16:07:23 +00:00
state, err = sm.HandleStateForks(ctx, state, ts.Height(), nil, ts)
2021-06-03 16:10:53 +00:00
if err != nil {
return nil, fmt.Errorf("failed to handle fork: %w", err)
}
2020-09-01 19:48:16 +00:00
r := store.NewChainRand(sm.cs, ts.Cids())
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("gas_limit", msg.GasLimit),
trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()),
trace.StringAttribute("value", msg.Value.String()),
)
}
vmopt := &vm.VMOpts{
StateBase: state,
2021-06-03 16:10:53 +00:00
Epoch: ts.Height() + 1,
Rand: r,
Bstore: sm.cs.StateBlockstore(),
2021-09-02 16:07:23 +00:00
Actors: sm.tsExec.NewActorRegistry(),
Syscalls: sm.Syscalls,
CircSupplyCalc: sm.GetVMCirculatingSupply,
NtwkVersion: sm.GetNtwkVersion,
BaseFee: ts.Blocks()[0].ParentBaseFee,
LookbackState: LookbackStateGetterForTipset(sm, ts),
}
vmi, err := sm.newVM(ctx, vmopt)
if err != nil {
return nil, xerrors.Errorf("failed to set up vm: %w", err)
}
for i, m := range priorMsgs {
_, err := vmi.ApplyMessage(ctx, m)
if err != nil {
return nil, xerrors.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
}
}
fromActor, err := vmi.StateTree().GetActor(msg.From)
if err != nil {
return nil, xerrors.Errorf("call raw get actor: %s", err)
}
msg.Nonce = fromActor.Nonce
fromKey, err := sm.ResolveToKeyAddress(ctx, msg.From, ts)
if err != nil {
return nil, xerrors.Errorf("could not resolve key: %w", err)
}
var msgApply types.ChainMsg
switch fromKey.Protocol() {
case address.BLS:
msgApply = msg
case address.SECP256K1:
msgApply = &types.SignedMessage{
Message: *msg,
Signature: crypto.Signature{
Type: crypto.SigTypeSecp256k1,
Data: make([]byte, 65),
},
}
}
ret, err := vmi.ApplyMessage(ctx, msgApply)
if err != nil {
return nil, xerrors.Errorf("apply message failed: %w", err)
}
var errs string
if ret.ActorErr != nil {
errs = ret.ActorErr.Error()
}
return &api.InvocResult{
2020-10-14 03:45:47 +00:00
MsgCid: msg.Cid(),
Msg: msg,
MsgRct: &ret.MessageReceipt,
2020-10-15 22:48:51 +00:00
GasCost: MakeMsgGasCost(msg, ret),
ExecutionTrace: ret.ExecutionTrace,
Error: errs,
Duration: ret.Duration,
}, nil
}
2019-09-19 20:25:18 +00:00
var errHaltExecution = fmt.Errorf("halt")
func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid) (*types.Message, *vm.ApplyRet, error) {
var finder messageFinder
// message to find
finder.mcid = mcid
2021-09-02 16:07:23 +00:00
_, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder)
2021-03-12 15:21:20 +00:00
if err != nil && !xerrors.Is(err, errHaltExecution) {
2019-09-19 20:25:18 +00:00
return nil, nil, xerrors.Errorf("unexpected error during execution: %w", err)
}
if finder.outr == nil {
return nil, nil, xerrors.Errorf("given message not found in tipset")
}
return finder.outm, finder.outr, nil
2019-09-19 20:25:18 +00:00
}