Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Robustify state manager against holes in actor method numbers #3538

Merged
merged 2 commits into from
Sep 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 60 additions & 14 deletions chain/stmgr/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package stmgr
import (
"bytes"
"context"
"fmt"
"os"
"reflect"
"runtime"
"strings"

cid "github.com/ipfs/go-cid"
cbor "github.com/ipfs/go-ipld-cbor"
Expand Down Expand Up @@ -586,14 +589,14 @@ func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBe
}, nil
}

type methodMeta struct {
type MethodMeta struct {
Name string

Params reflect.Type
Ret reflect.Type
}

var MethodsMap = map[cid.Cid][]methodMeta{}
var MethodsMap = map[cid.Cid]map[abi.MethodNum]MethodMeta{}

func init() {
cidToMethods := map[cid.Cid][2]interface{}{
Expand All @@ -611,25 +614,65 @@ func init() {
}

for c, m := range cidToMethods {
rt := reflect.TypeOf(m[0])
nf := rt.NumField()
exports := m[1].(abi.Invokee).Exports()
methods := make(map[abi.MethodNum]MethodMeta, len(exports))

MethodsMap[c] = append(MethodsMap[c], methodMeta{
// Explicitly add send, it's special.
methods[builtin.MethodSend] = MethodMeta{
Name: "Send",
Params: reflect.TypeOf(new(adt.EmptyValue)),
Ret: reflect.TypeOf(new(adt.EmptyValue)),
})
}

exports := m[1].(abi.Invokee).Exports()
// Learn method names from the builtin.Methods* structs.
rv := reflect.ValueOf(m[0])
rt := rv.Type()
nf := rt.NumField()
methodToName := make([]string, len(exports))
for i := 0; i < nf; i++ {
export := reflect.TypeOf(exports[i+1])
name := rt.Field(i).Name
number := rv.Field(i).Interface().(abi.MethodNum)
methodToName[number] = name
}

MethodsMap[c] = append(MethodsMap[c], methodMeta{
Name: rt.Field(i).Name,
Params: export.In(1),
Ret: export.Out(0),
})
// Iterate over exported methods. Some of these _may_ be nil and
// must be skipped.
for number, export := range exports {
if export == nil {
continue
}

ev := reflect.ValueOf(export)
et := ev.Type()

// Make sure the method name is correct.
// This is just a nice sanity check.
fnName := runtime.FuncForPC(ev.Pointer()).Name()
fnName = strings.TrimSuffix(fnName[strings.LastIndexByte(fnName, '.')+1:], "-fm")
mName := methodToName[number]
if mName != fnName {
panic(fmt.Sprintf(
"actor method name is %s but exported method name is %s",
fnName, mName,
))
}

switch abi.MethodNum(number) {
case builtin.MethodSend:
panic("method 0 is reserved for Send")
case builtin.MethodConstructor:
if fnName != "Constructor" {
panic("method 1 is reserved for Constructor")
}
}

methods[abi.MethodNum(number)] = MethodMeta{
Name: fnName,
Params: et.In(1),
Ret: et.Out(0),
}
}
MethodsMap[c] = methods
}
}

Expand All @@ -639,7 +682,10 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me
return nil, xerrors.Errorf("getting actor: %w", err)
}

m := MethodsMap[act.Code][method]
m, found := MethodsMap[act.Code][method]
if !found {
return nil, fmt.Errorf("unknown method %d for actor %s", method, act.Code)
}
return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil
}

Expand Down
3 changes: 3 additions & 0 deletions chain/vm/invoker.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ func (*Invoker) transform(instance Invokee) (nativeCode, error) {
}
code := make(nativeCode, len(exports))
for id, m := range exports {
if m == nil {
continue
}
meth := reflect.ValueOf(m)
code[id] = reflect.MakeFunc(reflect.TypeOf((invokeFunc)(nil)),
func(in []reflect.Value) []reflect.Value {
Expand Down
7 changes: 6 additions & 1 deletion cli/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,12 @@ func decodeTypedParams(ctx context.Context, fapi api.FullNode, to address.Addres
return nil, err
}

p := reflect.New(stmgr.MethodsMap[act.Code][method].Params.Elem()).Interface().(cbg.CBORMarshaler)
methodMeta, found := stmgr.MethodsMap[act.Code][method]
if !found {
return nil, fmt.Errorf("method %d not found on actor %s", method, act.Code)
}

p := reflect.New(methodMeta.Params.Elem()).Interface().(cbg.CBORMarshaler)

if err := json.Unmarshal([]byte(paramstr), p); err != nil {
return nil, fmt.Errorf("unmarshaling input into params type: %w", err)
Expand Down
12 changes: 10 additions & 2 deletions cli/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,11 @@ func sumGas(changes []*types.GasTrace) types.GasTrace {
}

func jsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, error) {
re := reflect.New(stmgr.MethodsMap[code][method].Params.Elem())
methodMeta, found := stmgr.MethodsMap[code][method]
if !found {
return "", fmt.Errorf("method %d not found on actor %s", method, code)
}
re := reflect.New(methodMeta.Params.Elem())
p := re.Interface().(cbg.CBORUnmarshaler)
if err := p.UnmarshalCBOR(bytes.NewReader(params)); err != nil {
return "", err
Expand All @@ -1178,7 +1182,11 @@ func jsonParams(code cid.Cid, method abi.MethodNum, params []byte) (string, erro
}

func jsonReturn(code cid.Cid, method abi.MethodNum, ret []byte) (string, error) {
re := reflect.New(stmgr.MethodsMap[code][method].Ret.Elem())
methodMeta, found := stmgr.MethodsMap[code][method]
if !found {
return "", fmt.Errorf("method %d not found on actor %s", method, code)
}
re := reflect.New(methodMeta.Ret.Elem())
p := re.Interface().(cbg.CBORUnmarshaler)
if err := p.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
return "", err
Expand Down