Skip to content

Commit de20617

Browse files
authored
fix: handle vm message param and return parsing (#1057)
- fixes #1055
1 parent f10455b commit de20617

File tree

2 files changed

+88
-46
lines changed

2 files changed

+88
-46
lines changed

lens/util/repo.go

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package util
33
import (
44
"bytes"
55
"context"
6+
"encoding/hex"
67
"encoding/json"
78
"fmt"
89
"reflect"
@@ -51,14 +52,14 @@ func ParseParams(params []byte, method abi.MethodNum, actCode cid.Cid) (string,
5152
p := reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler)
5253
if err := p.UnmarshalCBOR(bytes.NewReader(params)); err != nil {
5354
actorName := builtin.ActorNameByCode(actCode)
54-
return "", m.Name, fmt.Errorf("cbor decode into %s %s:(%s.%d) failed: %v", m.Name, actorName, actCode, method, err)
55+
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)
5556
}
5657

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

6465
return string(b), m.Name, err
@@ -78,14 +79,14 @@ func ParseReturn(ret []byte, method abi.MethodNum, actCode cid.Cid) (string, str
7879
p := reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler)
7980
if err := p.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
8081
actorName := builtin.ActorNameByCode(actCode)
81-
return "", m.Name, fmt.Errorf("cbor decode into %s %s:(%s.%d) failed: %v", m.Name, actorName, actCode, method, err)
82+
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)
8283
}
8384

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

9192
return string(b), m.Name, err
@@ -124,38 +125,6 @@ type MessageParamsReturn struct {
124125
Return string
125126
}
126127

