From 8b35f480c4f4c3dc7379f1767e2af8eeee4559f9 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Thu, 24 Sep 2020 17:51:34 -0700 Subject: [PATCH] initial vm conversion We're probably going to want to change some of these design decisions down the road, but this is a good starting point. * We may want to use a more general test for "is actor valid at epoch". Maybe just a function? * I'd like to push some of the actor metadata down into the actor types themselves. Ideally, we'd be able to register actors with a simple `Register(validation, manyActors...)` call. --- chain/stmgr/forks_test.go | 4 +- chain/vm/gas.go | 3 +- chain/vm/gas_v0.go | 6 +- chain/vm/invoker.go | 126 ++++++++++++++++++++++++-------------- chain/vm/invoker_test.go | 2 +- chain/vm/mkactor.go | 40 ++++++------ chain/vm/runtime.go | 18 ++---- chain/vm/vm.go | 10 +-- conformance/driver.go | 5 +- node/impl/full/chain.go | 2 +- node/impl/full/state.go | 2 +- 11 files changed, 121 insertions(+), 97 deletions(-) diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 516058a81..8ec00f95f 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -106,7 +106,7 @@ func TestForkHeightTriggers(t *testing.T) { sm := NewStateManager(cg.ChainStore()) - inv := vm.NewInvoker() + inv := vm.NewActorRegistry() // predicting the address here... may break if other assumptions change taddr, err := address.NewIDAddress(1002) @@ -143,7 +143,7 @@ func TestForkHeightTriggers(t *testing.T) { return nil } - inv.Register(builtin.PaymentChannelActorCodeID, &testActor{}, &testActorState{}) + inv.Register(actors.Version0, builtin.PaymentChannelActorCodeID, &testActor{}, &testActorState{}, false) sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (*vm.VM, error) { nvm, err := vm.NewVM(ctx, vmopt) if err != nil { diff --git a/chain/vm/gas.go b/chain/vm/gas.go index 12acf6a21..6802013e5 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -9,7 +9,6 @@ import ( addr "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" vmr "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/ipfs/go-cid" ) @@ -210,7 +209,7 @@ func (ps pricedSyscalls) VerifyPoSt(vi proof.WindowPoStVerifyInfo) error { // the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the // blocks in the parent of h2 (i.e. h2's grandparent). // Returns nil and an error if the headers don't prove a fault. -func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*runtime.ConsensusFault, error) { +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr.ConsensusFault, error) { ps.chargeGas(ps.pl.OnVerifyConsensusFault()) defer ps.chargeGas(gasOnActorExec) diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index e5ded440e..bfb49c345 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -8,7 +8,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" - "github.com/filecoin-project/specs-actors/actors/builtin" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" ) type scalingCost struct { @@ -112,14 +112,14 @@ func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.M if big.Cmp(value, abi.NewTokenAmount(0)) != 0 { ret += pl.sendTransferFunds - if methodNum == builtin.MethodSend { + if methodNum == builtin0.MethodSend { // transfer only ret += pl.sendTransferOnlyPremium } extra += "t" } - if methodNum != builtin.MethodSend { + if methodNum != builtin0.MethodSend { extra += "i" // running actors is cheaper becase we hand over to actors ret += pl.sendInvokeMethod diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 7465074b4..48a574f9d 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -40,79 +40,113 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/types" ) -type Invoker struct { - builtInCode map[cid.Cid]nativeCode - builtInState map[cid.Cid]reflect.Type +type ActorRegistry struct { + actors map[cid.Cid]*actorInfo } type invokeFunc func(rt vmr.Runtime, params []byte) ([]byte, aerrors.ActorError) type nativeCode []invokeFunc -func NewInvoker() *Invoker { - inv := &Invoker{ - builtInCode: make(map[cid.Cid]nativeCode), - builtInState: make(map[cid.Cid]reflect.Type), - } +type actorInfo struct { + methods nativeCode + stateType reflect.Type + // TODO: consider making this a network version range? + version actors.Version + singleton bool +} + +func NewActorRegistry() *ActorRegistry { + inv := &ActorRegistry{actors: make(map[cid.Cid]*actorInfo)} + + // TODO: define all these properties on the actors themselves, in specs-actors. // add builtInCode using: register(cid, singleton) - inv.Register(builtin0.SystemActorCodeID, system0.Actor{}, abi.EmptyValue{}) - inv.Register(builtin0.InitActorCodeID, init0.Actor{}, init0.State{}) - inv.Register(builtin0.RewardActorCodeID, reward0.Actor{}, reward0.State{}) - inv.Register(builtin0.CronActorCodeID, cron0.Actor{}, cron0.State{}) - inv.Register(builtin0.StoragePowerActorCodeID, power0.Actor{}, power0.State{}) - inv.Register(builtin0.StorageMarketActorCodeID, market0.Actor{}, market0.State{}) - inv.Register(builtin0.StorageMinerActorCodeID, miner0.Actor{}, miner0.State{}) - inv.Register(builtin0.MultisigActorCodeID, msig0.Actor{}, msig0.State{}) - inv.Register(builtin0.PaymentChannelActorCodeID, paych0.Actor{}, paych0.State{}) - inv.Register(builtin0.VerifiedRegistryActorCodeID, verifreg0.Actor{}, verifreg0.State{}) - inv.Register(builtin0.AccountActorCodeID, account0.Actor{}, account0.State{}) + inv.Register(actors.Version0, builtin0.SystemActorCodeID, system0.Actor{}, abi.EmptyValue{}, true) + inv.Register(actors.Version0, builtin0.InitActorCodeID, init0.Actor{}, init0.State{}, true) + inv.Register(actors.Version0, builtin0.RewardActorCodeID, reward0.Actor{}, reward0.State{}, true) + inv.Register(actors.Version0, builtin0.CronActorCodeID, cron0.Actor{}, cron0.State{}, true) + inv.Register(actors.Version0, builtin0.StoragePowerActorCodeID, power0.Actor{}, power0.State{}, true) + inv.Register(actors.Version0, builtin0.StorageMarketActorCodeID, market0.Actor{}, market0.State{}, true) + inv.Register(actors.Version0, builtin0.VerifiedRegistryActorCodeID, verifreg0.Actor{}, verifreg0.State{}, true) + inv.Register(actors.Version0, builtin0.StorageMinerActorCodeID, miner0.Actor{}, miner0.State{}, false) + inv.Register(actors.Version0, builtin0.MultisigActorCodeID, msig0.Actor{}, msig0.State{}, false) + inv.Register(actors.Version0, builtin0.PaymentChannelActorCodeID, paych0.Actor{}, paych0.State{}, false) + inv.Register(actors.Version0, builtin0.AccountActorCodeID, account0.Actor{}, account0.State{}, false) - inv.Register(builtin1.SystemActorCodeID, system1.Actor{}, abi.EmptyValue{}) - inv.Register(builtin1.InitActorCodeID, init1.Actor{}, init1.State{}) - inv.Register(builtin1.RewardActorCodeID, reward1.Actor{}, reward1.State{}) - inv.Register(builtin1.CronActorCodeID, cron1.Actor{}, cron1.State{}) - inv.Register(builtin1.StoragePowerActorCodeID, power1.Actor{}, power1.State{}) - inv.Register(builtin1.StorageMarketActorCodeID, market1.Actor{}, market1.State{}) - inv.Register(builtin1.StorageMinerActorCodeID, miner1.Actor{}, miner1.State{}) - inv.Register(builtin1.MultisigActorCodeID, msig1.Actor{}, msig1.State{}) - inv.Register(builtin1.PaymentChannelActorCodeID, paych1.Actor{}, paych1.State{}) - inv.Register(builtin1.VerifiedRegistryActorCodeID, verifreg1.Actor{}, verifreg1.State{}) - inv.Register(builtin1.AccountActorCodeID, account1.Actor{}, account1.State{}) + inv.Register(actors.Version0, builtin1.SystemActorCodeID, system1.Actor{}, abi.EmptyValue{}, true) + inv.Register(actors.Version0, builtin1.InitActorCodeID, init1.Actor{}, init1.State{}, true) + inv.Register(actors.Version0, builtin1.RewardActorCodeID, reward1.Actor{}, reward1.State{}, true) + inv.Register(actors.Version0, builtin1.CronActorCodeID, cron1.Actor{}, cron1.State{}, true) + inv.Register(actors.Version0, builtin1.StoragePowerActorCodeID, power1.Actor{}, power1.State{}, true) + inv.Register(actors.Version0, builtin1.StorageMarketActorCodeID, market1.Actor{}, market1.State{}, true) + inv.Register(actors.Version0, builtin1.VerifiedRegistryActorCodeID, verifreg1.Actor{}, verifreg1.State{}, true) + inv.Register(actors.Version0, builtin1.StorageMinerActorCodeID, miner1.Actor{}, miner1.State{}, false) + inv.Register(actors.Version0, builtin1.MultisigActorCodeID, msig1.Actor{}, msig1.State{}, false) + inv.Register(actors.Version0, builtin1.PaymentChannelActorCodeID, paych1.Actor{}, paych1.State{}, false) + inv.Register(actors.Version0, builtin1.AccountActorCodeID, account1.Actor{}, account1.State{}, false) return inv } -func (inv *Invoker) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { - - code, ok := inv.builtInCode[codeCid] +func (ar *ActorRegistry) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { + act, ok := ar.actors[codeCid] if !ok { log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Receiver()) return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "no code for actor %s(%d)(%s)", codeCid, method, hex.EncodeToString(params)) } - if method >= abi.MethodNum(len(code)) || code[method] == nil { + if method >= abi.MethodNum(len(act.methods)) || act.methods[method] == nil { return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "no method %d on actor", method) } - return code[method](rt, params) + if curVer := actors.VersionForNetwork(rt.NetworkVersion()); curVer != act.version { + return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "unsupported actors code version %d, expected %d", act.version, curVer) + } + return act.methods[method](rt, params) } -func (inv *Invoker) Register(c cid.Cid, instance Invokee, state interface{}) { - code, err := inv.transform(instance) +func (ar *ActorRegistry) Register(version actors.Version, c cid.Cid, instance Invokee, state interface{}, singleton bool) { + code, err := ar.transform(instance) if err != nil { panic(xerrors.Errorf("%s: %w", string(c.Hash()), err)) } - inv.builtInCode[c] = code - inv.builtInState[c] = reflect.TypeOf(state) + ar.actors[c] = &actorInfo{ + methods: code, + version: version, + stateType: reflect.TypeOf(state), + singleton: singleton, + } +} + +func (ar *ActorRegistry) Create(codeCid cid.Cid, rt vmr.Runtime) (*types.Actor, aerrors.ActorError) { + act, ok := ar.actors[codeCid] + if !ok { + return nil, aerrors.Newf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.") + } + if version := actors.VersionForNetwork(rt.NetworkVersion()); act.version != version { + return nil, aerrors.Newf(exitcode.SysErrorIllegalArgument, "Can only create version %d actors, attempted to create version %d actor", version, act.version) + } + + if act.singleton { + return nil, aerrors.Newf(exitcode.SysErrorIllegalArgument, "Can only have one instance of singleton actors.") + } + return &types.Actor{ + Code: codeCid, + Head: EmptyObjectCid, + Nonce: 0, + Balance: abi.NewTokenAmount(0), + }, nil } type Invokee interface { Exports() []interface{} } -func (*Invoker) transform(instance Invokee) (nativeCode, error) { +func (*ActorRegistry) transform(instance Invokee) (nativeCode, error) { itype := reflect.TypeOf(instance) exports := instance.Exports() runtimeType := reflect.TypeOf((*vmr.Runtime)(nil)).Elem() @@ -201,19 +235,19 @@ func DecodeParams(b []byte, out interface{}) error { return um.UnmarshalCBOR(bytes.NewReader(b)) } -func DumpActorState(code cid.Cid, b []byte) (interface{}, error) { - if code == builtin0.AccountActorCodeID { // Account code special case +func DumpActorState(act *types.Actor, b []byte) (interface{}, error) { + if act.IsAccountActor() { // Account code special case return nil, nil } - i := NewInvoker() // TODO: register builtins in init block + i := NewActorRegistry() // TODO: register builtins in init block - typ, ok := i.builtInState[code] + actInfo, ok := i.actors[act.Code] if !ok { - return nil, xerrors.Errorf("state type for actor %s not found", code) + return nil, xerrors.Errorf("state type for actor %s not found", act.Code) } - rv := reflect.New(typ) + rv := reflect.New(actInfo.stateType) um, ok := rv.Interface().(cbg.CBORUnmarshaler) if !ok { return nil, xerrors.New("state type does not implement CBORUnmarshaler") diff --git a/chain/vm/invoker_test.go b/chain/vm/invoker_test.go index 3744aa8d2..4005dd42f 100644 --- a/chain/vm/invoker_test.go +++ b/chain/vm/invoker_test.go @@ -77,7 +77,7 @@ func (basicContract) InvokeSomething10(rt runtime.Runtime, params *basicParams) } func TestInvokerBasic(t *testing.T) { - inv := Invoker{} + inv := ActorRegistry{} code, err := inv.transform(basicContract{}) assert.NoError(t, err) diff --git a/chain/vm/mkactor.go b/chain/vm/mkactor.go index 43d2f9431..26e8c3ba1 100644 --- a/chain/vm/mkactor.go +++ b/chain/vm/mkactor.go @@ -6,11 +6,13 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin1 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/types" @@ -39,7 +41,7 @@ func TryCreateAccountActor(rt *Runtime, addr address.Address) (*types.Actor, aer return nil, aerrors.Escalate(err, "registering actor address") } - act, aerr := makeActor(addr) + act, aerr := makeActor(actors.VersionForNetwork(rt.NetworkVersion()), addr) if aerr != nil { return nil, aerr } @@ -54,7 +56,7 @@ func TryCreateAccountActor(rt *Runtime, addr address.Address) (*types.Actor, aer } // call constructor on account - _, aerr = rt.internalSend(builtin.SystemActorAddr, addrID, builtin.MethodsAccount.Constructor, big.Zero(), p) + _, aerr = rt.internalSend(builtin0.SystemActorAddr, addrID, builtin0.MethodsAccount.Constructor, big.Zero(), p) if aerr != nil { return nil, aerrors.Wrap(aerr, "failed to invoke account constructor") } @@ -66,12 +68,10 @@ func TryCreateAccountActor(rt *Runtime, addr address.Address) (*types.Actor, aer return act, nil } -func makeActor(addr address.Address) (*types.Actor, aerrors.ActorError) { +func makeActor(ver actors.Version, addr address.Address) (*types.Actor, aerrors.ActorError) { switch addr.Protocol() { - case address.BLS: - return NewBLSAccountActor(), nil - case address.SECP256K1: - return NewSecp256k1AccountActor(), nil + case address.BLS, address.SECP256K1: + return newAccountActor(ver), nil case address.ID: return nil, aerrors.Newf(exitcode.SysErrInvalidReceiver, "no actor with given ID: %s", addr) case address.Actor: @@ -81,19 +81,19 @@ func makeActor(addr address.Address) (*types.Actor, aerrors.ActorError) { } } -func NewBLSAccountActor() *types.Actor { +func newAccountActor(ver actors.Version) *types.Actor { + // TODO: ActorsUpgrade use a global actor registry? + var code cid.Cid + switch ver { + case actors.Version0: + code = builtin0.AccountActorCodeID + case actors.Version1: + code = builtin1.AccountActorCodeID + default: + panic("unsupported actors version") + } nact := &types.Actor{ - Code: builtin.AccountActorCodeID, - Balance: types.NewInt(0), - Head: EmptyObjectCid, - } - - return nact -} - -func NewSecp256k1AccountActor() *types.Actor { - nact := &types.Actor{ - Code: builtin.AccountActorCodeID, + Code: code, Balance: types.NewInt(0), Head: EmptyObjectCid, } diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 156d57282..3dcd269fa 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -10,13 +10,11 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/cbor" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/network" rtt "github.com/filecoin-project/go-state-types/rt" - "github.com/filecoin-project/specs-actors/actors/builtin" rt0 "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/ipfs/go-cid" ipldcbor "github.com/ipfs/go-ipld-cbor" @@ -216,12 +214,9 @@ func (rt *Runtime) NewActorAddress() address.Address { } 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.") + act, aerr := rt.vm.areg.Create(codeID, rt) + if aerr != nil { + rt.Abortf(aerr.RetCode(), aerr.Error()) } _, err := rt.state.GetActor(address) @@ -231,12 +226,7 @@ func (rt *Runtime) CreateActor(codeID cid.Cid, address address.Address) { rt.chargeGas(rt.Pricelist().OnCreateActor()) - err = rt.state.SetActor(address, &types.Actor{ - Code: codeID, - Head: EmptyObjectCid, - Nonce: 0, - Balance: big.Zero(), - }) + err = rt.state.SetActor(address, act) if err != nil { panic(aerrors.Fatalf("creating actor entry: %v", err)) } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 54ea47698..d92d16310 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -152,7 +152,7 @@ type VM struct { cst *cbor.BasicIpldStore buf *bufbstore.BufferedBS blockHeight abi.ChainEpoch - inv *Invoker + areg *ActorRegistry rand Rand circSupplyCalc CircSupplyCalculator ntwkVersion NtwkVersionGetter @@ -186,7 +186,7 @@ func NewVM(ctx context.Context, opts *VMOpts) (*VM, error) { cst: cst, buf: buf, blockHeight: opts.Epoch, - inv: NewInvoker(), + areg: NewActorRegistry(), rand: opts.Rand, // TODO: Probably should be a syscall circSupplyCalc: opts.CircSupplyCalc, ntwkVersion: opts.NtwkVersion, @@ -734,15 +734,15 @@ func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params defer func() { rt.ctx = oldCtx }() - ret, err := vm.inv.Invoke(act.Code, rt, method, params) + ret, err := vm.areg.Invoke(act.Code, rt, method, params) if err != nil { return nil, err } return ret, nil } -func (vm *VM) SetInvoker(i *Invoker) { - vm.inv = i +func (vm *VM) SetInvoker(i *ActorRegistry) { + vm.areg = i } func (vm *VM) GetNtwkVersion(ctx context.Context, ce abi.ChainEpoch) network.Version { diff --git a/conformance/driver.go b/conformance/driver.go index f43a8739d..1a63aaff3 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -5,6 +5,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -140,11 +141,11 @@ func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, preroot cid.Cid, epoch return nil, cid.Undef, err } - invoker := vm.NewInvoker() + invoker := vm.NewActorRegistry() // register the chaos actor if required by the vector. if chaosOn, ok := d.selector["chaos_actor"]; ok && chaosOn == "true" { - invoker.Register(chaos.ChaosActorCodeCID, chaos.Actor{}, chaos.State{}) + invoker.Register(actors.Version0, chaos.ChaosActorCodeCID, chaos.Actor{}, chaos.State{}, true) } lvm.SetInvoker(invoker) diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index 9606a023a..ea715c66a 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -422,7 +422,7 @@ func resolveOnce(bs blockstore.Blockstore) func(ctx context.Context, ds ipld.Nod return nil, nil, xerrors.Errorf("getting actor head for @state: %w", err) } - m, err := vm.DumpActorState(act.Code, head.RawData()) + m, err := vm.DumpActorState(&act, head.RawData()) if err != nil { return nil, nil, err } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index f8bf92a92..d2bf5cf25 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -411,7 +411,7 @@ func (a *StateAPI) StateReadState(ctx context.Context, actor address.Address, ts return nil, xerrors.Errorf("getting actor head: %w", err) } - oif, err := vm.DumpActorState(act.Code, blk.RawData()) + oif, err := vm.DumpActorState(act, blk.RawData()) if err != nil { return nil, xerrors.Errorf("dumping actor state (a:%s): %w", actor, err) }