Skip to content

Commit

Permalink
feat: venus-shared: add util for parsing message (#5145)
Browse files Browse the repository at this point in the history
* feat: shared: add util for parsing message
  • Loading branch information
zl03jsj authored Jul 29, 2022
1 parent a81e7ae commit f5a4cc0
Show file tree
Hide file tree
Showing 9 changed files with 633 additions and 14 deletions.
9 changes: 5 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ linters-settings:
min-occurrences: 6

run:
skip-dirs-use-default: false
skip-dirs:
- pkg/constants$
- pkg/util/test$
skip-dirs:
- pkg/constants$
- pkg/util/test$
skip-files:
- ".*_gen\\.go$" # skip auto generated go files
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ MODULES:=

ldflags=-X=github.com/filecoin-project/venus/pkg/constants.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null))
ifneq ($(strip $(LDFLAGS)),)
ldflags+=-extldflags=$(LDFLAGS)
endif
ldflags+=-extldflags=$(LDFLAGS)
endif

GOFLAGS+=-ldflags="$(ldflags)"

Expand Down Expand Up @@ -103,7 +103,7 @@ test:
go test -v ./... -integration=true -unit=false

lint: $(BUILD_DEPS)
staticcheck ./...
golangci-lint run

deps: $(BUILD_DEPS)

Expand Down
4 changes: 1 addition & 3 deletions app/node/builder_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,7 @@ func SetNetParams(params *config.NetworkParamsConfig) {
}
if len(params.ReplaceProofTypes) > 0 {
newSupportedTypes := make([]abi.RegisteredSealProof, len(params.ReplaceProofTypes))
for idx, proofType := range params.ReplaceProofTypes {
newSupportedTypes[idx] = proofType
}
copy(newSupportedTypes, params.ReplaceProofTypes)
// Switch reference rather than mutate in place to avoid concurrent map mutation (in tests).
policy.SetSupportedProofTypes(newSupportedTypes...)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ var msgSendCmd = &cmds.Command{
}

nonceOption := req.Options["nonce"]
c := cid.Undef
var c cid.Cid
if nonceOption != nil {
nonce, ok := nonceOption.(uint64)
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion pkg/market/fundmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestFundManagerBasic(t *testing.T) {
// Note: Expect failure because there is no available balance to withdraw:
// balance - reserved = 16 - 16 = 0
amt = abi.NewTokenAmount(1)
sentinel, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
_, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt)
require.Error(t, err)
}

Expand Down
3 changes: 1 addition & 2 deletions venus-shared/utils/method_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ func loadMethodsMap() {
}
}

MethodsMap[actor.Code()] = methods
if realCode.Defined() {
MethodsMap[realCode] = methods
} else {
MethodsMap[actor.Code()] = methods
}
}
}
Expand Down
70 changes: 70 additions & 0 deletions venus-shared/utils/msg_parser/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package msgparser

import (
"bytes"
"context"
"fmt"
"reflect"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/venus/venus-shared/actors/builtin"
"github.com/filecoin-project/venus/venus-shared/types"
"github.com/filecoin-project/venus/venus-shared/utils"
"github.com/ipfs/go-cid"
)

type ActorGetter interface {
StateGetActor(context.Context, address.Address, types.TipSetKey) (*types.Actor, error)
}

type MessagePaser struct {
getter ActorGetter
actors map[cid.Cid]map[abi.MethodNum]utils.MethodMeta
}

func NewMessageParser(getter ActorGetter) (*MessagePaser, error) {
return &MessagePaser{getter: getter, actors: utils.MethodsMap}, nil
}

func (parser *MessagePaser) GetMethodMeta(code cid.Cid, m abi.MethodNum) (utils.MethodMeta, bool) {
meta, ok := parser.actors[code][m]
return meta, ok
}

