mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
ethclient/gethclient: callTracer methods (#31510)
Added methods `TraceCallWithCallTracer` and `TraceTransactionWithCallTracer`. Fixes #28182 --------- Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
This commit is contained in:
parent
4d4883731e
commit
15a9e92bbd
4 changed files with 349 additions and 0 deletions
104
ethclient/gethclient/gen_callframe_json.go
Normal file
104
ethclient/gethclient/gen_callframe_json.go
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package gethclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*callFrameMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (c CallFrame) MarshalJSON() ([]byte, error) {
|
||||||
|
type CallFrame0 struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
Gas hexutil.Uint64 `json:"gas"`
|
||||||
|
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
To *common.Address `json:"to,omitempty"`
|
||||||
|
Input hexutil.Bytes `json:"input"`
|
||||||
|
Output hexutil.Bytes `json:"output,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
RevertReason string `json:"revertReason,omitempty"`
|
||||||
|
Calls []CallFrame `json:"calls,omitempty"`
|
||||||
|
Logs []CallLog `json:"logs,omitempty"`
|
||||||
|
Value *hexutil.Big `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
var enc CallFrame0
|
||||||
|
enc.Type = c.Type
|
||||||
|
enc.From = c.From
|
||||||
|
enc.Gas = hexutil.Uint64(c.Gas)
|
||||||
|
enc.GasUsed = hexutil.Uint64(c.GasUsed)
|
||||||
|
enc.To = c.To
|
||||||
|
enc.Input = c.Input
|
||||||
|
enc.Output = c.Output
|
||||||
|
enc.Error = c.Error
|
||||||
|
enc.RevertReason = c.RevertReason
|
||||||
|
enc.Calls = c.Calls
|
||||||
|
enc.Logs = c.Logs
|
||||||
|
enc.Value = (*hexutil.Big)(c.Value)
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (c *CallFrame) UnmarshalJSON(input []byte) error {
|
||||||
|
type CallFrame0 struct {
|
||||||
|
Type *string `json:"type"`
|
||||||
|
From *common.Address `json:"from"`
|
||||||
|
Gas *hexutil.Uint64 `json:"gas"`
|
||||||
|
GasUsed *hexutil.Uint64 `json:"gasUsed"`
|
||||||
|
To *common.Address `json:"to,omitempty"`
|
||||||
|
Input *hexutil.Bytes `json:"input"`
|
||||||
|
Output *hexutil.Bytes `json:"output,omitempty"`
|
||||||
|
Error *string `json:"error,omitempty"`
|
||||||
|
RevertReason *string `json:"revertReason,omitempty"`
|
||||||
|
Calls []CallFrame `json:"calls,omitempty"`
|
||||||
|
Logs []CallLog `json:"logs,omitempty"`
|
||||||
|
Value *hexutil.Big `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
var dec CallFrame0
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Type != nil {
|
||||||
|
c.Type = *dec.Type
|
||||||
|
}
|
||||||
|
if dec.From != nil {
|
||||||
|
c.From = *dec.From
|
||||||
|
}
|
||||||
|
if dec.Gas != nil {
|
||||||
|
c.Gas = uint64(*dec.Gas)
|
||||||
|
}
|
||||||
|
if dec.GasUsed != nil {
|
||||||
|
c.GasUsed = uint64(*dec.GasUsed)
|
||||||
|
}
|
||||||
|
if dec.To != nil {
|
||||||
|
c.To = dec.To
|
||||||
|
}
|
||||||
|
if dec.Input != nil {
|
||||||
|
c.Input = *dec.Input
|
||||||
|
}
|
||||||
|
if dec.Output != nil {
|
||||||
|
c.Output = *dec.Output
|
||||||
|
}
|
||||||
|
if dec.Error != nil {
|
||||||
|
c.Error = *dec.Error
|
||||||
|
}
|
||||||
|
if dec.RevertReason != nil {
|
||||||
|
c.RevertReason = *dec.RevertReason
|
||||||
|
}
|
||||||
|
if dec.Calls != nil {
|
||||||
|
c.Calls = dec.Calls
|
||||||
|
}
|
||||||
|
if dec.Logs != nil {
|
||||||
|
c.Logs = dec.Logs
|
||||||
|
}
|
||||||
|
if dec.Value != nil {
|
||||||
|
c.Value = (*big.Int)(dec.Value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
61
ethclient/gethclient/gen_calllog_json.go
Normal file
61
ethclient/gethclient/gen_calllog_json.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package gethclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = (*callLogMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (c CallLog) MarshalJSON() ([]byte, error) {
|
||||||
|
type CallLog struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
Topics []common.Hash `json:"topics"`
|
||||||
|
Data hexutil.Bytes `json:"data"`
|
||||||
|
Index hexutil.Uint `json:"index"`
|
||||||
|
Position hexutil.Uint `json:"position"`
|
||||||
|
}
|
||||||
|
var enc CallLog
|
||||||
|
enc.Address = c.Address
|
||||||
|
enc.Topics = c.Topics
|
||||||
|
enc.Data = c.Data
|
||||||
|
enc.Index = hexutil.Uint(c.Index)
|
||||||
|
enc.Position = hexutil.Uint(c.Position)
|
||||||
|
return json.Marshal(&enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (c *CallLog) UnmarshalJSON(input []byte) error {
|
||||||
|
type CallLog struct {
|
||||||
|
Address *common.Address `json:"address"`
|
||||||
|
Topics []common.Hash `json:"topics"`
|
||||||
|
Data *hexutil.Bytes `json:"data"`
|
||||||
|
Index *hexutil.Uint `json:"index"`
|
||||||
|
Position *hexutil.Uint `json:"position"`
|
||||||
|
}
|
||||||
|
var dec CallLog
|
||||||
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dec.Address != nil {
|
||||||
|
c.Address = *dec.Address
|
||||||
|
}
|
||||||
|
if dec.Topics != nil {
|
||||||
|
c.Topics = dec.Topics
|
||||||
|
}
|
||||||
|
if dec.Data != nil {
|
||||||
|
c.Data = *dec.Data
|
||||||
|
}
|
||||||
|
if dec.Index != nil {
|
||||||
|
c.Index = uint(*dec.Index)
|
||||||
|
}
|
||||||
|
if dec.Position != nil {
|
||||||
|
c.Position = uint(*dec.Position)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -19,10 +19,12 @@ package gethclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -229,6 +231,124 @@ func (ec *Client) TraceBlock(ctx context.Context, hash common.Hash, config *trac
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CallTracerConfig configures the call tracer for
|
||||||
|
// TraceTransactionWithCallTracer and TraceCallWithCallTracer.
|
||||||
|
type CallTracerConfig struct {
|
||||||
|
// OnlyTopCall, when true, limits tracing to the main (top-level) call only.
|
||||||
|
OnlyTopCall bool
|
||||||
|
// WithLog, when true, includes log emissions in the trace output.
|
||||||
|
WithLog bool
|
||||||
|
// Timeout is the maximum duration the tracer may run.
|
||||||
|
// Zero means the server default (5s).
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type CallLog -field-override callLogMarshaling -out gen_calllog_json.go
|
||||||
|
|
||||||
|
// CallLog represents a log emitted during a traced call.
|
||||||
|
type CallLog struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
Topics []common.Hash `json:"topics"`
|
||||||
|
Data []byte `json:"data"`
|
||||||
|
Index uint `json:"index"`
|
||||||
|
Position uint `json:"position"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type callLogMarshaling struct {
|
||||||
|
Data hexutil.Bytes
|
||||||
|
Index hexutil.Uint
|
||||||
|
Position hexutil.Uint
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate go run github.com/fjl/gencodec -type CallFrame -field-override callFrameMarshaling -out gen_callframe_json.go
|
||||||
|
|
||||||
|
// CallFrame contains the result of a call tracer run.
|
||||||
|
type CallFrame struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
Gas uint64 `json:"gas"`
|
||||||
|
GasUsed uint64 `json:"gasUsed"`
|
||||||
|
To *common.Address `json:"to,omitempty"`
|
||||||
|
Input []byte `json:"input"`
|
||||||
|
Output []byte `json:"output,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
RevertReason string `json:"revertReason,omitempty"`
|
||||||
|
Calls []CallFrame `json:"calls,omitempty"`
|
||||||
|
Logs []CallLog `json:"logs,omitempty"`
|
||||||
|
Value *big.Int `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type callFrameMarshaling struct {
|
||||||
|
Gas hexutil.Uint64
|
||||||
|
GasUsed hexutil.Uint64
|
||||||
|
Input hexutil.Bytes
|
||||||
|
Output hexutil.Bytes
|
||||||
|
Value *hexutil.Big
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceTransactionWithCallTracer traces a transaction with the call tracer
|
||||||
|
// and returns a typed CallFrame. If config is nil, defaults are used.
|
||||||
|
func (ec *Client) TraceTransactionWithCallTracer(ctx context.Context, txHash common.Hash, config *CallTracerConfig) (*CallFrame, error) {
|
||||||
|
var result CallFrame
|
||||||
|
err := ec.c.CallContext(ctx, &result, "debug_traceTransaction", txHash, callTracerConfig(config))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceCallWithCallTracer executes a call with the call tracer and returns
|
||||||
|
// a typed CallFrame. blockNrOrHash selects the block context for the call.
|
||||||
|
// overrides specifies state overrides (nil for none), blockOverrides specifies
|
||||||
|
// block header overrides (nil for none), and config configures the tracer
|
||||||
|
// (nil for defaults).
|
||||||
|
func (ec *Client) TraceCallWithCallTracer(ctx context.Context, msg ethereum.CallMsg, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]OverrideAccount, blockOverrides *BlockOverrides, config *CallTracerConfig) (*CallFrame, error) {
|
||||||
|
var result CallFrame
|
||||||
|
err := ec.c.CallContext(ctx, &result, "debug_traceCall", toCallArg(msg), blockNrOrHash, callTraceCallConfig(config, overrides, blockOverrides))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// callTracerConfig converts a CallTracerConfig to the wire-format TraceConfig.
|
||||||
|
func callTracerConfig(config *CallTracerConfig) *tracers.TraceConfig {
|
||||||
|
tracer := "callTracer"
|
||||||
|
tc := &tracers.TraceConfig{Tracer: &tracer}
|
||||||
|
if config != nil {
|
||||||
|
if config.OnlyTopCall || config.WithLog {
|
||||||
|
cfg, _ := json.Marshal(struct {
|
||||||
|
OnlyTopCall bool `json:"onlyTopCall"`
|
||||||
|
WithLog bool `json:"withLog"`
|
||||||
|
}{config.OnlyTopCall, config.WithLog})
|
||||||
|
tc.TracerConfig = cfg
|
||||||
|
}
|
||||||
|
if config.Timeout != 0 {
|
||||||
|
s := config.Timeout.String()
|
||||||
|
tc.Timeout = &s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
|
// callTraceCallConfig builds the wire-format TraceCallConfig for debug_traceCall,
|
||||||
|
// bundling tracer settings with optional state and block overrides.
|
||||||
|
func callTraceCallConfig(config *CallTracerConfig, overrides map[common.Address]OverrideAccount, blockOverrides *BlockOverrides) interface{} {
|
||||||
|
tc := callTracerConfig(config)
|
||||||
|
// debug_traceCall expects a single config object that includes both
|
||||||
|
// tracer settings and any state/block overrides.
|
||||||
|
type traceCallConfig struct {
|
||||||
|
*tracers.TraceConfig
|
||||||
|
StateOverrides map[common.Address]OverrideAccount `json:"stateOverrides,omitempty"`
|
||||||
|
BlockOverrides *BlockOverrides `json:"blockOverrides,omitempty"`
|
||||||
|
}
|
||||||
|
return &traceCallConfig{
|
||||||
|
TraceConfig: tc,
|
||||||
|
StateOverrides: overrides,
|
||||||
|
BlockOverrides: blockOverrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toBlockNumArg(number *big.Int) string {
|
func toBlockNumArg(number *big.Int) string {
|
||||||
if number == nil {
|
if number == nil {
|
||||||
return "latest"
|
return "latest"
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -161,6 +162,12 @@ func TestGethClient(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
"TestCallContractWithBlockOverrides",
|
"TestCallContractWithBlockOverrides",
|
||||||
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
||||||
|
}, {
|
||||||
|
"TestTraceTransactionWithCallTracer",
|
||||||
|
func(t *testing.T) { testTraceTransactionWithCallTracer(t, client, txHashes) },
|
||||||
|
}, {
|
||||||
|
"TestTraceCallWithCallTracer",
|
||||||
|
func(t *testing.T) { testTraceCallWithCallTracer(t, client) },
|
||||||
},
|
},
|
||||||
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
||||||
// one block. The `testAccessList` fails if the miner has not yet created a
|
// one block. The `testAccessList` fails if the miner has not yet created a
|
||||||
|
|
@ -620,3 +627,60 @@ func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
|
||||||
t.Fatalf("unexpected result: %x", res)
|
t.Fatalf("unexpected result: %x", res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testTraceTransactionWithCallTracer(t *testing.T, client *rpc.Client, txHashes []common.Hash) {
|
||||||
|
ec := New(client)
|
||||||
|
for _, txHash := range txHashes {
|
||||||
|
// With nil config (defaults).
|
||||||
|
result, err := ec.TraceTransactionWithCallTracer(context.Background(), txHash, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("nil config: %v", err)
|
||||||
|
}
|
||||||
|
if result.Type != "CALL" {
|
||||||
|
t.Fatalf("unexpected type: %s", result.Type)
|
||||||
|
}
|
||||||
|
if result.From == (common.Address{}) {
|
||||||
|
t.Fatal("from is zero")
|
||||||
|
}
|
||||||
|
if result.Gas == 0 {
|
||||||
|
t.Fatal("gas is zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
// With explicit config.
|
||||||
|
result, err = ec.TraceTransactionWithCallTracer(context.Background(), txHash,
|
||||||
|
&CallTracerConfig{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("explicit config: %v", err)
|
||||||
|
}
|
||||||
|
if result.Type != "CALL" {
|
||||||
|
t.Fatalf("unexpected type: %s", result.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTraceCallWithCallTracer(t *testing.T, client *rpc.Client) {
|
||||||
|
ec := New(client)
|
||||||
|
msg := ethereum.CallMsg{
|
||||||
|
From: testAddr,
|
||||||
|
To: &common.Address{},
|
||||||
|
Gas: 21000,
|
||||||
|
GasPrice: big.NewInt(1000000000),
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
}
|
||||||
|
result, err := ec.TraceCallWithCallTracer(context.Background(), msg,
|
||||||
|
rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), nil, nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result.Type != "CALL" {
|
||||||
|
t.Fatalf("unexpected type: %s", result.Type)
|
||||||
|
}
|
||||||
|
if result.From == (common.Address{}) {
|
||||||
|
t.Fatal("from is zero")
|
||||||
|
}
|
||||||
|
if result.Gas == 0 {
|
||||||
|
t.Fatal("gas is zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue