From 114cb2d2aa44015058258f1b839e330528791624 Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Mon, 9 Mar 2020 19:24:02 -0700 Subject: [PATCH] squash the spec shim and the vmcontext into the new runtime type --- chain/state/statetree.go | 24 +++ chain/types/vmcontext.go | 27 --- chain/vm/invoker.go | 18 +- chain/vm/invoker_test.go | 6 +- chain/vm/runtime.go | 428 +++++++++++++++++++++++++++++++++++++++ chain/vm/spec_shim.go | 309 ---------------------------- chain/vm/vm.go | 285 +++----------------------- 7 files changed, 495 insertions(+), 602 deletions(-) create mode 100644 chain/vm/runtime.go delete mode 100644 chain/vm/spec_shim.go diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 4386efb43..3b1884dcc 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -125,6 +125,30 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) { return &act, nil } +func (st *StateTree) DeleteActor(addr address.Address) error { + if addr == address.Undef { + return xerrors.Errorf("DeleteActor called on undefined address") + } + + iaddr, err := st.LookupID(addr) + if err != nil { + if xerrors.Is(err, init_.ErrAddressNotFound) { + return xerrors.Errorf("resolution lookup failed (%s): %w", addr, err) + } + return xerrors.Errorf("address resolution: %w", err) + } + + addr = iaddr + + delete(st.actorcache, addr) + + if err := st.root.Delete(context.TODO(), string(addr.Bytes())); err != nil { + return xerrors.Errorf("failed to delete actor: %w", err) + } + + return nil +} + func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { ctx, span := trace.StartSpan(ctx, "stateTree.Flush") defer span.End() diff --git a/chain/types/vmcontext.go b/chain/types/vmcontext.go index 639032358..ef7a910d6 100644 --- a/chain/types/vmcontext.go +++ b/chain/types/vmcontext.go @@ -1,17 +1,10 @@ package types import ( - "context" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/actors/aerrors" cid "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" cbg "github.com/whyrusleeping/cbor-gen" ) @@ -31,26 +24,6 @@ type StateTree interface { GetActor(addr address.Address) (*Actor, error) } -type VMContext interface { - Message() *Message - Origin() address.Address - Ipld() cbor.IpldStore - Send(to address.Address, method abi.MethodNum, value BigInt, params []byte) ([]byte, aerrors.ActorError) - BlockHeight() abi.ChainEpoch - GasUsed() BigInt - Storage() Storage - StateTree() (StateTree, aerrors.ActorError) - ActorCodeCID(address.Address) (cid.Cid, error) - LookupID(address.Address) (address.Address, error) - VerifySignature(sig *crypto.Signature, from address.Address, data []byte) aerrors.ActorError - ChargeGas(uint64) aerrors.ActorError - GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, aerrors.ActorError) - GetBalance(address.Address) (BigInt, aerrors.ActorError) - Sys() runtime.Syscalls - - Context() context.Context -} - type storageWrapper struct { s Storage } diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 33034b2ab..91258cacb 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -21,6 +21,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/builtin/power" "github.com/filecoin-project/specs-actors/actors/builtin/reward" "github.com/filecoin-project/specs-actors/actors/builtin/system" + "github.com/filecoin-project/specs-actors/actors/runtime" vmr "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -33,7 +34,7 @@ type invoker struct { builtInState map[cid.Cid]reflect.Type } -type invokeFunc func(act *types.Actor, vmctx types.VMContext, params []byte) ([]byte, aerrors.ActorError) +type invokeFunc func(act *types.Actor, rt runtime.Runtime, params []byte) ([]byte, aerrors.ActorError) type nativeCode []invokeFunc func NewInvoker() *invoker { @@ -56,7 +57,7 @@ func NewInvoker() *invoker { return inv } -func (inv *invoker) Invoke(act *types.Actor, vmctx types.VMContext, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { +func (inv *invoker) Invoke(act *types.Actor, rt runtime.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { if act.Code == builtin.AccountActorCodeID { return nil, aerrors.Newf(254, "cannot invoke methods on account actors") @@ -64,13 +65,13 @@ func (inv *invoker) Invoke(act *types.Actor, vmctx types.VMContext, method abi.M code, ok := inv.builtInCode[act.Code] if !ok { - log.Errorf("no code for actor %s (Addr: %s)", act.Code, vmctx.Message().To) + log.Errorf("no code for actor %s (Addr: %s)", act.Code, rt.Message().Receiver()) return nil, aerrors.Newf(255, "no code for actor %s(%d)(%s)", act.Code, method, hex.EncodeToString(params)) } if method >= abi.MethodNum(len(code)) || code[method] == nil { return nil, aerrors.Newf(255, "no method %d on actor", method) } - return code[method](act, vmctx, params) + return code[method](act, rt, params) } @@ -87,7 +88,6 @@ type Invokee interface { Exports() []interface{} } -var tVMContext = reflect.TypeOf((*types.VMContext)(nil)).Elem() var tAError = reflect.TypeOf((*aerrors.ActorError)(nil)).Elem() func (*invoker) transform(instance Invokee) (nativeCode, error) { @@ -117,7 +117,7 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) { return nil, newErr("first arguemnt should be vmr.Runtime") } if t.In(1).Kind() != reflect.Ptr { - return nil, newErr("second argument should be types.VMContext") + return nil, newErr("second argument should be Runtime") } if t.NumOut() != 1 { @@ -150,10 +150,10 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) { } } } - shim := &runtimeShim{vmctx: in[1].Interface().(*VMContext)} - rval, aerror := shim.shimCall(func() interface{} { + rt := in[1].Interface().(*Runtime) + rval, aerror := rt.shimCall(func() interface{} { ret := meth.Call([]reflect.Value{ - reflect.ValueOf(shim), + reflect.ValueOf(rt), param, }) return ret[0].Interface() diff --git a/chain/vm/invoker_test.go b/chain/vm/invoker_test.go index b81b7c08b..fe3d7b7fb 100644 --- a/chain/vm/invoker_test.go +++ b/chain/vm/invoker_test.go @@ -84,7 +84,7 @@ func TestInvokerBasic(t *testing.T) { bParam, err := actors.SerializeParams(&basicParams{B: 1}) assert.NoError(t, err) - _, aerr := code[0](nil, &VMContext{}, bParam) + _, aerr := code[0](nil, &Runtime{}, bParam) assert.Equal(t, byte(1), aerrors.RetCode(aerr), "return code should be 1") if aerrors.IsFatal(aerr) { @@ -96,14 +96,14 @@ func TestInvokerBasic(t *testing.T) { bParam, err := actors.SerializeParams(&basicParams{B: 2}) assert.NoError(t, err) - _, aerr := code[10](nil, &VMContext{}, bParam) + _, aerr := code[10](nil, &Runtime{}, bParam) assert.Equal(t, byte(12), aerrors.RetCode(aerr), "return code should be 12") if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") } } - _, aerr := code[1](nil, &VMContext{}, []byte{99}) + _, aerr := code[1](nil, &Runtime{}, []byte{99}) if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") } diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go new file mode 100644 index 000000000..c0be66173 --- /dev/null +++ b/chain/vm/runtime.go @@ -0,0 +1,428 @@ +package vm + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "runtime/debug" + + "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/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" + hamt "github.com/ipfs/go-hamt-ipld" + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/trace" + + "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 + height abi.ChainEpoch + cst cbor.IpldStore + + gasAvailable types.BigInt + gasUsed types.BigInt + + sys runtime.Syscalls + + // address that started invoke chain + origin address.Address + + internalExecutions []*ExecutionResult +} + +func (rs *Runtime) ResolveAddress(address address.Address) (ret address.Address, ok bool) { + r, err := rs.LookupID(address) + if err != nil { // TODO: check notfound + rs.Abortf(exitcode.ErrPlaceholder, "resolve address: %v", err) + } + return r, true +} + +func (rs *Runtime) Get(c cid.Cid, o vmr.CBORUnmarshaler) bool { + if err := rs.cst.Get(context.TODO(), c, o); err != nil { + // TODO: err not found? + rs.Abortf(exitcode.ErrPlaceholder, "storage get: %v", err) + } + return true +} + +func (rs *Runtime) Put(x vmr.CBORMarshaler) cid.Cid { + c, err := rs.cst.Put(context.TODO(), x) + if err != nil { + rs.Abortf(exitcode.ErrPlaceholder, "storage put: %v", err) // todo: spec code? + } + return c +} + +var _ vmr.Runtime = (*Runtime)(nil) + +func (rs *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.Warn("VM.Call failure: ", ar) + debug.PrintStack() + aerr = ar + return + } + debug.PrintStack() + log.Errorf("ERROR") + aerr = aerrors.Newf(1, "spec actors failure: %s", r) + } + }() + + ret := f() + 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 (rs *Runtime) Message() vmr.Message { + var err error + + rawm := *rs.msg + rawm.From, err = rs.LookupID(rawm.From) + if err != nil { + rs.Abortf(exitcode.ErrPlaceholder, "resolve from address: %v", err) + } + + rawm.To, err = rs.LookupID(rawm.To) + if err != nil { + rs.Abortf(exitcode.ErrPlaceholder, "resolve to address: %v", err) + } + + return &rawm +} + +func (rs *Runtime) ValidateImmediateCallerAcceptAny() { + return +} + +func (rs *Runtime) CurrentBalance() abi.TokenAmount { + b, err := rs.GetBalance(rs.Message().Receiver()) + if err != nil { + rs.Abortf(exitcode.ExitCode(err.RetCode()), "get current balance: %v", err) + } + return b +} + +func (rs *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) { + act, err := rs.state.GetActor(addr) + if err != nil { + // todo: notfound + rs.Abortf(exitcode.ErrPlaceholder, "%v", 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, int64(randEpoch), entropy) + if err != nil { + rt.Abortf(exitcode.SysErrInternal, "could not get randomness: %s", err) + } + return res +} + +func (rs *Runtime) Store() vmr.Store { + return rs +} + +func (rt *Runtime) NewActorAddress() address.Address { + var b bytes.Buffer + if err := rt.Message().Caller().MarshalCBOR(&b); err != nil { // todo: spec says cbor; why not just bytes? + rt.Abortf(exitcode.ErrSerialization, "writing caller address into a buffer: %v", err) + } + + act, err := rt.state.GetActor(rt.origin) + if err != nil { + rt.Abortf(exitcode.SysErrInternal, "getting top level actor: %v", err) + } + + if err := binary.Write(&b, binary.BigEndian, act.Nonce); err != nil { + rt.Abortf(exitcode.ErrSerialization, "writing nonce address into a buffer: %v", err) + } + if err := binary.Write(&b, binary.BigEndian, uint64(0)); err != nil { // TODO: expose on vm + rt.Abortf(exitcode.ErrSerialization, "writing callSeqNum address into a buffer: %v", err) + } + addr, err := address.NewActorAddress(b.Bytes()) + if err != nil { + rt.Abortf(exitcode.ErrSerialization, "create actor address: %v", err) + } + + return addr +} + +func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { + var err error + + err = rt.state.SetActor(address, &types.Actor{ + Code: codeId, + Head: EmptyObjectCid, + Nonce: 0, + Balance: big.Zero(), + }) + if err != nil { + rt.Abortf(exitcode.SysErrInternal, "creating actor entry: %v", err) + } +} + +func (rt *Runtime) DeleteActor() { + if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil { + rt.Abortf(exitcode.SysErrInternal, "failed to delete actor: %s", err) + } +} + +const GasVerifySignature = 50 + +func (rs *Runtime) Syscalls() vmr.Syscalls { + // TODO: Make sure this is wrapped in something that charges gas for each of the calls + return rs.sys +} + +func (rs *Runtime) StartSpan(name string) vmr.TraceSpan { + panic("implement me") +} + +func (rt *Runtime) ValidateImmediateCallerIs(as ...address.Address) { + imm, err := rt.LookupID(rt.Message().Caller()) + if err != nil { + rt.Abortf(exitcode.ErrIllegalState, "couldn't resolve immediate caller") + } + + for _, a := range as { + if imm == a { + return + } + } + rt.Abortf(exitcode.ErrForbidden, "caller %s is not one of %s", rt.Message().Caller(), as) +} + +func (rt *Runtime) Context() context.Context { + return rt.ctx +} + +func (rs *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) { + panic(aerrors.NewfSkip(2, uint8(code), msg, args...)) +} + +func (rs *Runtime) AbortStateMsg(msg string) { + panic(aerrors.NewfSkip(3, 101, msg)) +} + +func (rt *Runtime) ValidateImmediateCallerType(ts ...cid.Cid) { + callerCid, ok := rt.GetActorCodeCID(rt.Message().Caller()) + if !ok { + rt.Abortf(exitcode.ErrIllegalArgument, "failed to lookup code cid for caller") + } + for _, t := range ts { + if t == callerCid { + return + } + } + rt.Abortf(exitcode.ErrForbidden, "caller cid type %q was not one of %v", callerCid, ts) +} + +func (rs *Runtime) CurrEpoch() abi.ChainEpoch { + return rs.height +} + +type dumbWrapperType struct { + val []byte +} + +func (dwt *dumbWrapperType) Into(um vmr.CBORUnmarshaler) error { + return um.UnmarshalCBOR(bytes.NewReader(dwt.val)) +} + +func (rs *Runtime) Send(to address.Address, method abi.MethodNum, m vmr.CBORMarshaler, value abi.TokenAmount) (vmr.SendReturn, exitcode.ExitCode) { + var params []byte + if m != nil { + buf := new(bytes.Buffer) + if err := m.MarshalCBOR(buf); err != nil { + rs.Abortf(exitcode.SysErrInvalidParameters, "failed to marshal input parameters: %s", err) + } + params = buf.Bytes() + } + + ret, err := rs.internalSend(to, method, types.BigInt(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 nil, exitcode.ExitCode(err.RetCode()) + } + return &dumbWrapperType{ret}, 0 +} + +func (rt *Runtime) internalSend(to address.Address, method abi.MethodNum, value types.BigInt, params []byte) ([]byte, aerrors.ActorError) { + fmt.Println("internal send: ", to, method) + 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: rt.Message().Receiver(), + 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, err, subrt := rt.vm.send(ctx, msg, rt, 0) + if err != nil { + fmt.Println("send failed") + if err := st.Revert(); err != nil { + return nil, aerrors.Escalate(err, "failed to revert state tree after failed subcall") + } + } + fmt.Println("after 'send'", ret) + + mr := types.MessageReceipt{ + ExitCode: exitcode.ExitCode(aerrors.RetCode(err)), + Return: ret, + GasUsed: types.EmptyInt, + } + + var es = "" + if err != nil { + es = err.Error() + } + er := ExecutionResult{ + Msg: msg, + MsgRct: &mr, + Error: es, + Subcalls: subrt.internalExecutions, + } + + fmt.Println("keeping execution!") + rt.internalExecutions = append(rt.internalExecutions, &er) + return ret, err +} + +func (rs *Runtime) State() vmr.StateHandle { + return &shimStateHandle{rs: rs} +} + +type shimStateHandle struct { + rs *Runtime +} + +func (ssh *shimStateHandle) Create(obj vmr.CBORMarshaler) { + c := ssh.rs.Put(obj) + ssh.rs.stateCommit(EmptyObjectCid, c) +} + +func (ssh *shimStateHandle) Readonly(obj vmr.CBORUnmarshaler) { + act, err := ssh.rs.state.GetActor(ssh.rs.Message().Receiver()) + if err != nil { + ssh.rs.Abortf(exitcode.SysErrInternal, "failed to get actor for Readonly state: %s", err) + } + ssh.rs.Get(act.Head, obj) +} + +func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func() interface{}) interface{} { + act, err := ssh.rs.state.GetActor(ssh.rs.Message().Receiver()) + if err != nil { + ssh.rs.Abortf(exitcode.SysErrInternal, "failed to get actor for Readonly state: %s", err) + } + baseState := act.Head + ssh.rs.Get(baseState, obj) + + out := f() + + c := ssh.rs.Put(obj) + + ssh.rs.stateCommit(baseState, c) + + return out +} + +func (rt *Runtime) LookupID(a address.Address) (address.Address, error) { + return rt.state.LookupID(a) +} + +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 hamt.ErrNotFound: + return types.NewInt(0), nil + case nil: + return act.Balance, nil + } +} + +func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { + rt.ChargeGas(gasCommit) + + // TODO: we can make this more efficient in the future... + act, err := rt.state.GetActor(rt.Message().Receiver()) + if err != nil { + rt.Abortf(exitcode.SysErrInternal, "failed to get actor to commit state: %s", err) + } + + if act.Head != oldh { + rt.Abortf(exitcode.ErrIllegalState, "failed to update, inconsistent base reference") + } + + act.Head = newh + + if err := rt.state.SetActor(rt.Message().Receiver(), act); err != nil { + rt.Abortf(exitcode.SysErrInternal, "failed to set actor in commit state: %s", err) + } + + return nil +} + +func (rt *Runtime) ChargeGas(amount uint64) { + toUse := types.NewInt(amount) + rt.gasUsed = types.BigAdd(rt.gasUsed, toUse) + if rt.gasUsed.GreaterThan(rt.gasAvailable) { + rt.Abortf(exitcode.SysErrOutOfGas, "not enough gas: used=%s, available=%s", rt.gasUsed, rt.gasAvailable) + } +} diff --git a/chain/vm/spec_shim.go b/chain/vm/spec_shim.go deleted file mode 100644 index e6977853a..000000000 --- a/chain/vm/spec_shim.go +++ /dev/null @@ -1,309 +0,0 @@ -package vm - -import ( - "bytes" - "context" - "encoding/binary" - "runtime/debug" - - "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/crypto" - 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" - cbg "github.com/whyrusleeping/cbor-gen" - - "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/lotus/chain/types" -) - -type runtimeShim struct { - vmctx types.VMContext -} - -func (rs *runtimeShim) ResolveAddress(address address.Address) (ret address.Address, ok bool) { - r, err := rs.vmctx.LookupID(address) - if err != nil { // TODO: check notfound - rs.Abortf(exitcode.ErrPlaceholder, "resolve address: %v", err) - } - return r, true -} - -func (rs *runtimeShim) Get(c cid.Cid, o vmr.CBORUnmarshaler) bool { - err := rs.vmctx.Storage().Get(c, o) - if err != nil { // todo: not found - rs.Abortf(exitcode.ErrPlaceholder, "storage get: %v", err) - } - return true -} - -func (rs *runtimeShim) Put(x vmr.CBORMarshaler) cid.Cid { - c, err := rs.vmctx.Storage().Put(x) - if err != nil { - rs.Abortf(exitcode.ErrPlaceholder, "storage put: %v", err) // todo: spec code? - } - return c -} - -var _ vmr.Runtime = (*runtimeShim)(nil) - -func (rs *runtimeShim) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) { - defer func() { - if r := recover(); r != nil { - if ar, ok := r.(aerrors.ActorError); ok { - log.Warn("VM.Call failure: ", ar) - debug.PrintStack() - aerr = ar - return - } - debug.PrintStack() - log.Errorf("ERROR") - aerr = aerrors.Newf(1, "spec actors failure: %s", r) - } - }() - - ret := f() - 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 (rs *runtimeShim) Message() vmr.Message { - var err error - - rawm := *rs.vmctx.Message() // TODO: normalize addresses earlier - rawm.From, err = rs.vmctx.LookupID(rawm.From) - if err != nil { - rs.Abortf(exitcode.ErrPlaceholder, "resolve from address: %v", err) - } - - rawm.To, err = rs.vmctx.LookupID(rawm.To) - if err != nil { - rs.Abortf(exitcode.ErrPlaceholder, "resolve to address: %v", err) - } - - return &rawm -} - -func (rs *runtimeShim) ValidateImmediateCallerAcceptAny() { - return -} - -func (rs *runtimeShim) CurrentBalance() abi.TokenAmount { - b, err := rs.vmctx.GetBalance(rs.vmctx.Message().To) - if err != nil { - rs.Abortf(exitcode.ExitCode(err.RetCode()), "get current balance: %v", err) - } - return b -} - -func (rs *runtimeShim) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) { - ret, err := rs.vmctx.ActorCodeCID(addr) - if err != nil { - // todo: notfound - rs.Abortf(exitcode.ErrPlaceholder, "%v", err) - } - - return ret, true -} - -func (rs *runtimeShim) GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { - r, err := rs.vmctx.GetRandomness(personalization, randEpoch, entropy) - if err != nil { - rs.Abortf(exitcode.SysErrInternal, "getting randomness: %v", err) - } - - return r -} - -func (rs *runtimeShim) Store() vmr.Store { - return rs -} - -func (rs *runtimeShim) NewActorAddress() address.Address { - var b bytes.Buffer - if err := rs.ImmediateCaller().MarshalCBOR(&b); err != nil { // todo: spec says cbor; why not just bytes? - rs.Abortf(exitcode.ErrSerialization, "writing caller address into a buffer: %v", err) - } - - var err error - st, err := rs.vmctx.StateTree() - if err != nil { - rs.Abortf(exitcode.SysErrInternal, "getting statetree: %v", err) - } - act, err := st.GetActor(rs.vmctx.Origin()) - if err != nil { - rs.Abortf(exitcode.SysErrInternal, "getting top level actor: %v", err) - } - - if err := binary.Write(&b, binary.BigEndian, act.Nonce); err != nil { - rs.Abortf(exitcode.ErrSerialization, "writing nonce address into a buffer: %v", err) - } - if err := binary.Write(&b, binary.BigEndian, uint64(0)); err != nil { // TODO: expose on vm - rs.Abortf(exitcode.ErrSerialization, "writing callSeqNum address into a buffer: %v", err) - } - addr, err := address.NewActorAddress(b.Bytes()) - if err != nil { - rs.Abortf(exitcode.ErrSerialization, "create actor address: %v", err) - } - - return addr -} - -func (rs *runtimeShim) CreateActor(codeId cid.Cid, address address.Address) { - var err error - st, err := rs.vmctx.StateTree() - if err != nil { - rs.Abortf(exitcode.SysErrInternal, "getting statetree: %v", err) - } - - err = st.SetActor(address, &types.Actor{ - Code: codeId, - Head: EmptyObjectCid, - Nonce: 0, - Balance: big.Zero(), - }) - if err != nil { - rs.Abortf(exitcode.SysErrInternal, "creating actor entry: %v", err) - } - - return -} - -func (rs *runtimeShim) DeleteActor() { - panic("implement me") -} - -func (rs *runtimeShim) Syscalls() vmr.Syscalls { - return rs.vmctx.Sys() -} - -func (rs *runtimeShim) StartSpan(name string) vmr.TraceSpan { - panic("implement me") -} - -func (rs *runtimeShim) ValidateImmediateCallerIs(as ...address.Address) { - imm, err := rs.vmctx.LookupID(rs.vmctx.Message().From) - if err != nil { - rs.Abortf(exitcode.ErrIllegalState, "couldn't resolve immediate caller") - } - - for _, a := range as { - if imm == a { - return - } - } - rs.Abortf(exitcode.ErrForbidden, "caller %s is not one of %s", rs.vmctx.Message().From, as) -} - -func (rs *runtimeShim) ImmediateCaller() address.Address { - return rs.vmctx.Message().From -} - -func (rs *runtimeShim) Context() context.Context { - return rs.vmctx.Context() -} - -func (rs *runtimeShim) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) { - panic(aerrors.NewfSkip(2, uint8(code), msg, args...)) -} - -func (rs *runtimeShim) AbortStateMsg(msg string) { - panic(aerrors.NewfSkip(3, 101, msg)) -} - -func (rs *runtimeShim) ValidateImmediateCallerType(...cid.Cid) { - log.Info("validate caller type is dumb") -} - -func (rs *runtimeShim) CurrEpoch() abi.ChainEpoch { - return rs.vmctx.BlockHeight() -} - -type dumbWrapperType struct { - val []byte -} - -func (dwt *dumbWrapperType) Into(um vmr.CBORUnmarshaler) error { - return um.UnmarshalCBOR(bytes.NewReader(dwt.val)) -} - -func (rs *runtimeShim) Send(to address.Address, method abi.MethodNum, m vmr.CBORMarshaler, value abi.TokenAmount) (vmr.SendReturn, exitcode.ExitCode) { - var params []byte - if m != nil { - buf := new(bytes.Buffer) - if err := m.MarshalCBOR(buf); err != nil { - rs.Abortf(exitcode.SysErrInvalidParameters, "failed to marshal input parameters: %s", err) - } - params = buf.Bytes() - } - - ret, err := rs.vmctx.Send(to, method, types.BigInt(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 nil, exitcode.ExitCode(err.RetCode()) - } - return &dumbWrapperType{ret}, 0 -} - -func (rs *runtimeShim) State() vmr.StateHandle { - return &shimStateHandle{rs: rs} -} - -type shimStateHandle struct { - rs *runtimeShim -} - -func (ssh *shimStateHandle) Create(obj vmr.CBORMarshaler) { - c, err := ssh.rs.vmctx.Storage().Put(obj) - if err != nil { - panic(err) - } - if err := ssh.rs.vmctx.Storage().Commit(EmptyObjectCid, c); err != nil { - panic(err) - } -} - -func (ssh *shimStateHandle) Readonly(obj vmr.CBORUnmarshaler) { - if err := ssh.rs.vmctx.Storage().Get(ssh.rs.vmctx.Storage().GetHead(), obj); err != nil { - panic(err) - } -} - -func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func() interface{}) interface{} { - head := ssh.rs.vmctx.Storage().GetHead() - if err := ssh.rs.vmctx.Storage().Get(head, obj); err != nil { - panic(err) - } - - out := f() - - c, err := ssh.rs.vmctx.Storage().Put(obj) - if err != nil { - panic(err) - } - if err := ssh.rs.vmctx.Storage().Commit(head, c); err != nil { - panic(err) - } - - return out -} diff --git a/chain/vm/vm.go b/chain/vm/vm.go index e4260d91c..de793f9c0 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -11,7 +11,6 @@ import ( block "github.com/ipfs/go-block-format" cid "github.com/ipfs/go-cid" - hamt "github.com/ipfs/go-hamt-ipld" blockstore "github.com/ipfs/go-ipfs-blockstore" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" @@ -33,7 +32,6 @@ import ( "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/bufbstore" - "github.com/filecoin-project/lotus/lib/sigs" ) var log = logging.Logger("vm") @@ -60,208 +58,11 @@ type ExecutionResult struct { Error string } -type VMContext struct { - ctx context.Context - - vm *VM - state *state.StateTree - msg *types.Message - height abi.ChainEpoch - cst cbor.IpldStore - - gasAvailable types.BigInt - gasUsed types.BigInt - - sys runtime.Syscalls - - // root cid of the state of the actor this invocation will be on - sroot cid.Cid - - // address that started invoke chain - origin address.Address - - internalExecutions []*ExecutionResult -} - -// Message is the message that kicked off the current invocation -func (vmc *VMContext) Message() *types.Message { - return vmc.msg -} - -func (vmc *VMContext) GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, aerrors.ActorError) { - res, err := vmc.vm.rand.GetRandomness(vmc.ctx, personalization, int64(randEpoch), entropy) - if err != nil { - return nil, aerrors.Escalate(err, "could not get randomness") - } - return res, nil -} - -func (vmc *VMContext) Sys() runtime.Syscalls { - return vmc.sys -} - // Storage interface -func (vmc *VMContext) Put(i cbg.CBORMarshaler) (cid.Cid, aerrors.ActorError) { - c, err := vmc.cst.Put(context.TODO(), i) - if err != nil { - return cid.Undef, aerrors.HandleExternalError(err, fmt.Sprintf("putting object %T", i)) - } - return c, nil -} - -func (vmc *VMContext) Get(c cid.Cid, out cbg.CBORUnmarshaler) aerrors.ActorError { - err := vmc.cst.Get(context.TODO(), c, out) - if err != nil { - return aerrors.HandleExternalError(err, "getting cid") - } - return nil -} - -func (vmc *VMContext) GetHead() cid.Cid { - return vmc.sroot -} - -func (vmc *VMContext) Commit(oldh, newh cid.Cid) aerrors.ActorError { - if err := vmc.ChargeGas(gasCommit); err != nil { - return aerrors.Wrap(err, "out of gas") - } - if vmc.sroot != oldh { - return aerrors.New(1, "failed to update, inconsistent base reference") - } - - vmc.sroot = newh - return nil -} - // End of storage interface -// Storage provides access to the VM storage layer -func (vmc *VMContext) Storage() types.Storage { - return vmc -} - -func (vmc *VMContext) Ipld() cbor.IpldStore { - return vmc.cst -} - -func (vmc *VMContext) Origin() address.Address { - return vmc.origin -} - // Send allows the current execution context to invoke methods on other actors in the system -func (vmc *VMContext) Send(to address.Address, method abi.MethodNum, value types.BigInt, params []byte) ([]byte, aerrors.ActorError) { - ctx, span := trace.StartSpan(vmc.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: vmc.msg.To, - To: to, - Method: method, - Value: value, - Params: params, - GasLimit: vmc.gasAvailable, - } - - st := vmc.state - if err := st.Snapshot(ctx); err != nil { - return nil, aerrors.Fatalf("snapshot failed: %s", err) - } - defer st.ClearSnapshot() - - ret, err, _ := vmc.vm.send(ctx, msg, vmc, 0) - if err != nil { - if err := st.Revert(); err != nil { - return nil, aerrors.Escalate(err, "failed to revert state tree after failed subcall") - } - } - - mr := types.MessageReceipt{ - ExitCode: exitcode.ExitCode(aerrors.RetCode(err)), - Return: ret, - GasUsed: types.EmptyInt, - } - - var es = "" - if err != nil { - es = err.Error() - } - er := ExecutionResult{ - Msg: msg, - MsgRct: &mr, - Error: es, - } - - vmc.internalExecutions = append(vmc.internalExecutions, &er) - return ret, err -} - -// BlockHeight returns the height of the block this message was added to the chain in -func (vmc *VMContext) BlockHeight() abi.ChainEpoch { - return vmc.height -} - -func (vmc *VMContext) GasUsed() types.BigInt { - return vmc.gasUsed -} - -func (vmc *VMContext) ChargeGas(amount uint64) aerrors.ActorError { - toUse := types.NewInt(amount) - vmc.gasUsed = types.BigAdd(vmc.gasUsed, toUse) - if vmc.gasUsed.GreaterThan(vmc.gasAvailable) { - return aerrors.Newf(uint8(exitcode.SysErrOutOfGas), "not enough gas: used=%s, available=%s", vmc.gasUsed, vmc.gasAvailable) - } - return nil -} - -func (vmc *VMContext) StateTree() (types.StateTree, aerrors.ActorError) { - if vmc.msg.To != builtin.InitActorAddr { - return nil, aerrors.Escalate(fmt.Errorf("only init actor can access state tree directly"), "invalid use of StateTree") - } - - return vmc.state, nil -} - -func (vmc *VMContext) ActorCodeCID(addr address.Address) (ret cid.Cid, err error) { - act, err := vmc.state.GetActor(addr) - if err != nil { - return cid.Undef, err - } - return act.Code, nil -} - -func (vmc *VMContext) LookupID(a address.Address) (address.Address, error) { - return vmc.state.LookupID(a) -} - -const GasVerifySignature = 50 - -func (vmctx *VMContext) VerifySignature(sig *crypto.Signature, act address.Address, data []byte) aerrors.ActorError { - if err := vmctx.ChargeGas(GasVerifySignature); err != nil { - return err - } - - if act.Protocol() == address.ID { - kaddr, err := ResolveToKeyAddr(vmctx.state, vmctx.cst, act) - if err != nil { - return aerrors.Wrap(err, "failed to resolve address to key address") - } - act = kaddr - } - - if err := sigs.Verify(sig, act, data); err != nil { - return aerrors.New(2, "signature verification failed") - } - - return nil -} func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, aerrors.ActorError) { if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 { @@ -285,56 +86,35 @@ func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Ad return aast.Address, nil } -func (vmctx *VMContext) GetBalance(a address.Address) (types.BigInt, aerrors.ActorError) { - act, err := vmctx.state.GetActor(a) - switch err { - default: - return types.EmptyInt, aerrors.Escalate(err, "failed to look up actor balance") - case hamt.ErrNotFound: - return types.NewInt(0), nil - case nil: - return act.Balance, nil - } -} - -func (vmctx *VMContext) Context() context.Context { - return vmctx.ctx -} - var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil) type gasChargingBlocks struct { - chargeGas func(uint64) aerrors.ActorError + chargeGas func(uint64) under cbor.IpldBlockstore } func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) { - if err := bs.chargeGas(gasGetObj); err != nil { - return nil, err - } + bs.chargeGas(gasGetObj) blk, err := bs.under.Get(c) if err != nil { return nil, aerrors.Escalate(err, "failed to get block from blockstore") } - if err := bs.chargeGas(uint64(len(blk.RawData())) * gasGetPerByte); err != nil { - return nil, err - } + bs.chargeGas(uint64(len(blk.RawData())) * gasGetPerByte) return blk, nil } func (bs *gasChargingBlocks) Put(blk block.Block) error { - if err := bs.chargeGas(gasPutObj + uint64(len(blk.RawData()))*gasPutPerByte); err != nil { - return err - } + bs.chargeGas(gasPutObj + uint64(len(blk.RawData()))*gasPutPerByte) + if err := bs.under.Put(blk); err != nil { return aerrors.Escalate(err, "failed to write data to disk") } return nil } -func (vm *VM) makeVMContext(ctx context.Context, sroot cid.Cid, msg *types.Message, origin address.Address, usedGas types.BigInt) *VMContext { - vmc := &VMContext{ +func (vm *VM) makeRuntime(ctx context.Context, sroot cid.Cid, msg *types.Message, origin address.Address, usedGas types.BigInt) *Runtime { + rt := &Runtime{ ctx: ctx, vm: vm, state: vm.cstate, @@ -347,11 +127,12 @@ func (vm *VM) makeVMContext(ctx context.Context, sroot cid.Cid, msg *types.Messa gasUsed: usedGas, gasAvailable: msg.GasLimit, } - vmc.cst = &cbor.BasicIpldStore{ - Blocks: &gasChargingBlocks{vmc.ChargeGas, vm.cst.Blocks}, + rt.cst = &cbor.BasicIpldStore{ + Blocks: &gasChargingBlocks{rt.ChargeGas, vm.cst.Blocks}, Atlas: vm.cst.Atlas, } - return vmc + + return rt } type VM struct { @@ -399,8 +180,8 @@ type ApplyRet struct { InternalExecutions []*ExecutionResult } -func (vm *VM) send(ctx context.Context, msg *types.Message, parent *VMContext, - gasCharge uint64) ([]byte, aerrors.ActorError, *VMContext) { +func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, + gasCharge uint64) ([]byte, aerrors.ActorError, *Runtime) { st := vm.cstate @@ -428,17 +209,15 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *VMContext, gasUsed = types.BigAdd(parent.gasUsed, gasUsed) origin = parent.origin } - vmctx := vm.makeVMContext(ctx, toActor.Head, msg, origin, gasUsed) + rt := vm.makeRuntime(ctx, toActor.Head, msg, origin, gasUsed) if parent != nil { defer func() { - parent.gasUsed = vmctx.gasUsed + parent.gasUsed = rt.gasUsed }() } if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { - if aerr := vmctx.ChargeGas(gasFundTransfer); aerr != nil { - return nil, aerrors.Wrap(aerr, "sending funds"), nil - } + rt.ChargeGas(gasFundTransfer) if err := Transfer(fromActor, toActor, msg.Value); err != nil { return nil, aerrors.Absorb(err, 1, "failed to transfer funds"), nil @@ -446,14 +225,14 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *VMContext, } if msg.Method != 0 { - ret, err := vm.Invoke(toActor, vmctx, msg.Method, msg.Params) + ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params) if !aerrors.IsFatal(err) { - toActor.Head = vmctx.Storage().GetHead() + toActor.Head = rt.sroot } - return ret, err, vmctx + return ret, err, rt } - return nil, nil, vmctx + return nil, nil, rt } func checkMessage(msg *types.Message) error { @@ -546,7 +325,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, } fromActor.Nonce++ - ret, actorErr, vmctx := vm.send(ctx, msg, nil, msgGasCost) + ret, actorErr, rt := vm.send(ctx, msg, nil, msgGasCost) if aerrors.IsFatal(actorErr) { return nil, xerrors.Errorf("[from=%s,to=%s,n=%d,m=%d,h=%d] fatal error: %w", msg.From, msg.To, msg.Nonce, msg.Method, vm.blockHeight, actorErr) @@ -565,8 +344,8 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, return nil, xerrors.Errorf("revert state failed: %w", err) } } else { + gasUsed = rt.gasUsed // refund unused gas - gasUsed = vmctx.GasUsed() refund := types.BigMul(types.BigSub(msg.GasLimit, gasUsed), msg.GasPrice) if err := Transfer(gasHolder, fromActor, refund); err != nil { return nil, xerrors.Errorf("failed to refund gas") @@ -594,7 +373,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, GasUsed: gasUsed, }, ActorErr: actorErr, - InternalExecutions: vmctx.internalExecutions, + InternalExecutions: rt.internalExecutions, }, nil } @@ -744,26 +523,24 @@ func (vm *VM) SetBlockHeight(h abi.ChainEpoch) { vm.blockHeight = h } -func (vm *VM) Invoke(act *types.Actor, vmctx *VMContext, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { - ctx, span := trace.StartSpan(vmctx.ctx, "vm.Invoke") +func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { + ctx, span := trace.StartSpan(rt.ctx, "vm.Invoke") defer span.End() if span.IsRecordingEvents() { span.AddAttributes( - trace.StringAttribute("to", vmctx.Message().To.String()), + trace.StringAttribute("to", rt.Message().Receiver().String()), trace.Int64Attribute("method", int64(method)), - trace.StringAttribute("value", vmctx.Message().Value.String()), + trace.StringAttribute("value", rt.Message().ValueReceived().String()), ) } var oldCtx context.Context - oldCtx, vmctx.ctx = vmctx.ctx, ctx + oldCtx, rt.ctx = rt.ctx, ctx defer func() { - vmctx.ctx = oldCtx + rt.ctx = oldCtx }() - if err := vmctx.ChargeGas(gasInvoke); err != nil { - return nil, aerrors.Wrap(err, "invokeing") - } - ret, err := vm.inv.Invoke(act, vmctx, method, params) + rt.ChargeGas(gasInvoke) + ret, err := vm.inv.Invoke(act, rt, method, params) if err != nil { return nil, err }