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

feat: ETH TRACE API: Trace installed bytecode in the Ethereum JSON-RPC trace_block output #11984

Closed
wants to merge 1 commit into from
Closed
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
85 changes: 81 additions & 4 deletions node/impl/full/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,18 +855,20 @@ func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtyp
return nil, xerrors.Errorf("failed load computed state-tree: %w", err)
}

cid, err := ts.Key().Cid()
tsCid, err := ts.Key().Cid()
if err != nil {
return nil, xerrors.Errorf("failed to get tipset key cid: %w", err)
}

blkHash, err := ethtypes.EthHashFromCid(cid)
blkHash, err := ethtypes.EthHashFromCid(tsCid)
if err != nil {
return nil, xerrors.Errorf("failed to parse eth hash from cid: %w", err)
}

allTraces := make([]*ethtypes.EthTraceBlock, 0, len(trace))
msgIdx := 0
deployedCode := make(map[abi.ActorID]*cid.Cid)

for _, ir := range trace {
// ignore messages from system actor
if ir.Msg.From == builtinactors.SystemActorAddr {
Expand All @@ -888,7 +890,12 @@ func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtyp
return nil, xerrors.Errorf("when processing message %s: %w", ir.MsgCid, err)
}

err = buildTraces(env, []int{}, &ir.ExecutionTrace)
var deployed map[abi.ActorID]*cid.Cid
if ir.MsgRct.ExitCode.IsSuccess() {
deployed = deployedCode
}
Comment on lines +893 to +896
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to do this per sub-trace. The top-level message can succeed while sub-messages cain fail.

Basically, move this into buildTraces and, if the receipt of the currently processed message has a non-zero exit code, set this to nil for the sub-call.


err = buildTraces(env, []int{}, &ir.ExecutionTrace, deployed)
if err != nil {
return nil, xerrors.Errorf("failed building traces for msg %s: %w", ir.MsgCid, err)
}
Expand All @@ -904,9 +911,62 @@ func (a *EthModule) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtyp
}
}

var ethTraces []*ethtypes.EthTrace
for _, trace := range allTraces {
trace := trace
ethTraces = append(ethTraces, trace.EthTrace)
}

if err := a.updateContractAddresses(ctx, ts, ethTraces, deployedCode); err != nil {
return nil, xerrors.Errorf("failed to update contract addresses: %w", err)
}

return allTraces, nil
}

func (a *EthModule) updateContractAddresses(ctx context.Context, ts *types.TipSet, allTraces []*ethtypes.EthTrace, deployedCode map[abi.ActorID]*cid.Cid) error {
// Update Contract Addresses if contract still exists
for _, trace := range allTraces {
switch r := trace.Result.(type) {
Comment on lines +929 to +930
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct, we want to update the last one only. The idea was to record a pointer to r.Code in deployedCode, so we could set it without even having to look at the traces. That is:

deployedCode := make(map[abi.ActorID]*EthBytes)

Then, inside the buildTraces logic, we'd record deployedCode[receiverID] = &r.Code.

Finally, we'd iterate over deployedCode and set *deployedCode[receiverID] = loadByteCode(receiverID), as long as the receiver is still live.

case *ethtypes.EthCreateTraceResult:
contractAddr := r.Address
to, err := contractAddr.ToFilecoinAddress()
if err != nil {
return xerrors.Errorf("cannot get Filecoin address: %w", err)
}

actor, err := a.StateManager.LoadActor(ctx, to, ts)
if err != nil {
// failed to look up contract
continue
}
// Not a contract.
if !builtinactors.IsEvmActor(actor.Code) {
continue
}

id, err := a.StateManager.LookupID(ctx, to, ts)
if err != nil {
// mapping does not exist anymore
continue
}

codeCid, ok := deployedCode[id]
if !ok {
continue
}

blk, err := a.Chain.StateBlockstore().Get(ctx, *codeCid)
if blk == nil || err != nil {
continue
}

r.Code = blk.RawData()
}
}
return nil
}

