Merge pull request #19 from filecoin-project/feat/invoke

Add built in invoker
This commit is contained in:
Jakub Sztandera 2019-07-11 21:09:24 +02:00 committed by GitHub
commit 60efe8542e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 1 deletions

132
chain/invoker.go Normal file
View File

@ -0,0 +1,132 @@
package chain
import (
"errors"
"fmt"
"reflect"
"github.com/ipfs/go-cid"
)
type invoker struct {
builtInCode map[cid.Cid]nativeCode
}
type invokeFunc func(act *Actor, vmctx *VMContext, params []byte) (InvokeRet, error)
type nativeCode []invokeFunc
type InvokeRet struct {
result []byte
returnCode byte
}
func newInvoker() *invoker {
inv := &invoker{
builtInCode: make(map[cid.Cid]nativeCode),
}
// add builtInCode using: register(cid, singleton)
return inv
}
func (inv *invoker) Invoke(act *Actor, vmctx *VMContext, method uint64, params []byte) (InvokeRet, error) {
code, ok := inv.builtInCode[act.Code]
if !ok {
return InvokeRet{}, errors.New("no code for actor")
}
if method >= uint64(len(code)) || code[method] == nil {
return InvokeRet{}, fmt.Errorf("no method %d on actor", method)
}
return code[method](act, vmctx, params)
}
func (inv *invoker) register(c cid.Cid, instance Invokee) {
code, err := inv.transform(instance)
if err != nil {
panic(err)
}
inv.builtInCode[c] = code
}
type unmarshalCBOR interface {
UnmarshalCBOR([]byte) (int, error)
}
type Invokee interface {
Exports() []interface{}
}
var tUnmarhsalCBOR = reflect.TypeOf((*unmarshalCBOR)(nil)).Elem()
var tError = reflect.TypeOf((*error)(nil)).Elem()
func (*invoker) transform(instance Invokee) (nativeCode, error) {
itype := reflect.TypeOf(instance)
exports := instance.Exports()
for i, m := range exports {
i := i
newErr := func(str string) error {
return fmt.Errorf("transform(%s) export(%d): %s", itype.Name(), i, str)
}
if m == nil {
continue
}
meth := reflect.ValueOf(m)
t := meth.Type()
if t.Kind() != reflect.Func {
return nil, newErr("is not a function")
}
if t.NumIn() != 3 {
return nil, newErr("wrong number of inputs should be: " +
"*Actor, *VMContext, <type of parameter>")
}
if t.In(0) != reflect.TypeOf(&Actor{}) {
return nil, newErr("first arguemnt should be *Actor")
}
if t.In(1) != reflect.TypeOf(&VMContext{}) {
return nil, newErr("second argument should be *VMContext")
}
if !t.In(2).Implements(tUnmarhsalCBOR) {
return nil, newErr("parameter doesn't implement UnmarshalCBOR")
}
if t.In(2).Kind() != reflect.Ptr {
return nil, newErr("parameter has to be a pointer")
}
if t.NumOut() != 2 {
return nil, newErr("wrong number of outputs should be: " +
"(InvokeRet, error)")
}
if t.Out(0) != reflect.TypeOf(InvokeRet{}) {
return nil, newErr("first output should be of type InvokeRet")
}
if !t.Out(1).Implements(tError) {
return nil, newErr("second output should be error type")
}
}
code := make(nativeCode, len(exports))
for id, m := range exports {
meth := reflect.ValueOf(m)
code[id] = reflect.MakeFunc(reflect.TypeOf((invokeFunc)(nil)),
func(in []reflect.Value) []reflect.Value {
paramT := meth.Type().In(2).Elem()
param := reflect.New(paramT)
inBytes := in[2].Interface().([]byte)
_, err := param.Interface().(unmarshalCBOR).UnmarshalCBOR(inBytes)
if err != nil {
return []reflect.Value{
reflect.ValueOf(InvokeRet{}),
reflect.ValueOf(err),
}
}
return meth.Call([]reflect.Value{
in[0], in[1], param,
})
}).Interface().(invokeFunc)
}
return code, nil
}

59
chain/invoker_test.go Normal file
View File

@ -0,0 +1,59 @@
package chain
import (
"testing"
"github.com/stretchr/testify/assert"
)
type basicContract struct{}
type basicParams struct {
b byte
}
func (b *basicParams) UnmarshalCBOR(in []byte) (int, error) {
b.b = in[0]
return 1, nil
}
func (b basicContract) Exports() []interface{} {
return []interface{}{
b.InvokeSomething0,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
b.InvokeSomething10,
}
}
func (basicContract) InvokeSomething0(act *Actor, vmctx *VMContext,
params *basicParams) (InvokeRet, error) {
return InvokeRet{
returnCode: params.b,
}, nil
}
func (basicContract) InvokeSomething10(act *Actor, vmctx *VMContext,
params *basicParams) (InvokeRet, error) {
return InvokeRet{
returnCode: params.b + 10,
}, nil
}
func TestInvokerBasic(t *testing.T) {
inv := invoker{}
code, err := inv.transform(basicContract{})
assert.NoError(t, err)
ret, err := code[0](nil, nil, []byte{1})
assert.NoError(t, err)
assert.Equal(t, byte(1), ret.returnCode, "return code should be 1")
ret, err = code[10](nil, nil, []byte{2})
assert.NoError(t, err)
assert.Equal(t, byte(12), ret.returnCode, "return code should be 1")
}

View File

@ -68,6 +68,7 @@ type VM struct {
buf *bufbstore.BufferedBS
blockHeight uint64
blockMiner address.Address
inv *invoker
}
func NewVM(base cid.Cid, height uint64, maddr address.Address, cs *ChainStore) (*VM, error) {
@ -85,6 +86,7 @@ func NewVM(base cid.Cid, height uint64, maddr address.Address, cs *ChainStore) (
buf: buf,
blockHeight: height,
blockMiner: maddr,
inv: newInvoker(),
}, nil
}
@ -205,5 +207,9 @@ func (vm *VM) TransferFunds(from, to address.Address, amt BigInt) error {
}
func (vm *VM) Invoke(act *Actor, vmctx *VMContext, method uint64, params []byte) ([]byte, byte, error) {
panic("Implement me")
ret, err := vm.inv.Invoke(act, vmctx, method, params)
if err != nil {
return nil, 0, err
}
return ret.result, ret.returnCode, nil
}