2023-08-22 16:15:14 +00:00
package full
2023-07-24 14:55:42 +00:00
import (
2023-08-01 19:15:58 +00:00
"bytes"
2023-07-24 14:55:42 +00:00
2023-08-24 20:05:11 +00:00
"github.com/multiformats/go-multicodec"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
2023-08-03 15:18:01 +00:00
"github.com/filecoin-project/go-state-types/builtin"
2023-08-25 21:50:24 +00:00
"github.com/filecoin-project/go-state-types/builtin/v10/evm"
2023-07-24 14:55:42 +00:00
2023-08-04 11:35:49 +00:00
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
2023-11-17 17:20:31 +00:00
"github.com/filecoin-project/lotus/chain/state"
2023-07-24 14:55:42 +00:00
"github.com/filecoin-project/lotus/chain/types"
2023-08-22 16:15:14 +00:00
"github.com/filecoin-project/lotus/chain/types/ethtypes"
2023-07-24 14:55:42 +00:00
)
2023-08-25 18:21:48 +00:00
// decodePayload is a utility function which decodes the payload using the given codec
2023-08-24 20:05:11 +00:00
func decodePayload ( payload [ ] byte , codec uint64 ) ( ethtypes . EthBytes , error ) {
if len ( payload ) == 0 {
2023-08-25 18:21:48 +00:00
return nil , nil
2023-08-24 20:05:11 +00:00
}
switch multicodec . Code ( codec ) {
case multicodec . Identity :
2023-08-25 18:21:48 +00:00
return nil , nil
2023-08-24 20:05:11 +00:00
case multicodec . DagCbor , multicodec . Cbor :
buf , err := cbg . ReadByteArray ( bytes . NewReader ( payload ) , uint64 ( len ( payload ) ) )
if err != nil {
return nil , xerrors . Errorf ( "decodePayload: failed to decode cbor payload: %w" , err )
}
return buf , nil
case multicodec . Raw :
return ethtypes . EthBytes ( payload ) , nil
}
return nil , xerrors . Errorf ( "decodePayload: unsupported codec: %d" , codec )
}
2023-08-22 16:15:14 +00:00
// buildTraces recursively builds the traces for a given ExecutionTrace by walking the subcalls
2023-11-17 17:20:31 +00:00
func buildTraces ( traces * [ ] * ethtypes . EthTrace , parent * ethtypes . EthTrace , addr [ ] int , et types . ExecutionTrace , height int64 , st * state . StateTree ) error {
2023-08-24 20:05:11 +00:00
// lookup the eth address from the from/to addresses. Note that this may fail but to support
// this we need to include the ActorID in the trace. For now, just log a warning and skip
// this trace.
//
// TODO: Add ActorID in trace, see https://github.com/filecoin-project/lotus/pull/11100#discussion_r1302442288
2023-11-17 17:20:31 +00:00
from , err := lookupEthAddress ( et . Msg . From , st )
2023-08-23 14:25:52 +00:00
if err != nil {
2023-08-24 20:05:11 +00:00
log . Warnf ( "buildTraces: failed to lookup from address %s: %v" , et . Msg . From , err )
return nil
2023-08-23 14:25:52 +00:00
}
2023-11-17 17:20:31 +00:00
to , err := lookupEthAddress ( et . Msg . To , st )
2023-08-23 14:25:52 +00:00
if err != nil {
2023-08-24 20:05:11 +00:00
log . Warnf ( "buildTraces: failed to lookup to address %s: %w" , et . Msg . To , err )
return nil
2023-08-23 14:25:52 +00:00
}
2024-02-07 22:22:23 +00:00
// Skip the trace if we never reached the point where we invoked this actor.
if et . InvokedActor == nil {
return nil
}
2023-08-22 16:15:14 +00:00
trace := & ethtypes . EthTrace {
Action : ethtypes . EthTraceAction {
2023-08-25 18:21:48 +00:00
From : from ,
To : to ,
2023-08-25 13:15:34 +00:00
Gas : ethtypes . EthUint64 ( et . Msg . GasLimit ) ,
Input : nil ,
Value : ethtypes . EthBigInt ( et . Msg . Value ) ,
FilecoinFrom : et . Msg . From ,
FilecoinTo : et . Msg . To ,
FilecoinMethod : et . Msg . Method ,
2024-02-07 22:22:23 +00:00
FilecoinCodeCid : et . InvokedActor . State . Code ,
2023-07-24 14:55:42 +00:00
} ,
2023-08-22 16:15:14 +00:00
Result : ethtypes . EthTraceResult {
GasUsed : ethtypes . EthUint64 ( et . SumGas ( ) . TotalGas ) ,
2023-08-24 20:05:11 +00:00
Output : nil ,
2023-07-24 14:55:42 +00:00
} ,
2023-08-25 18:21:48 +00:00
Subtraces : 0 , // will be updated by the children once they are added to the trace
2023-07-24 14:55:42 +00:00
TraceAddress : addr ,
2023-08-03 15:18:01 +00:00
2023-08-25 18:21:48 +00:00
Parent : parent ,
LastByteCode : nil ,
2023-08-01 19:15:58 +00:00
}
2023-08-22 16:15:14 +00:00
trace . SetCallType ( "call" )
2023-08-03 17:24:22 +00:00
2023-08-25 13:15:34 +00:00
if et . Msg . Method == builtin . MethodsEVM . InvokeContract {
2023-08-25 18:21:48 +00:00
log . Debugf ( "COND1 found InvokeContract call at height: %d" , height )
2023-08-25 13:15:34 +00:00
// TODO: ignore return errors since actors can send gibberish and we don't want
// to fail the whole trace in that case
2023-08-24 20:05:11 +00:00
trace . Action . Input , err = decodePayload ( et . Msg . Params , et . Msg . ParamsCodec )
if err != nil {
return xerrors . Errorf ( "buildTraces: %w" , err )
}
trace . Result . Output , err = decodePayload ( et . MsgRct . Return , et . MsgRct . ReturnCodec )
if err != nil {
return xerrors . Errorf ( "buildTraces: %w" , err )
}
} else if et . Msg . To == builtin . EthereumAddressManagerActorAddr &&
et . Msg . Method == builtin . MethodsEAM . CreateExternal {
2023-08-25 18:21:48 +00:00
log . Debugf ( "COND2 found CreateExternal call at height: %d" , height )
2023-08-24 20:05:11 +00:00
trace . Action . Input , err = decodePayload ( et . Msg . Params , et . Msg . ParamsCodec )
if err != nil {
return xerrors . Errorf ( "buildTraces: %w" , err )
}
if et . MsgRct . ExitCode . IsSuccess ( ) {
// ignore return value
trace . Result . Output = nil
} else {
// return value is the error message
trace . Result . Output , err = decodePayload ( et . MsgRct . Return , et . MsgRct . ReturnCodec )
if err != nil {
return xerrors . Errorf ( "buildTraces: %w" , err )
}
}
// treat this as a contract creation
trace . SetCallType ( "create" )
} else {
2023-08-25 18:21:48 +00:00
// we are going to assume a native method, but we may change it in one of the edge cases below
// TODO: only do this if we know it's a native method (optimization)
2023-11-06 17:41:22 +00:00
trace . Action . Input = encodeFilecoinParamsAsABI ( et . Msg . Method , et . Msg . ParamsCodec , et . Msg . Params )
trace . Result . Output = encodeFilecoinReturnAsABI ( et . MsgRct . ExitCode , et . MsgRct . ReturnCodec , et . MsgRct . Return )
2023-08-24 20:05:11 +00:00
}
2023-08-03 17:24:22 +00:00
// TODO: is it OK to check this here or is this only specific to certain edge case (evm to evm)?
2023-08-03 15:18:01 +00:00
if et . Msg . ReadOnly {
2023-08-22 16:15:14 +00:00
trace . SetCallType ( "staticcall" )
2023-08-03 15:18:01 +00:00
}
2023-08-25 18:21:48 +00:00
// there are several edge cases that require special handling when displaying the traces. Note that while iterating over
// the traces we update the trace backwards (through the parent pointer)
2023-08-03 15:18:01 +00:00
if parent != nil {
// Handle Native actor creation
//
// Actor A calls to the init actor on method 2 and The init actor creates the target actor B then calls it on method 1
2023-08-25 13:15:34 +00:00
if parent . Action . FilecoinTo == builtin . InitActorAddr &&
parent . Action . FilecoinMethod == builtin . MethodsInit . Exec &&
2023-08-08 11:44:34 +00:00
et . Msg . Method == builtin . MethodConstructor {
2024-02-07 22:22:23 +00:00
log . Debugf ( "COND3 Native actor creation! method:%d, code:%s, height:%d" , et . Msg . Method , et . InvokedActor . State . Code . String ( ) , height )
2023-08-22 16:15:14 +00:00
parent . SetCallType ( "create" )
2023-08-25 18:21:48 +00:00
parent . Action . To = to
parent . Action . Input = [ ] byte { 0xFE }
2023-08-24 20:05:11 +00:00
parent . Result . Output = nil
2023-08-03 15:18:01 +00:00
// there should never be any subcalls when creating a native actor
2023-08-29 10:38:21 +00:00
//
// TODO: add support for native actors calling another when created
2023-08-08 11:16:43 +00:00
return nil
2023-08-03 15:18:01 +00:00
}
2023-08-01 19:15:58 +00:00
2023-08-03 15:18:01 +00:00
// Handle EVM contract creation
//
// To detect EVM contract creation we need to check for the following sequence of events:
//
// 1) EVM contract A calls the EAM (Ethereum Address Manager) on method 2 (create) or 3 (create2).
// 2) The EAM calls the init actor on method 3 (Exec4).
// 3) The init actor creates the target actor B then calls it on method 1.
2023-08-22 16:15:14 +00:00
if parent . Parent != nil {
2023-08-25 13:15:34 +00:00
calledCreateOnEAM := parent . Parent . Action . FilecoinTo == builtin . EthereumAddressManagerActorAddr &&
( parent . Parent . Action . FilecoinMethod == builtin . MethodsEAM . Create || parent . Parent . Action . FilecoinMethod == builtin . MethodsEAM . Create2 )
eamCalledInitOnExec4 := parent . Action . FilecoinTo == builtin . InitActorAddr &&
parent . Action . FilecoinMethod == builtin . MethodsInit . Exec4
initCreatedActor := trace . Action . FilecoinMethod == builtin . MethodConstructor
2023-08-03 15:18:01 +00:00
2023-08-25 18:21:48 +00:00
// TODO: We need to handle failures in contract creations and support resurrections on an existing but dead EVM actor)
2023-08-03 15:18:01 +00:00
if calledCreateOnEAM && eamCalledInitOnExec4 && initCreatedActor {
2024-02-07 22:22:23 +00:00
log . Debugf ( "COND4 EVM contract creation method:%d, code:%s, height:%d" , et . Msg . Method , et . InvokedActor . State . Code . String ( ) , height )
2023-08-03 15:18:01 +00:00
2023-08-25 13:15:34 +00:00
if parent . Parent . Action . FilecoinMethod == builtin . MethodsEAM . Create {
2023-08-22 16:15:14 +00:00
parent . Parent . SetCallType ( "create" )
2023-08-03 15:18:01 +00:00
} else {
2023-08-22 16:15:14 +00:00
parent . Parent . SetCallType ( "create2" )
2023-08-03 15:18:01 +00:00
}
// update the parent.parent to make this
2023-08-22 16:15:14 +00:00
parent . Parent . Action . To = trace . Action . To
parent . Parent . Subtraces = 0
2023-08-03 15:18:01 +00:00
// delete the parent (the EAM) and skip the current trace (init)
* traces = ( * traces ) [ : len ( * traces ) - 1 ]
2023-08-08 11:16:43 +00:00
return nil
2023-08-03 15:18:01 +00:00
}
}
2023-08-01 19:15:58 +00:00
2023-08-25 21:50:24 +00:00
if builtinactors . IsEvmActor ( parent . Action . FilecoinCodeCid ) {
// Handle delegate calls
2023-08-25 18:21:48 +00:00
//
2023-08-25 21:50:24 +00:00
// 1) Look for trace from an EVM actor to itself on InvokeContractDelegate, method 6.
// 2) Check that the previous trace calls another actor on method 3 (GetByteCode) and they are at the same level (same parent)
// 3) Treat this as a delegate call to actor A.
if parent . LastByteCode != nil && trace . Action . From == trace . Action . To &&
trace . Action . FilecoinMethod == builtin . MethodsEVM . InvokeContractDelegate {
log . Debugf ( "COND7 found delegate call, height: %d" , height )
prev := parent . LastByteCode
if prev . Action . From == trace . Action . From && prev . Action . FilecoinMethod == builtin . MethodsEVM . GetBytecode && prev . Parent == trace . Parent {
trace . SetCallType ( "delegatecall" )
trace . Action . To = prev . Action . To
var dp evm . DelegateCallParams
err := dp . UnmarshalCBOR ( bytes . NewReader ( et . Msg . Params ) )
if err != nil {
return xerrors . Errorf ( "failed UnmarshalCBOR: %w" , err )
}
trace . Action . Input = dp . Input
trace . Result . Output , err = decodePayload ( et . MsgRct . Return , et . MsgRct . ReturnCodec )
if err != nil {
return xerrors . Errorf ( "failed decodePayload: %w" , err )
}
}
} else {
// Handle EVM call special casing
//
// Any outbound call from an EVM actor on methods 1-1023 are side-effects from EVM instructions
// and should be dropped from the trace.
if et . Msg . Method > 0 &&
et . Msg . Method <= 1023 {
log . Debugf ( "Infof found outbound call from an EVM actor on method 1-1023 method:%d, code:%s, height:%d" , et . Msg . Method , parent . Action . FilecoinCodeCid . String ( ) , height )
if et . Msg . Method == builtin . MethodsEVM . GetBytecode {
// save the last bytecode trace to handle delegate calls
parent . LastByteCode = trace
}
return nil
2023-08-25 18:21:48 +00:00
}
}
2023-08-03 17:24:22 +00:00
}
2023-08-25 21:50:24 +00:00
2023-08-01 19:15:58 +00:00
}
2023-08-25 18:21:48 +00:00
// we are adding trace to the traces so update the parent subtraces count as it was originally set to zero
if parent != nil {
2023-08-28 18:47:45 +00:00
parent . Subtraces ++
2023-08-25 18:21:48 +00:00
}
2023-08-01 19:15:58 +00:00
* traces = append ( * traces , trace )
2023-07-24 14:55:42 +00:00
for i , call := range et . Subcalls {
2023-11-17 17:20:31 +00:00
err := buildTraces ( traces , trace , append ( addr , i ) , call , height , st )
2023-08-08 11:16:43 +00:00
if err != nil {
return err
}
2023-07-24 14:55:42 +00:00
}
2023-08-08 11:16:43 +00:00
return nil
2023-07-24 14:55:42 +00:00
}