func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
if len(traceTypes) != 1 || traceTypes[0] != "trace" {
return nil, fmt.Errorf("only 'trace' is supported")
Expand All @@ -927,6 +987,8 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum
return nil, xerrors.Errorf("failed load computed state-tree: %w", err)
}

deployedCode := make(map[abi.ActorID]*cid.Cid)

allTraces := make([]*ethtypes.EthTraceReplayBlockTransaction, 0, len(trace))
for _, ir := range trace {
// ignore messages from system actor
Expand All @@ -947,7 +1009,12 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum
return nil, xerrors.Errorf("when processing message %s: %w", ir.MsgCid, err)
}

err = buildTraces(env, []int{}, &ir.ExecutionTrace)
var deployed map[abi.ActorID]*cid.Cid
if ir.MsgRct.ExitCode.IsSuccess() {
deployed = deployedCode
}

err = buildTraces(env, []int{}, &ir.ExecutionTrace, deployed)
if err != nil {
return nil, xerrors.Errorf("failed building traces for msg %s: %w", ir.MsgCid, err)
}
Expand All @@ -971,6 +1038,16 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum
})
}

var ethTraces []*ethtypes.EthTrace
for _, trace := range allTraces {
trace := trace
ethTraces = append(ethTraces, trace.Trace...)
}

if err := a.updateContractAddresses(ctx, ts, ethTraces, deployedCode); err != nil {
return nil, xerrors.Errorf("failed to update contract addresses: %w", err)
}

return allTraces, nil
}

Expand Down
36 changes: 22 additions & 14 deletions node/impl/full/eth_trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"

"github.com/ipfs/go-cid"
"github.com/multiformats/go-multicodec"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
Expand Down Expand Up @@ -163,8 +164,8 @@ func traceErrMsg(et *types.ExecutionTrace) string {
}

