lotus/chain/vm/runtime.go
2020-07-31 00:29:44 -04:00

619 lines
17 KiB
Go

package vm
import (
"bytes"
"context"
"encoding/binary"
"fmt"
gruntime "runtime"
"time"
samarket "github.com/filecoin-project/specs-actors/actors/builtin/market"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
"github.com/filecoin-project/specs-actors/actors/builtin"
sainit "github.com/filecoin-project/specs-actors/actors/builtin/init"
sapower "github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/filecoin-project/specs-actors/actors/crypto"
"github.com/filecoin-project/specs-actors/actors/runtime"
vmr "github.com/filecoin-project/specs-actors/actors/runtime"
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/filecoin-project/specs-actors/actors/util/adt"
"github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
cbg "github.com/whyrusleeping/cbor-gen"
"go.opencensus.io/trace"
"golang.org/x/xerrors"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/aerrors"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/types"
)
type Runtime struct {
ctx context.Context
vm *VM
state *state.StateTree
msg *types.Message
vmsg vmr.Message
height abi.ChainEpoch
cst cbor.IpldStore
pricelist Pricelist
gasAvailable int64
gasUsed int64
sys runtime.Syscalls
// address that started invoke chain
origin address.Address
originNonce uint64
executionTrace types.ExecutionTrace
numActorsCreated uint64
allowInternal bool
callerValidated bool
lastGasChargeTime time.Time
lastGasCharge *types.GasTrace
}
func (rt *Runtime) TotalFilCircSupply() abi.TokenAmount {
filVested, err := rt.vm.GetVestedFunds(rt.ctx)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get vested funds for computing total supply: %s", err)
}
rew, err := rt.state.GetActor(builtin.RewardActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
filMined := types.BigSub(types.FromFil(build.FilAllocStorageMining), rew.Balance)
if filMined.LessThan(big.Zero()) {
filMined = big.Zero()
}
burnt, err := rt.state.GetActor(builtin.BurntFundsActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
filBurned := burnt.Balance
market, err := rt.state.GetActor(builtin.StorageMarketActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
var mst samarket.State
if err := rt.cst.Get(rt.ctx, market.Head, &mst); err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get market state: %s", err)
}
filMarketLocked := types.BigAdd(mst.TotalClientLockedCollateral, mst.TotalProviderLockedCollateral)
filMarketLocked = types.BigAdd(filMarketLocked, mst.TotalClientStorageFee)
power, err := rt.state.GetActor(builtin.StoragePowerActorAddr)
if err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err)
}
var pst sapower.State
if err := rt.cst.Get(rt.ctx, power.Head, &pst); err != nil {
rt.Abortf(exitcode.ErrIllegalState, "failed to get storage power state: %s", err)
}
filLocked := types.BigAdd(filMarketLocked, pst.TotalPledgeCollateral)
ret := types.BigAdd(filVested, filMined)
ret = types.BigSub(ret, filBurned)
ret = types.BigSub(ret, filLocked)
if ret.LessThan(big.Zero()) {
ret = big.Zero()
}
return ret
}
func (rt *Runtime) ResolveAddress(addr address.Address) (ret address.Address, ok bool) {
r, err := rt.state.LookupID(addr)
if err != nil {
if xerrors.Is(err, sainit.ErrAddressNotFound) {
return address.Undef, false
}
panic(aerrors.Fatalf("failed to resolve address %s: %s", addr, err))
}
return r, true
}
type notFoundErr interface {
IsNotFound() bool
}
func (rt *Runtime) Get(c cid.Cid, o vmr.CBORUnmarshaler) bool {
if err := rt.cst.Get(context.TODO(), c, o); err != nil {
var nfe notFoundErr
if xerrors.As(err, &nfe) && nfe.IsNotFound() {
if xerrors.As(err, new(cbor.SerializationError)) {
panic(aerrors.Newf(exitcode.ErrSerialization, "failed to unmarshal cbor object %s", err))
}
return false
}
panic(aerrors.Fatalf("failed to get cbor object %s: %s", c, err))
}
return true
}
func (rt *Runtime) Put(x vmr.CBORMarshaler) cid.Cid {
c, err := rt.cst.Put(context.TODO(), x)
if err != nil {
if xerrors.As(err, new(cbor.SerializationError)) {
panic(aerrors.Newf(exitcode.ErrSerialization, "failed to marshal cbor object %s", err))
}
panic(aerrors.Fatalf("failed to put cbor object: %s", err))
}
return c
}
var _ vmr.Runtime = (*Runtime)(nil)
func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) {
defer func() {
if r := recover(); r != nil {
if ar, ok := r.(aerrors.ActorError); ok {
log.Warnf("VM.Call failure: %+v", ar)
aerr = ar
return
}
//log.Desugar().WithOptions(zap.AddStacktrace(zapcore.ErrorLevel)).
//Sugar().Errorf("spec actors failure: %s", r)
log.Errorf("spec actors failure: %s", r)
aerr = aerrors.Newf(1, "spec actors failure: %s", r)
}
}()
ret := f()
if !rt.callerValidated {
rt.Abortf(exitcode.SysErrorIllegalActor, "Caller MUST be validated during method execution")
}
switch ret := ret.(type) {
case []byte:
return ret, nil
case *adt.EmptyValue:
return nil, nil
case cbg.CBORMarshaler:
buf := new(bytes.Buffer)
if err := ret.MarshalCBOR(buf); err != nil {
return nil, aerrors.Absorb(err, 2, "failed to marshal response to cbor")
}
return buf.Bytes(), nil
case nil:
return nil, nil
default:
return nil, aerrors.New(3, "could not determine type for response from call")
}
}
func (rt *Runtime) Message() vmr.Message {
return rt.vmsg
}
func (rt *Runtime) ValidateImmediateCallerAcceptAny() {
rt.abortIfAlreadyValidated()
return
}
func (rt *Runtime) CurrentBalance() abi.TokenAmount {
b, err := rt.GetBalance(rt.Message().Receiver())
if err != nil {
rt.Abortf(err.RetCode(), "get current balance: %v", err)
}
return b
}
func (rt *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) {
act, err := rt.state.GetActor(addr)
if err != nil {
if xerrors.Is(err, types.ErrActorNotFound) {
return cid.Undef, false
}
panic(aerrors.Fatalf("failed to get actor: %s", err))
}
return act.Code, true
}
func (rt *Runtime) GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness {
res, err := rt.vm.rand.GetRandomness(rt.ctx, personalization, randEpoch, entropy)
if err != nil {
panic(aerrors.Fatalf("could not get randomness: %s", err))
}
return res
}
func (rt *Runtime) Store() vmr.Store {
return rt
}
func (rt *Runtime) NewActorAddress() address.Address {
var b bytes.Buffer
oa, _ := ResolveToKeyAddr(rt.vm.cstate, rt.vm.cst, rt.origin)
if err := oa.MarshalCBOR(&b); err != nil { // todo: spec says cbor; why not just bytes?
panic(aerrors.Fatalf("writing caller address into a buffer: %v", err))
}
if err := binary.Write(&b, binary.BigEndian, rt.originNonce); err != nil {
panic(aerrors.Fatalf("writing nonce address into a buffer: %v", err))
}
if err := binary.Write(&b, binary.BigEndian, rt.numActorsCreated); err != nil { // TODO: expose on vm
panic(aerrors.Fatalf("writing callSeqNum address into a buffer: %v", err))
}
addr, err := address.NewActorAddress(b.Bytes())
if err != nil {
panic(aerrors.Fatalf("create actor address: %v", err))
}
rt.incrementNumActorsCreated()
return addr
}
func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) {
if !builtin.IsBuiltinActor(codeID) {
rt.Abortf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.")
}
if builtin.IsSingletonActor(codeID) {
rt.Abortf(exitcode.SysErrorIllegalArgument, "Can only have one instance of singleton actors.")
}
_, err := rt.state.GetActor(address)
if err == nil {
rt.Abortf(exitcode.SysErrorIllegalArgument, "Actor address already exists")
}
rt.chargeGas(rt.Pricelist().OnCreateActor())
err = rt.state.SetActor(address, &types.Actor{
Code: codeID,
Head: EmptyObjectCid,
Nonce: 0,
Balance: big.Zero(),
})
if err != nil {
panic(aerrors.Fatalf("creating actor entry: %v", err))
}
_ = rt.chargeGasSafe(gasOnActorExec)
}
func (rt *Runtime) DeleteActor(addr address.Address) {
rt.chargeGas(rt.Pricelist().OnDeleteActor())
act, err := rt.state.GetActor(rt.Message().Receiver())
if err != nil {
if xerrors.Is(err, types.ErrActorNotFound) {
rt.Abortf(exitcode.SysErrorIllegalActor, "failed to load actor in delete actor: %s", err)
}
panic(aerrors.Fatalf("failed to get actor: %s", err))
}
if !act.Balance.IsZero() {
if err := rt.vm.transfer(rt.Message().Receiver(), builtin.BurntFundsActorAddr, act.Balance); err != nil {
panic(aerrors.Fatalf("failed to transfer balance to burnt funds actor: %s", err))
}
}
if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil {
panic(aerrors.Fatalf("failed to delete actor: %s", err))
}
_ = rt.chargeGasSafe(gasOnActorExec)
}
func (rt *Runtime) Syscalls() vmr.Syscalls {
return rt.sys
}
func (rt *Runtime) StartSpan(name string) vmr.TraceSpan {
panic("implement me")
}
func (rt *Runtime) ValidateImmediateCallerIs(as ...address.Address) {
rt.abortIfAlreadyValidated()
imm := rt.Message().Caller()
for _, a := range as {
if imm == a {
return
}
}
rt.Abortf(exitcode.SysErrForbidden, "caller %s is not one of %s", rt.Message().Caller(), as)
}
func (rt *Runtime) Context() context.Context {
return rt.ctx
}
func (rt *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) {
log.Warnf("Abortf: " + fmt.Sprintf(msg, args...))
panic(aerrors.NewfSkip(2, code, msg, args...))
}
func (rt *Runtime) AbortStateMsg(msg string) {
panic(aerrors.NewfSkip(3, 101, msg))
}
func (rt *Runtime) ValidateImmediateCallerType(ts ...cid.Cid) {
rt.abortIfAlreadyValidated()
callerCid, ok := rt.GetActorCodeCID(rt.Message().Caller())
if !ok {
panic(aerrors.Fatalf("failed to lookup code cid for caller"))
}
for _, t := range ts {
if t == callerCid {
return
}
}
rt.Abortf(exitcode.SysErrForbidden, "caller cid type %q was not one of %v", callerCid, ts)
}
func (rt *Runtime) CurrEpoch() abi.ChainEpoch {
return rt.height
}
type dumbWrapperType struct {
val []byte
}
func (dwt *dumbWrapperType) Into(um vmr.CBORUnmarshaler) error {
return um.UnmarshalCBOR(bytes.NewReader(dwt.val))
}
func (rt *Runtime) Send(to address.Address, method abi.MethodNum, m vmr.CBORMarshaler, value abi.TokenAmount) (vmr.SendReturn, exitcode.ExitCode) {
if !rt.allowInternal {
rt.Abortf(exitcode.SysErrorIllegalActor, "runtime.Send() is currently disallowed")
}
var params []byte
if m != nil {
buf := new(bytes.Buffer)
if err := m.MarshalCBOR(buf); err != nil {
rt.Abortf(exitcode.SysErrInvalidParameters, "failed to marshal input parameters: %s", err)
}
params = buf.Bytes()
}
ret, err := rt.internalSend(rt.Message().Receiver(), to, method, value, params)
if err != nil {
if err.IsFatal() {
panic(err)
}
log.Warnf("vmctx send failed: to: %s, method: %d: ret: %d, err: %s", to, method, ret, err)
return &dumbWrapperType{nil}, err.RetCode()
}
_ = rt.chargeGasSafe(gasOnActorExec)
return &dumbWrapperType{ret}, 0
}
func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum, value types.BigInt, params []byte) ([]byte, aerrors.ActorError) {
start := build.Clock.Now()
ctx, span := trace.StartSpan(rt.ctx, "vmc.Send")
defer span.End()
if span.IsRecordingEvents() {
span.AddAttributes(
trace.StringAttribute("to", to.String()),
trace.Int64Attribute("method", int64(method)),
trace.StringAttribute("value", value.String()),
)
}
msg := &types.Message{
From: from,
To: to,
Method: method,
Value: value,
Params: params,
GasLimit: rt.gasAvailable,
}
st := rt.state
if err := st.Snapshot(ctx); err != nil {
return nil, aerrors.Fatalf("snapshot failed: %s", err)
}
defer st.ClearSnapshot()
ret, errSend, subrt := rt.vm.send(ctx, msg, rt, nil, start)
if errSend != nil {
if errRevert := st.Revert(); errRevert != nil {
return nil, aerrors.Escalate(errRevert, "failed to revert state tree after failed subcall")
}
}
if subrt != nil {
rt.numActorsCreated = subrt.numActorsCreated
}
rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace)
return ret, errSend
}
func (rt *Runtime) State() vmr.StateHandle {
return &shimStateHandle{rt: rt}
}
type shimStateHandle struct {
rt *Runtime
}
func (ssh *shimStateHandle) Create(obj vmr.CBORMarshaler) {
c := ssh.rt.Put(obj)
// TODO: handle error below
ssh.rt.stateCommit(EmptyObjectCid, c)
}
func (ssh *shimStateHandle) Readonly(obj vmr.CBORUnmarshaler) {
act, err := ssh.rt.state.GetActor(ssh.rt.Message().Receiver())
if err != nil {
ssh.rt.Abortf(exitcode.SysErrorIllegalArgument, "failed to get actor for Readonly state: %s", err)
}
ssh.rt.Get(act.Head, obj)
}
func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func()) {
if obj == nil {
ssh.rt.Abortf(exitcode.SysErrorIllegalActor, "Must not pass nil to Transaction()")
}
act, err := ssh.rt.state.GetActor(ssh.rt.Message().Receiver())
if err != nil {
ssh.rt.Abortf(exitcode.SysErrorIllegalActor, "failed to get actor for Transaction: %s", err)
}
baseState := act.Head
ssh.rt.Get(baseState, obj)
ssh.rt.allowInternal = false
f()
ssh.rt.allowInternal = true
c := ssh.rt.Put(obj)
// TODO: handle error below
ssh.rt.stateCommit(baseState, c)
}
func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorError) {
act, err := rt.state.GetActor(a)
switch err {
default:
return types.EmptyInt, aerrors.Escalate(err, "failed to look up actor balance")
case types.ErrActorNotFound:
return types.NewInt(0), nil
case nil:
return act.Balance, nil
}
}
func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError {
// TODO: we can make this more efficient in the future...
act, err := rt.state.GetActor(rt.Message().Receiver())
if err != nil {
return aerrors.Escalate(err, "failed to get actor to commit state")
}
if act.Head != oldh {
return aerrors.Fatal("failed to update, inconsistent base reference")
}
act.Head = newh
if err := rt.state.SetActor(rt.Message().Receiver(), act); err != nil {
return aerrors.Fatalf("failed to set actor in commit state: %s", err)
}
return nil
}
func (rt *Runtime) finilizeGasTracing() {
if rt.lastGasCharge != nil {
rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime)
}
}
// ChargeGas is spec actors function
func (rt *Runtime) ChargeGas(name string, compute int64, virtual int64) {
err := rt.chargeGasInternal(newGasCharge(name, compute, 0).WithVirtual(virtual, 0), 1)
if err != nil {
panic(err)
}
}
func (rt *Runtime) chargeGas(gas GasCharge) {
err := rt.chargeGasInternal(gas, 1)
if err != nil {
panic(err)
}
}
func (rt *Runtime) chargeGasFunc(skip int) func(GasCharge) {
return func(gas GasCharge) {
err := rt.chargeGasInternal(gas, 1+skip)
if err != nil {
panic(err)
}
}
}
func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError {
toUse := gas.Total()
var callers [10]uintptr
cout := gruntime.Callers(2+skip, callers[:])
now := build.Clock.Now()
if rt.lastGasCharge != nil {
rt.lastGasCharge.TimeTaken = now.Sub(rt.lastGasChargeTime)
}
gasTrace := types.GasTrace{
Name: gas.Name,
Extra: gas.Extra,
TotalGas: toUse,
ComputeGas: gas.ComputeGas,
StorageGas: gas.StorageGas,
TotalVirtualGas: gas.VirtualCompute*GasComputeMulti + gas.VirtualStorage*GasStorageMulti,
VirtualComputeGas: gas.VirtualCompute,
VirtualStorageGas: gas.VirtualStorage,
Callers: callers[:cout],
}
rt.executionTrace.GasCharges = append(rt.executionTrace.GasCharges, &gasTrace)
rt.lastGasChargeTime = now
rt.lastGasCharge = &gasTrace
// overflow safe
if rt.gasUsed > rt.gasAvailable-toUse {
rt.gasUsed = rt.gasAvailable
return aerrors.Newf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d",
rt.gasUsed, rt.gasAvailable)
}
rt.gasUsed += toUse
return nil
}
func (rt *Runtime) chargeGasSafe(gas GasCharge) aerrors.ActorError {
return rt.chargeGasInternal(gas, 1)
}
func (rt *Runtime) Pricelist() Pricelist {
return rt.pricelist
}
func (rt *Runtime) incrementNumActorsCreated() {
rt.numActorsCreated++
}
func (rt *Runtime) abortIfAlreadyValidated() {
if rt.callerValidated {
rt.Abortf(exitcode.SysErrorIllegalActor, "Method must validate caller identity exactly once")
}
rt.callerValidated = true
}
func (rt *Runtime) Log(level vmr.LogLevel, msg string, args ...interface{}) {
switch level {
case vmr.DEBUG:
actorLog.Debugf(msg, args...)
case vmr.INFO:
actorLog.Infof(msg, args...)
case vmr.WARN:
actorLog.Warnf(msg, args...)
case vmr.ERROR:
actorLog.Errorf(msg, args...)
}
}