127-
func MethodParamsReturnForMessage(m *MessageTrace, destCode cid.Cid) (*MessageParamsReturn, error) {
128-
// Method is optional, zero means a plain value transfer
129-
if m.Message.Method == 0 {
130-
return &MessageParamsReturn{
131-
MethodName: "Send",
132-
Params: "",
133-
Return: "",
134-
}, nil
135-
}
136-
137-
if !destCode.Defined() {
138-
return nil, fmt.Errorf("missing actor code")
139-
}
140-
141-
params, _, err := ParseParams(m.Message.Params, m.Message.Method, destCode)
142-
if err != nil {
143-
log.Warnf("failed to parse parameters of message %s: %v", m.Message.Cid(), err)
144-
return nil, fmt.Errorf("unknown method for actor type %s method %d: %w", destCode.String(), int64(m.Message.Method), err)
145-
}
146-
ret, method, err := ParseReturn(m.Receipt.Return, m.Message.Method, destCode)
147-
if err != nil {
148-
log.Warnf("failed to parse return of message %s: %v", m.Message.Cid(), err)
149-
return nil, fmt.Errorf("unknown method for actor type %s method %d: %w", destCode.String(), int64(m.Message.Method), err)
150-
}
151-
152-
return &MessageParamsReturn{
153-
MethodName: method,
154-
Params: params,
155-
Return: ret,
156-
}, nil
157-
}
158-
159128
func walkExecutionTrace(et *types.ExecutionTrace, trace *[]*MessageTrace) {
160129
for _, sub := range et.Subcalls {
161130
*trace = append(*trace, &MessageTrace{

tasks/messageexecutions/vm/task.go

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package vm
22

33
import (
44
"context"
5+
"encoding/hex"
56
"fmt"
67

78
"github.com/filecoin-project/go-address"
9+
"github.com/filecoin-project/go-state-types/exitcode"
810
"github.com/filecoin-project/lotus/chain/types"
911
"github.com/ipfs/go-cid"
12+
logging "github.com/ipfs/go-log/v2"
1013
"go.opentelemetry.io/otel"
1114
"go.opentelemetry.io/otel/attribute"
1215
"golang.org/x/sync/errgroup"
@@ -20,6 +23,8 @@ import (
2023
messages "github.com/filecoin-project/lily/tasks/messages"
2124
)
2225

26+
var log = logging.Logger("lily/tasks/vmmsg")
27+
2328
type Task struct {
2429
node tasks.DataSource
2530
}
@@ -85,26 +90,73 @@ func (t *Task) ProcessTipSets(ctx context.Context, current *types.TipSet, execut
8590
default:
8691
}
8792

93+
// TODO this loop could be parallelized if it becomes a bottleneck.
94+
// NB: the getActorCode method is the expensive call since it resolves addresses and may load the statetree.
8895
for _, child := range util.GetChildMessagesOf(parentMsg) {
8996
// Cid() computes a CID, so only call it once
9097
childCid := child.Message.Cid()
91-
toCode, ok := getActorCode(child.Message.To)
92-
if !ok {
98+
99+
toCode, found := getActorCode(child.Message.To)
100+
if !found && child.Receipt.ExitCode == 0 {
101+
// No destination actor code. Normally Lotus will create an account actor for unknown addresses but if the
102+
// message fails then Lotus will not allow the actor to be created, and we are left with an address of an
103+
// unknown type.
104+
// If the message was executed it means we are out of step with Lotus behaviour somehow. This probably
105+
// indicates that Lily actor type detection is out of date.
106+
log.Errorw("parsing VM message", "source_cid", parentMsg.Cid, "source_receipt", parentMsg.Ret, "child_cid", childCid, "child_receipt", child.Receipt)
93107
errorsDetected = append(errorsDetected, &messages.MessageError{
94108
Cid: parentMsg.Cid,
95-
Error: fmt.Errorf("failed to get to actor code for message: %s", childCid).Error(),
109+
Error: fmt.Errorf("failed to get to actor code for message: %s to address %s", childCid, child.Message.To).Error(),
110+
})
111+
continue
112+
}
113+
114+
// if the to actor code was not found we cannot parse params or return, record the message and continue
115+
if !found ||
116+
// if the exit code indicates an issue with params or method we cannot parse the message params
117+
child.Receipt.ExitCode == exitcode.ErrSerialization ||
118+
child.Receipt.ExitCode == exitcode.ErrIllegalArgument ||
119+
child.Receipt.ExitCode == exitcode.SysErrInvalidMethod ||
120+
// UsrErrUnsupportedMethod TODO: https://github.com/filecoin-project/go-state-types/pull/44
121+
child.Receipt.ExitCode == exitcode.ExitCode(22) {
122+
123+
// append results and continue
124+
vmMessageResults = append(vmMessageResults, &messagemodel.VMMessage{
125+
Height: int64(parentMsg.Height),
126+
StateRoot: parentMsg.StateRoot.String(),
127+
Source: parentMsg.Cid.String(),
128+
Cid: childCid.String(),
129+
From: child.Message.From.String(),
130+
To: child.Message.To.String(),
131+
Value: child.Message.Value.String(),
132+
GasUsed: child.Receipt.GasUsed,
133+
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)
134+
ActorCode: toCode.String(), // since the actor code wasn't found this will be the string of an undefined CID.
135+
Method: uint64(child.Message.Method),
136+
Params: "",
137+
Returns: "",
96138
})
97139
continue
98140
}
99-
meta, err := util.MethodParamsReturnForMessage(child, toCode)
141+
142+
// the to actor code was found and its exit code indicates the params should be parsable. We can safely
143+
// attempt to parse message params and return, but exit code may still be non-zero here.
144+
145+
params, _, err := util.ParseParams(child.Message.Params, child.Message.Method, toCode)
100146
if err != nil {
147+
// a failure here indicates an error in message param parsing, or in exitcode checks above.
101148
errorsDetected = append(errorsDetected, &messages.MessageError{
102-
Cid: parentMsg.Cid,
103-
Error: fmt.Errorf("failed get child message (%s) metadata: %w", childCid, err).Error(),
149+
Cid: parentMsg.Cid,
150+
// hex encode the params for reproduction in a unit test.
151+
Error: fmt.Errorf("failed parse child message params cid: %s to code: %s method: %d params (hex encoded): %s : %w",
152+
childCid, toCode, child.Message.Method, hex.EncodeToString(child.Message.Params), err).Error(),
104153
})
154+
// don't append message to result as it may contain invalud data.
105155
continue
106156
}
107-
vmMessageResults = append(vmMessageResults, &messagemodel.VMMessage{
157+
158+
// params successfully parsed.
159+
vmMsg := &messagemodel.VMMessage{
108160
Height: int64(parentMsg.Height),
109161
StateRoot: parentMsg.StateRoot.String(),
110162
Source: parentMsg.Cid.String(),
@@ -116,9 +168,30 @@ func (t *Task) ProcessTipSets(ctx context.Context, current *types.TipSet, execut
116168
ExitCode: int64(child.Receipt.ExitCode),
117169
ActorCode: toCode.String(),
118170
Method: uint64(child.Message.Method),
119-
Params: meta.Params,
120-
Returns: meta.Return,
121-
})
171+
Params: params,
172+
// Return will be filled below if exit code is non-zero
173+
}
174+
175+
// only parse return of successful messages since unsuccessful messages don't return a parseable value.
176+
// As an example: a message may return ErrForbidden, it will have valid params, but will not contain a
177+
// parsable return value in its receipt.
178+
if child.Receipt.ExitCode.IsSuccess() {
179+
ret, _, err := util.ParseReturn(child.Receipt.Return, child.Message.Method, toCode)
180+
if err != nil {
181+
errorsDetected = append(errorsDetected, &messages.MessageError{
182+
Cid: parentMsg.Cid,
183+
// hex encode the return for reproduction in a unit test.
184+
Error: fmt.Errorf("failed parse child message return cid: %s to code: %s method: %d return (hex encoded): %s : %w",
185+
childCid, toCode, child.Message.Method, hex.EncodeToString(child.Receipt.Return), err).Error(),
186+
})
187+
// don't append message to result as it may contain invalid data.
188+
continue
189+
}
190+
// add the message return.
191+
vmMsg.Returns = ret
192+
}
193+
// append message to results
194+
vmMessageResults = append(vmMessageResults, vmMsg)
122195
}
123196
}
124197

0 commit comments

Comments
 (0)