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

fix: handle vm message param and return parsing #1057

Merged
merged 1 commit into from
Sep 7, 2022
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
41 changes: 5 additions & 36 deletions lens/util/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package util
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
Expand Down Expand Up @@ -51,14 +52,14 @@ func ParseParams(params []byte, method abi.MethodNum, actCode cid.Cid) (string,
p := reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler)
if err := p.UnmarshalCBOR(bytes.NewReader(params)); err != nil {
actorName := builtin.ActorNameByCode(actCode)
return "", m.Name, fmt.Errorf("cbor decode into %s %s:(%s.%d) failed: %v", m.Name, actorName, actCode, method, err)
return "", m.Name, fmt.Errorf("parse message params cbor decode into %s %s:(%s.%d) return (hex): %s failed: %w", m.Name, actorName, actCode, method, hex.EncodeToString(params), err)
}

b, err := MarshalWithOverrides(p, map[reflect.Type]marshaller{
reflect.TypeOf(bitfield.BitField{}): bitfieldCountMarshaller,
})
if err != nil {
return "", "", fmt.Errorf("failed to parse message params method: %d, actor code: %s, params: %s: %w", method, actCode, string(params), err)
return "", "", fmt.Errorf("parse message params method: %d actor code: %s params: %s failed: %w", method, actCode, hex.EncodeToString(params), err)
}

return string(b), m.Name, err
Expand All @@ -78,14 +79,14 @@ func ParseReturn(ret []byte, method abi.MethodNum, actCode cid.Cid) (string, str
p := reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler)
if err := p.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
actorName := builtin.ActorNameByCode(actCode)
return "", m.Name, fmt.Errorf("cbor decode into %s %s:(%s.%d) failed: %v", m.Name, actorName, actCode, method, err)
return "", m.Name, fmt.Errorf("parse message return cbor decode into %s %s:(%s.%d) return (hex): %s failed: %w", m.Name, actorName, actCode, method, hex.EncodeToString(ret), err)
}

b, err := MarshalWithOverrides(p, map[reflect.Type]marshaller{
reflect.TypeOf(bitfield.BitField{}): bitfieldCountMarshaller,
})
if err != nil {
return "", "", fmt.Errorf("failed to parse message return method: %d, actor code: %s, return: %s: %w", method, actCode, string(ret), err)
return "", "", fmt.Errorf("parse message return method: %d actor code: %s return (hex): %s failed: %w", method, actCode, hex.EncodeToString(ret), err)
}

return string(b), m.Name, err
Expand Down Expand Up @@ -124,38 +125,6 @@ type MessageParamsReturn struct {
Return string
}

func MethodParamsReturnForMessage(m *MessageTrace, destCode cid.Cid) (*MessageParamsReturn, error) {
// Method is optional, zero means a plain value transfer
if m.Message.Method == 0 {
return &MessageParamsReturn{
MethodName: "Send",
Params: "",
Return: "",
}, nil
}

if !destCode.Defined() {
return nil, fmt.Errorf("missing actor code")
}

params, _, err := ParseParams(m.Message.Params, m.Message.Method, destCode)
if err != nil {
log.Warnf("failed to parse parameters of message %s: %v", m.Message.Cid(), err)
return nil, fmt.Errorf("unknown method for actor type %s method %d: %w", destCode.String(), int64(m.Message.Method), err)
}
ret, method, err := ParseReturn(m.Receipt.Return, m.Message.Method, destCode)
if err != nil {
log.Warnf("failed to parse return of message %s: %v", m.Message.Cid(), err)
return nil, fmt.Errorf("unknown method for actor type %s method %d: %w", destCode.String(), int64(m.Message.Method), err)
}

return &MessageParamsReturn{
MethodName: method,
Params: params,
Return: ret,
}, nil
}

