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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if number == nil {
|
||||
return "latest"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"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/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -161,6 +162,12 @@ func TestGethClient(t *testing.T) {
|
|||
}, {
|
||||
"TestCallContractWithBlockOverrides",
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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