// buildTraces recursively builds the traces for a given ExecutionTrace by walking the subcalls
func buildTraces(env *environment, addr []int, et *types.ExecutionTrace) error {
trace, recurseInto, err := buildTrace(env, addr, et)
func buildTraces(env *environment, addr []int, et *types.ExecutionTrace, deployedCode map[abi.ActorID]*cid.Cid) error {
trace, recurseInto, err := buildTrace(env, addr, et, deployedCode)
if err != nil {
return xerrors.Errorf("at trace %v: %w", addr, err)
}
Expand All @@ -188,7 +189,7 @@ func buildTraces(env *environment, addr []int, et *types.ExecutionTrace) error {
// end up repeatedly mutating previous paths.
addr = addr[:len(addr):len(addr)]
for i := range recurseInto.Subcalls {
err := buildTraces(subEnv, append(addr, subEnv.subtraceCount), &recurseInto.Subcalls[i])
err := buildTraces(subEnv, append(addr, subEnv.subtraceCount), &recurseInto.Subcalls[i], deployedCode)
if err != nil {
return err
}
Expand All @@ -202,7 +203,7 @@ func buildTraces(env *environment, addr []int, et *types.ExecutionTrace) error {
// buildTrace processes the passed execution trace and updates the environment, if necessary.
//
// On success, it returns a trace to add (or nil to skip) and the trace recurse into (or nil to skip).
func buildTrace(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
func buildTrace(env *environment, addr []int, et *types.ExecutionTrace, deployedCode map[abi.ActorID]*cid.Cid) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
// This function first assumes that the call is a "native" call, then handles all the "not
// native" cases. If we get any unexpected results in any of these special cases, we just
// keep the "native" interpretation and move on.
Expand Down Expand Up @@ -256,7 +257,7 @@ func buildTrace(env *environment, addr []int, et *types.ExecutionTrace) (*ethtyp
case builtin.EthereumAddressManagerActorAddr:
switch et.Msg.Method {
case builtin.MethodsEAM.Create, builtin.MethodsEAM.Create2, builtin.MethodsEAM.CreateExternal:
return traceEthCreate(env, addr, et)
return traceEthCreate(env, addr, et, deployedCode)
}
}

Expand Down Expand Up @@ -421,39 +422,39 @@ var _ *eam12.Return = (*eam12.Return)((*eam12.CreateExternalReturn)(nil))
// Decode the parameters and return value of an EVM smart contract creation through the EAM. This
// should only be called with an ExecutionTrace for a Create, Create2, or CreateExternal method
// invocation on the EAM.
func decodeCreateViaEAM(et *types.ExecutionTrace) (initcode []byte, addr *ethtypes.EthAddress, err error) {
func decodeCreateViaEAM(et *types.ExecutionTrace) (initcode []byte, addr *ethtypes.EthAddress, id uint64, err error) {
switch et.Msg.Method {
case builtin.MethodsEAM.Create:
params, err := decodeParams[eam12.CreateParams](&et.Msg)
if err != nil {
return nil, nil, err
return nil, nil, 0, err
}
initcode = params.Initcode
case builtin.MethodsEAM.Create2:
params, err := decodeParams[eam12.Create2Params](&et.Msg)
if err != nil {
return nil, nil, err
return nil, nil, 0, err
}
initcode = params.Initcode
case builtin.MethodsEAM.CreateExternal:
input, err := decodePayload(et.Msg.Params, et.Msg.ParamsCodec)
if err != nil {
return nil, nil, err
return nil, nil, 0, err
}
initcode = input
default:
return nil, nil, xerrors.Errorf("unexpected CREATE method %d", et.Msg.Method)
return nil, nil, 0, xerrors.Errorf("unexpected CREATE method %d", et.Msg.Method)
}
ret, err := decodeReturn[eam12.CreateReturn](&et.MsgRct)
if err != nil {
return nil, (*ethtypes.EthAddress)(&ret.EthAddress), err
return nil, (*ethtypes.EthAddress)(&ret.EthAddress), 0, err
}
return initcode, (*ethtypes.EthAddress)(&ret.EthAddress), nil
return initcode, (*ethtypes.EthAddress)(&ret.EthAddress), ret.ActorID, nil
}

// Build an EthTrace for an EVM "create" operation. This should only be called with an
// ExecutionTrace for a Create, Create2, or CreateExternal method invocation on the EAM.
func traceEthCreate(env *environment, addr []int, et *types.ExecutionTrace) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
func traceEthCreate(env *environment, addr []int, et *types.ExecutionTrace, deployedCode map[abi.ActorID]*cid.Cid) (*ethtypes.EthTrace, *types.ExecutionTrace, error) {
// Same as the Init actor case above, see the comment there.
if et.Msg.ReadOnly {
return nil, nil, nil
Expand Down Expand Up @@ -484,7 +485,7 @@ func traceEthCreate(env *environment, addr []int, et *types.ExecutionTrace) (*et
}

// Decode inputs & determine create type.
initcode, createdAddr, err := decodeCreateViaEAM(et)
initcode, createdAddr, id, err := decodeCreateViaEAM(et)
if err != nil {
return nil, nil, xerrors.Errorf("EAM called with invalid params or returned an invalid result, but it still tried to construct the contract: %w", err)
}
Expand All @@ -498,6 +499,13 @@ func traceEthCreate(env *environment, addr []int, et *types.ExecutionTrace) (*et
// So we don't try and include a sentinel "impossible bytecode"
// value (the value specified by EIP-3541).
output = []byte{0xFE}
if deployedCode != nil {
var getBytecodeReturn evm12.GetBytecodeReturn
if err := getBytecodeReturn.UnmarshalCBOR(bytes.NewReader(et.MsgRct.Return)); err == nil && getBytecodeReturn.Cid != nil {
deployedCode[abi.ActorID(id)] = getBytecodeReturn.Cid
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Stebalien This is not correct. How do I get the byte code of the contract here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I need to make a call to the GetBytecode method of the contract Actor ?
Looks like none of the trace types we here have the byte code in them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I missed this question. You can actually just read the bytecode from the EVM actor state (load the actor and call GetBytecode using the actor shims.

However, you have to do this after building the trace (see #11984 (comment))

}
}

case 33: // Reverted, parse the revert message.
// If we managed to call the constructor, parse/return its revert message. If we
// fail, we just return no output.
Expand Down
Loading