func walkExecutionTrace(et *types.ExecutionTrace, trace *[]*MessageTrace) {
for _, sub := range et.Subcalls {
*trace = append(*trace, &MessageTrace{
Expand Down
93 changes: 83 additions & 10 deletions tasks/messageexecutions/vm/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package vm

import (
"context"
"encoding/hex"
"fmt"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/lotus/chain/types"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"golang.org/x/sync/errgroup"
Expand All @@ -20,6 +23,8 @@ import (
messages "github.com/filecoin-project/lily/tasks/messages"
)

var log = logging.Logger("lily/tasks/vmmsg")

type Task struct {
node tasks.DataSource
}
Expand Down Expand Up @@ -85,26 +90,73 @@ func (t *Task) ProcessTipSets(ctx context.Context, current *types.TipSet, execut
default:
}

// TODO this loop could be parallelized if it becomes a bottleneck.
// NB: the getActorCode method is the expensive call since it resolves addresses and may load the statetree.
for _, child := range util.GetChildMessagesOf(parentMsg) {
// Cid() computes a CID, so only call it once
childCid := child.Message.Cid()
toCode, ok := getActorCode(child.Message.To)
if !ok {

toCode, found := getActorCode(child.Message.To)
if !found && child.Receipt.ExitCode == 0 {
// No destination actor code. Normally Lotus will create an account actor for unknown addresses but if the
// message fails then Lotus will not allow the actor to be created, and we are left with an address of an
// unknown type.
// If the message was executed it means we are out of step with Lotus behaviour somehow. This probably
// indicates that Lily actor type detection is out of date.
log.Errorw("parsing VM message", "source_cid", parentMsg.Cid, "source_receipt", parentMsg.Ret, "child_cid", childCid, "child_receipt", child.Receipt)
errorsDetected = append(errorsDetected, &messages.MessageError{
Cid: parentMsg.Cid,
Error: fmt.Errorf("failed to get to actor code for message: %s", childCid).Error(),
Error: fmt.Errorf("failed to get to actor code for message: %s to address %s", childCid, child.Message.To).Error(),
})
continue
}

// if the to actor code was not found we cannot parse params or return, record the message and continue
if !found ||
// if the exit code indicates an issue with params or method we cannot parse the message params
child.Receipt.ExitCode == exitcode.ErrSerialization ||
child.Receipt.ExitCode == exitcode.ErrIllegalArgument ||
child.Receipt.ExitCode == exitcode.SysErrInvalidMethod ||
// UsrErrUnsupportedMethod TODO: https://github.com/filecoin-project/go-state-types/pull/44
child.Receipt.ExitCode == exitcode.ExitCode(22) {

// append results and continue
vmMessageResults = append(vmMessageResults, &messagemodel.VMMessage{
Height: int64(parentMsg.Height),
StateRoot: parentMsg.StateRoot.String(),
Source: parentMsg.Cid.String(),
Cid: childCid.String(),
From: child.Message.From.String(),
To: child.Message.To.String(),
Value: child.Message.Value.String(),
GasUsed: child.Receipt.GasUsed,
ExitCode: int64(child.Receipt.ExitCode), // exit code is guaranteed to be non-zero which will indicate why actor was not found (i.e. message that created the actor failed to apply)
ActorCode: toCode.String(), // since the actor code wasn't found this will be the string of an undefined CID.
Method: uint64(child.Message.Method),
Params: "",
Returns: "",
})
continue
}
meta, err := util.MethodParamsReturnForMessage(child, toCode)

// the to actor code was found and its exit code indicates the params should be parsable. We can safely
// attempt to parse message params and return, but exit code may still be non-zero here.

params, _, err := util.ParseParams(child.Message.Params, child.Message.Method, toCode)
if err != nil {
// a failure here indicates an error in message param parsing, or in exitcode checks above.
errorsDetected = append(errorsDetected, &messages.MessageError{
Cid: parentMsg.Cid,
Error: fmt.Errorf("failed get child message (%s) metadata: %w", childCid, err).Error(),
Cid: parentMsg.Cid,
// hex encode the params for reproduction in a unit test.
Error: fmt.Errorf("failed parse child message params cid: %s to code: %s method: %d params (hex encoded): %s : %w",
childCid, toCode, child.Message.Method, hex.EncodeToString(child.Message.Params), err).Error(),
})
// don't append message to result as it may contain invalud data.
continue
}
vmMessageResults = append(vmMessageResults, &messagemodel.VMMessage{

// params successfully parsed.
vmMsg := &messagemodel.VMMessage{
Height: int64(parentMsg.Height),
StateRoot: parentMsg.StateRoot.String(),
Source: parentMsg.Cid.String(),
Expand All @@ -116,9 +168,30 @@ func (t *Task) ProcessTipSets(ctx context.Context, current *types.TipSet, execut
ExitCode: int64(child.Receipt.ExitCode),
ActorCode: toCode.String(),
Method: uint64(child.Message.Method),
Params: meta.Params,
Returns: meta.Return,
})
Params: params,
// Return will be filled below if exit code is non-zero
}

// only parse return of successful messages since unsuccessful messages don't return a parseable value.
// As an example: a message may return ErrForbidden, it will have valid params, but will not contain a
// parsable return value in its receipt.
if child.Receipt.ExitCode.IsSuccess() {
ret, _, err := util.ParseReturn(child.Receipt.Return, child.Message.Method, toCode)
if err != nil {
errorsDetected = append(errorsDetected, &messages.MessageError{
Cid: parentMsg.Cid,
// hex encode the return for reproduction in a unit test.
Error: fmt.Errorf("failed parse child message return cid: %s to code: %s method: %d return (hex encoded): %s : %w",
childCid, toCode, child.Message.Method, hex.EncodeToString(child.Receipt.Return), err).Error(),
})
// don't append message to result as it may contain invalid data.
continue
}
// add the message return.
vmMsg.Returns = ret
}
// append message to results
vmMessageResults = append(vmMessageResults, vmMsg)
}
}

Expand Down