func (parser *MessagePaser) ParseMessage(ctx context.Context, msg *types.Message, receipt *types.MessageReceipt) (args interface{}, ret interface{}, err error) {
if int(msg.Method) == int(builtin.MethodSend) {
return nil, nil, nil
}

actor, err := parser.getter.StateGetActor(ctx, msg.To, types.EmptyTSK)
if err != nil {
return nil, nil, fmt.Errorf("get actor(%s) failed:%w", msg.To.String(), err)
}

methodMeta, found := parser.GetMethodMeta(actor.Code, msg.Method)
if !found {
return nil, nil, fmt.Errorf("actor:%v method(%d) not exist", actor, msg.Method)
}

in := reflect.New(methodMeta.Params.Elem()).Interface()
if unmarshaler, isok := in.(cbor.Unmarshaler); isok {
if err = unmarshaler.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil {
return nil, nil, fmt.Errorf("unmarshalerCBOR msg params failed:%w", err)
}
}

var out interface{}
if receipt != nil && receipt.ExitCode == exitcode.Ok {
out = reflect.New(methodMeta.Ret.Elem()).Interface()
if unmarshaler, isok := out.(cbor.Unmarshaler); isok {
if err = unmarshaler.UnmarshalCBOR(bytes.NewReader(receipt.Return)); err != nil {
return nil, nil, fmt.Errorf("unmarshalerCBOR msg returns failed:%w", err)
}
}
}

return in, out, nil
}
236 changes: 236 additions & 0 deletions venus-shared/utils/msg_parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package msgparser

import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"reflect"
"testing"

tf "github.com/filecoin-project/venus/pkg/testhelpers/testflags"

"github.com/filecoin-project/go-address"
cbor "github.com/filecoin-project/go-cbor-util"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/specs-actors/v8/actors/builtin"
"github.com/filecoin-project/venus/venus-shared/types"
"github.com/stretchr/testify/require"
)

func init() {
address.CurrentNetwork = address.Mainnet
}

type mockActorGetter struct {
actors map[string]*types.Actor
}

func (x *mockActorGetter) StateGetActor(ctx context.Context, addr address.Address, _ types.TipSetKey) (*types.Actor, error) {
actor, isok := x.actors[addr.String()]
if !isok {
return nil, fmt.Errorf("address:%s not found", addr.String())
}
return actor, nil
}

func newMockActorGetter(t *testing.T) *mockActorGetter {
return &mockActorGetter{
actors: map[string]*types.Actor{
"f01": {Code: builtin.InitActorCodeID},
"f02": {Code: builtin.RewardActorCodeID},
"f03": {Code: builtin.CronActorCodeID},
"f04": {Code: builtin.StoragePowerActorCodeID},
"f05": {Code: builtin.StorageMarketActorCodeID},
"f06": {Code: builtin.VerifiedRegistryActorCodeID},
"f2nbgz5oxgdqjkuvkbtdzwiepisiycnpjyibcvmpy": {Code: builtin.MultisigActorCodeID},
"f14mj3nt7tlgyditvk6h5p6i36vqebwgfqndjeq6a": {Code: builtin.AccountActorCodeID},
"f2ttj54ce3xziwij7qnyzbadu3bhxjewriol66tai": {Code: builtin.MultisigActorCodeID},
"f028027": {Code: builtin.MultisigActorCodeID},
"f01481491": {Code: builtin.StorageMinerActorCodeID},
"f066563": {Code: builtin.StorageMinerActorCodeID},
"f0748179": {Code: builtin.StorageMinerActorCodeID},
"f0116287": {Code: builtin.StorageMinerActorCodeID},
"f033036": {Code: builtin.StorageMinerActorCodeID},
"f0807472": {Code: builtin.StorageMinerActorCodeID},
"f0392813": {Code: builtin.StorageMinerActorCodeID},
"f0441116": {Code: builtin.StorageMinerActorCodeID},
"f01111881": {Code: builtin.StorageMinerActorCodeID},
"f01387570": {Code: builtin.StorageMinerActorCodeID},
"f0717289": {Code: builtin.StorageMinerActorCodeID},
"f01476109": {Code: builtin.StorageMinerActorCodeID},
"f01451690": {Code: builtin.StorageMinerActorCodeID},
"f01203636": {Code: builtin.StorageMinerActorCodeID},
"f01171513": {Code: builtin.StorageMinerActorCodeID},
"f054464": {Code: builtin.StorageMinerActorCodeID},
"f0106363": {Code: builtin.StorageMinerActorCodeID},
"f01190965": {Code: builtin.StorageMinerActorCodeID},
"f02388": {Code: builtin.StorageMinerActorCodeID},
"f01203143": {Code: builtin.StorageMinerActorCodeID},
"f01211558": {Code: builtin.StorageMinerActorCodeID},
"f01483143": {Code: builtin.StorageMinerActorCodeID},
"f027083": {Code: builtin.MultisigActorCodeID},
},
}
}

type testCase map[string]interface{}

