-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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 | ||
} | ||
|
||
err = buildTraces(env, []int{}, &ir.ExecutionTrace, deployed) | ||
if err != nil { | ||
return nil, xerrors.Errorf("failed building traces for msg %s: %w", ir.MsgCid, err) | ||
} | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 deployedCode := make(map[abi.ActorID]*EthBytes) Then, inside the Finally, we'd iterate over |
||
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") | ||
|
@@ -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 | ||
|
@@ -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) | ||
} | ||
|
@@ -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 | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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) | ||
} | ||
|
@@ -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 | ||
} | ||
|
@@ -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. | ||
|
@@ -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) | ||
} | ||
} | ||
|
||
|
@@ -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 | ||
|
@@ -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) | ||
} | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do I need to make a call to the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. | ||
|
There was a problem hiding this comment.
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.