Skip to content

Commit

Permalink
Improve the correctness of Eth's trace_block
Browse files Browse the repository at this point in the history
- Improve encoding/decoding of parameters and return values:
  - Encode "native" parameters and return values with Solidity ABI.
  - Correctly decode parameters to "create" calls.
  - Use the correct (ish) output for "create" calls.
  - Handle all forms of "create".
- Make robust with respect to reverts:
  - Use the actor ID/address from the trace instead of looking it up in
    the state-tree (may not exist in the state-tree due to a revert).
  - Gracefully handle failed actor/contract creation.
- Improve performance:
  - We avoid looking anything up in the state-tree when translating the
    trace, which should significantly improve performance.
- Improve code readability:
  - Remove all "backtracking" logic.
  - Use an "environment" struct to store temporary state instead of
    attaching it to the trace.
- Fix random bugs:
  - Fix an allocation bug in the "address" logic (need to set the
    capacity before modifying the slice).
  - Improved error checking/handling.
  • Loading branch information
Stebalien committed Feb 7, 2024
1 parent ca87794 commit fefa8f3
Show file tree
Hide file tree
Showing 7 changed files with 452 additions and 232 deletions.
Binary file modified build/openrpc/full.json.gz
Binary file not shown.
Binary file modified build/openrpc/gateway.json.gz
Binary file not shown.
38 changes: 16 additions & 22 deletions chain/types/ethtypes/eth_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,17 +349,21 @@ func IsEthAddress(addr address.Address) bool {
return namespace == builtintypes.EthereumAddressManagerActorID && len(payload) == 20 && !bytes.HasPrefix(payload, maskedIDPrefix[:])
}

func EthAddressFromActorID(id abi.ActorID) EthAddress {
var ethaddr EthAddress
ethaddr[0] = 0xff
binary.BigEndian.PutUint64(ethaddr[12:], uint64(id))
return ethaddr
}

