lotus/chain/stmgr/call.go

333 lines
9.7 KiB
Go
Raw Normal View History

package stmgr
import (
"context"
"errors"
2019-09-19 20:25:18 +00:00
"fmt"
2022-06-14 15:00:51 +00:00
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
2022-06-14 15:00:51 +00:00
"go.opencensus.io/trace"
"golang.org/x/xerrors"
2021-09-18 17:57:04 +00:00
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
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/filecoin-project/lotus/api"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/lotus/chain/rand"
"github.com/filecoin-project/lotus/chain/state"
"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.
//
2022-08-29 14:25:30 +00:00
// - 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 {
2021-12-11 21:03:00 +00:00
pts, err := sm.cs.GetTipSetFromKey(ctx, 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 {
2021-12-11 21:03:00 +00:00
pts, err := sm.cs.LoadTipSet(ctx, 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
}
2022-03-15 23:34:59 +00:00
// Since we're simulating a future message, pretend we're applying it in the "next" tipset
vmHeight := pheight + 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,
Epoch: vmHeight,
Rand: rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, sm.GetNetworkVersion),
Bstore: sm.cs.StateBlockstore(),
2021-09-02 16:07:23 +00:00
Actors: sm.tsExec.NewActorRegistry(),
Syscalls: sm.Syscalls,
CircSupplyCalc: sm.GetVMCirculatingSupply,
NetworkVersion: sm.GetNetworkVersion(ctx, pheight+1),
BaseFee: types.NewInt(0),
LookbackState: LookbackStateGetterForTipset(sm, ts),
Tracing: true,
}
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()),
)
}
stTree, err := sm.StateTree(bstate)
if err != nil {
2022-03-15 23:34:59 +00:00
return nil, xerrors.Errorf("failed to load state tree: %w", err)
}
fromActor, err := stTree.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 && ret == 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,
}, err
}
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()
// Copy the message as we'll be modifying the nonce.
msgCopy := *msg
msg = &msgCopy
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 {
2021-12-11 21:03:00 +00:00
pts, err := sm.cs.GetTipSetFromKey(ctx, 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 {
2021-12-11 21:03:00 +00:00
pts, err := sm.cs.GetTipSetFromKey(ctx, 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
}
}
2022-03-15 23:34:59 +00:00
// Since we're simulating a future message, pretend we're applying it in the "next" tipset
vmHeight := ts.Height() + 1
stateCid, _, 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.
stateCid, err = sm.HandleStateForks(ctx, stateCid, 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)
}
r := rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, sm.GetNetworkVersion)
if span.IsRecordingEvents() {
span.AddAttributes(
trace.Int64Attribute("gas_limit", msg.GasLimit),
trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()),
trace.StringAttribute("value", msg.Value.String()),
)
}
buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync())
vmopt := &vm.VMOpts{
StateBase: stateCid,
Epoch: vmHeight,
Rand: r,
Bstore: buffStore,
2021-09-02 16:07:23 +00:00
Actors: sm.tsExec.NewActorRegistry(),
Syscalls: sm.Syscalls,
CircSupplyCalc: sm.GetVMCirculatingSupply,
NetworkVersion: sm.GetNetworkVersion(ctx, ts.Height()+1),
BaseFee: ts.Blocks()[0].ParentBaseFee,
LookbackState: LookbackStateGetterForTipset(sm, ts),
Tracing: true,
}
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)
}
}
2022-03-15 23:34:59 +00:00
// We flush to get the VM's view of the state tree after applying the above messages
// This is needed to get the correct nonce from the actor state to match the VM
stateCid, err = vmi.Flush(ctx)
if err != nil {
return nil, xerrors.Errorf("flushing vm: %w", err)
}
stTree, err := state.LoadStateTree(cbor.NewCborStore(buffStore), stateCid)
if err != nil {
return nil, xerrors.Errorf("loading state tree: %w", err)
}
fromActor, err := stTree.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),
},
}
}
// If the fee cap is set to zero, make gas free.
if msg.GasFeeCap.NilOrZero() {
// Now estimate with a new VM with no base fee.
vmopt.BaseFee = big.Zero()
vmopt.StateBase = stateCid
vmi, err = sm.newVM(ctx, vmopt)
if err != nil {
return nil, xerrors.Errorf("failed to set up estimation vm: %w", err)
}
}
ret, err := vmi.ApplyMessage(ctx, msgApply)
if err != nil {
return nil, xerrors.Errorf("gas estimation 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
_, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true)
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
}