func (c testCase) name(t *testing.T) string {
return c.stringField(t, "name")
}

func (c testCase) stringField(t *testing.T, fieldName string) string {
fs, isok := c[fieldName]
if !isok || fs == nil {
return ""
}
str, isok := fs.(string)
if !isok {
t.Fatalf("'%s' field must be base64 string", fieldName)
}
return str
}

func (c testCase) base64Field(t *testing.T, fieldName string) []byte {
bytes, err := base64.StdEncoding.DecodeString(c.stringField(t, fieldName))
if err != nil {
t.Fatalf("decode field(%s) base64 to bytes failed:%v", fieldName, err)
}
return bytes
}

func (c testCase) addressFiled(t *testing.T, fieldName string) address.Address {
addStr := c.stringField(t, fieldName)
addr, err := address.NewFromString(addStr)
if err != nil {
t.Fatalf("decode string(%s) to address failed:%v", addStr, err)
}
return addr
}

func (c testCase) numField(t *testing.T, fieldName string) int64 {
num, isok := c[fieldName]
if !isok {
t.Fatalf("can't find field:%s", fieldName)
}

n, isok := num.(float64)
if !isok {
t.Fatalf("field(%s) is not number", fieldName)
}
return int64(n)
}

func (c testCase) msgid() string {
if msgCid, isok := c["cid"]; isok {
if s, isok := msgCid.(string); isok {
return s
}
}
return "not exist"
}

func (c testCase) receipt(t *testing.T) *types.MessageReceipt {
// doesn't care about other filed
return &types.MessageReceipt{
Return: c.base64Field(t, "return"),
}
}

func (c testCase) message(t *testing.T) *types.Message {
return &types.Message{
Version: 0,
To: c.addressFiled(t, "to"),
Method: abi.MethodNum(c.numField(t, "method")),
Params: c.base64Field(t, "params"),
}
}

func (c testCase) wantArgs(t *testing.T, p reflect.Type) interface{} {
if p == nil {
return nil
}
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
i := reflect.New(p).Interface()
data := c.base64Field(t, "params")
if len(data) == 0 {
data = []byte("{}")
}
if err := cbor.ReadCborRPC(bytes.NewReader(data), i); err != nil {
require.NoError(t, err)
}
return i
}

func (c testCase) wantRet(t *testing.T, p reflect.Type) interface{} {
if p == nil {
return nil
}
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
i := reflect.New(p).Interface()
data := c.base64Field(t, "return")
if len(data) == 0 {
data = []byte("{}")
}
if err := cbor.ReadCborRPC(bytes.NewReader(data), i); err != nil {
require.NoError(t, err)
}
return i
}

func (c testCase) wantErr() bool {
isErr, isok := c["is_err"]
if isok {
if b, isok := isErr.(bool); isok {
return b
}
}
return false
}

func TestMessagePaser_ParseMessage(t *testing.T) {
tf.UnitTest(t)
var tests []testCase
file := "./test_cases_parsing_message.json"
data, err := ioutil.ReadFile(file)
require.NoErrorf(t, err, "read file:%s failed:%v", file, err)
require.NoErrorf(t, json.Unmarshal(data, &tests), "unmarshal data to test cases failed")

ms, err := NewMessageParser(newMockActorGetter(t))
require.NoError(t, err)

ctx := context.TODO()

for _, tt := range tests {
t.Run(tt.name(t), func(t *testing.T) {
msgID := tt.msgid()
msg := tt.message(t)
gotArgs, gotRets, err := ms.ParseMessage(ctx, msg, tt.receipt(t))
if (err != nil) != tt.wantErr() {
t.Errorf("ParseMessage(%s) error = %v, wantErr %v\n%#v",
tt.msgid(), err, tt.wantErr(), msg)
return
}

if tt.wantErr() {
return
}
wantArgs := tt.wantArgs(t, reflect.TypeOf(gotArgs))
if !reflect.DeepEqual(gotArgs, wantArgs) {
t.Errorf("ParseMessage(%v) gotArgs = %v, want %v", msgID, gotArgs, wantArgs)
}

wantRets := tt.wantRet(t, reflect.TypeOf(gotRets))
if !reflect.DeepEqual(gotRets, wantRets) {
t.Errorf("ParseMessage(%s) gotRet = %v, want %v", msgID, gotRets, wantRets)
}
})
}
}
Loading

0 comments on commit f5a4cc0

Please sign in to comment.