func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) {
switch addr.Protocol() {
case address.ID:
id, err := address.IDFromAddress(addr)
if err != nil {
return EthAddress{}, err
}
var ethaddr EthAddress
ethaddr[0] = 0xff
binary.BigEndian.PutUint64(ethaddr[12:], id)
return ethaddr, nil
return EthAddressFromActorID(abi.ActorID(id)), nil
case address.Delegated:
payload := addr.Payload()
namespace, n, err := varint.FromUvarint(payload)
Expand Down Expand Up @@ -987,13 +991,8 @@ type EthTrace struct {
Result EthTraceResult `json:"result"`
Subtraces int `json:"subtraces"`
TraceAddress []int `json:"traceAddress"`
Type string `json:"Type"`

Parent *EthTrace `json:"-"`

// if a subtrace makes a call to GetBytecode, we store a pointer to that subtrace here
// which we then lookup when checking for delegatecall (InvokeContractDelegate)
LastByteCode *EthTrace `json:"-"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
}

func (t *EthTrace) SetCallType(callType string) {
Expand All @@ -1018,17 +1017,12 @@ type EthTraceReplayBlockTransaction struct {
}

type EthTraceAction struct {
CallType string `json:"callType"`
From EthAddress `json:"from"`
To EthAddress `json:"to"`
Gas EthUint64 `json:"gas"`
Input EthBytes `json:"input"`
Value EthBigInt `json:"value"`

FilecoinMethod abi.MethodNum `json:"-"`
FilecoinCodeCid cid.Cid `json:"-"`
FilecoinFrom address.Address `json:"-"`
FilecoinTo address.Address `json:"-"`
CallType string `json:"callType"`
From EthAddress `json:"from"`
To *EthAddress `json:"to"`
Gas EthUint64 `json:"gas"`
Input EthBytes `json:"input"`
Value EthBigInt `json:"value"`
}

type EthTraceResult struct {
Expand Down
6 changes: 4 additions & 2 deletions documentation/en/api-v1-unstable-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -3110,7 +3110,8 @@ Response:
"traceAddress": [
123
],
"Type": "string value",
"type": "string value",
"error": "string value",
"blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
"blockNumber": 9,
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
Expand Down Expand Up @@ -3159,7 +3160,8 @@ Response:
"traceAddress": [
123
],
"Type": "string value"
"type": "string value",
"error": "string value"
}
],
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
Expand Down
58 changes: 27 additions & 31 deletions node/impl/full/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/filecoin-project/go-jsonrpc"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v10/evm"
"github.com/filecoin-project/go-state-types/exitcode"
Expand Down Expand Up @@ -875,28 +874,28 @@ func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtyp
return nil, xerrors.Errorf("failed to get transaction hash by cid: %w", err)
}
if txHash == nil {
log.Warnf("cannot find transaction hash for cid %s", ir.MsgCid)
continue
return nil, xerrors.Errorf("cannot find transaction hash for cid %s", ir.MsgCid)
}

traces := []*ethtypes.EthTrace{}
err = buildTraces(&traces, nil, []int{}, ir.ExecutionTrace, int64(ts.Height()), st)
env, err := baseEnvironment(st, ir.Msg.From)
if err != nil {
return nil, xerrors.Errorf("failed building traces: %w", err)
return nil, xerrors.Errorf("when processing message %s: %w", ir.MsgCid, err)
}

traceBlocks := make([]*ethtypes.EthTraceBlock, 0, len(traces))
for _, trace := range traces {
traceBlocks = append(traceBlocks, &ethtypes.EthTraceBlock{
err = buildTraces(env, []int{}, &ir.ExecutionTrace)
if err != nil {
return nil, xerrors.Errorf("failed building traces for msg %s: %w", ir.MsgCid, err)
}

for _, trace := range env.traces {
allTraces = append(allTraces, &ethtypes.EthTraceBlock{
EthTrace: trace,
BlockHash: blkHash,
BlockNumber: int64(ts.Height()),
TransactionHash: *txHash,
TransactionPosition: msgIdx,
})
}

allTraces = append(allTraces, traceBlocks...)
}

return allTraces, nil
Expand Down Expand Up @@ -934,34 +933,31 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum
return nil, xerrors.Errorf("failed to get transaction hash by cid: %w", err)
}
if txHash == nil {
log.Warnf("cannot find transaction hash for cid %s", ir.MsgCid)
continue
return nil, xerrors.Errorf("cannot find transaction hash for cid %s", ir.MsgCid)
}

var output ethtypes.EthBytes
invokeCreateOnEAM := ir.Msg.To == builtin.EthereumAddressManagerActorAddr && (ir.Msg.Method == builtin.MethodsEAM.Create || ir.Msg.Method == builtin.MethodsEAM.Create2)
if ir.Msg.Method == builtin.MethodsEVM.InvokeContract || invokeCreateOnEAM {
output, err = decodePayload(ir.ExecutionTrace.MsgRct.Return, ir.ExecutionTrace.MsgRct.ReturnCodec)
if err != nil {
return nil, xerrors.Errorf("failed to decode payload: %w", err)
}
} else {
output = encodeFilecoinReturnAsABI(ir.ExecutionTrace.MsgRct.ExitCode, ir.ExecutionTrace.MsgRct.ReturnCodec, ir.ExecutionTrace.MsgRct.Return)
env, err := baseEnvironment(st, ir.Msg.From)
if err != nil {
return nil, xerrors.Errorf("when processing message %s: %w", ir.MsgCid, err)
}

t := ethtypes.EthTraceReplayBlockTransaction{
Output: output,
TransactionHash: *txHash,
StateDiff: nil,
VmTrace: nil,
err = buildTraces(env, []int{}, &ir.ExecutionTrace)
if err != nil {
return nil, xerrors.Errorf("failed building traces for msg %s: %w", ir.MsgCid, err)
}

err = buildTraces(&t.Trace, nil, []int{}, ir.ExecutionTrace, int64(ts.Height()), st)
if err != nil {
return nil, xerrors.Errorf("failed building traces: %w", err)
var output []byte
if len(env.traces) > 0 {
output = env.traces[0].Result.Output
}

allTraces = append(allTraces, &t)
allTraces = append(allTraces, &ethtypes.EthTraceReplayBlockTransaction{
Output: output,
TransactionHash: *txHash,
Trace: env.traces,
StateDiff: nil,
VmTrace: nil,
})
}

return allTraces, nil
Expand Down
Loading

0 comments on commit fefa8f3

Please sign in to comment.