-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: venus-shared: add util for parsing message (#5145)
* feat: shared: add util for parsing message
- Loading branch information
Showing
9 changed files
with
633 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.