add tracing apis to gethclient

This commit is contained in:
bnovil 2023-09-27 18:53:47 +08:00
parent b85c183ea7
commit 62f178bc85
2 changed files with 395 additions and 9 deletions

View file

@ -29,6 +29,8 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -78,6 +80,20 @@ type StorageResult struct {
Proof []string `json:"proof"` Proof []string `json:"proof"`
} }
// TxTraceResult is the result of a single transaction trace.
type TxTraceResult struct {
TxHash common.Hash `json:"txHash"` // transaction hash
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
}
// BlockTraceResult is the result of TraceChain
type BlockTraceResult struct {
Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace
Hash common.Hash `json:"hash"` // Block hash corresponding to this trace
Traces []interface{} `json:"traces"` // Trace results produced by the task
}
// GetProof returns the account and storage values of the specified account including the Merkle-proof. // GetProof returns the account and storage values of the specified account including the Merkle-proof.
// The block number can be nil, in which case the value is taken from the latest known block. // The block number can be nil, in which case the value is taken from the latest known block.
func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) { func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) {
@ -204,6 +220,46 @@ func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- co
return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions") return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions")
} }
// TraceCall lets you trace a given eth_call
func (ec *Client) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *tracers.TraceCallConfig) (interface{}, error) {
var result interface{}
err := ec.c.CallContext(ctx, &result, "debug_traceCall", args, blockNrOrHash, config)
return result, err
}
// TraceTransaction returns the structured logs created during the execution of EVM
func (ec *Client) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig) (interface{}, error) {
var result interface{}
err := ec.c.CallContext(ctx, &result, "debug_traceTransaction", hash, config)
return result, err
}
// TraceChain TraceChaiin subscribes to chain, receiving results from channel BlockTraceResult
func (ec *Client) TraceChain(ctx context.Context, ch chan<- BlockTraceResult, start, end rpc.BlockNumber, config *tracers.TraceConfig) (*rpc.ClientSubscription, error) {
return ec.c.Subscribe(ctx, "debug", ch, "traceChain", start, end, config)
}
// TraceBlock returns the structured logs created during the execution of EVM
func (ec *Client) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *tracers.TraceConfig) ([]*TxTraceResult, error) {
var result []*TxTraceResult
err := ec.c.CallContext(ctx, &result, "debug_traceBlock", blob, config)
return result, err
}
// TraceBlockByNumber returns the structured logs created during the execution of EVM
func (ec *Client) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *tracers.TraceConfig) ([]*TxTraceResult, error) {
var result []*TxTraceResult
err := ec.c.CallContext(ctx, &result, "debug_traceBlockByNumber", number, config)
return result, err
}
// TraceBlockByHash returns the structured logs created during the execution of EVM
func (ec *Client) TraceBlockByHash(ctx context.Context, hash common.Hash, config *tracers.TraceConfig) ([]*TxTraceResult, error) {
var result []*TxTraceResult
err := ec.c.CallContext(ctx, &result, "debug_traceBlockByHash", hash, config)
return result, err
}
func toBlockNumArg(number *big.Int) string { func toBlockNumArg(number *big.Int) string {
if number == nil { if number == nil {
return "latest" return "latest"

View file

@ -20,7 +20,14 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rlp"
"math/big" "math/big"
"reflect"
"testing" "testing"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
@ -31,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"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/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"
@ -44,6 +50,8 @@ var (
testSlot = common.HexToHash("0xdeadbeef") testSlot = common.HexToHash("0xdeadbeef")
testValue = crypto.Keccak256Hash(testSlot[:]) testValue = crypto.Keccak256Hash(testSlot[:])
testBalance = big.NewInt(2e15) testBalance = big.NewInt(2e15)
testBlocks []*types.Block
testTransactionHash common.Hash
) )
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
@ -64,6 +72,9 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
n.RegisterAPIs([]rpc.API{{ n.RegisterAPIs([]rpc.API{{
Namespace: "eth", Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false), Service: filters.NewFilterAPI(filterSystem, false),
}, {
Namespace: "debug",
Service: tracers.NewAPI(ethservice.APIBackend),
}}) }})
// Import the test chain. // Import the test chain.
@ -83,12 +94,26 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
ExtraData: []byte("test genesis"), ExtraData: []byte("test genesis"),
Timestamp: 9000, Timestamp: 9000,
} }
signer := types.HomesteadSigner{}
generate := func(i int, g *core.BlockGen) { generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5) g.OffsetTime(5)
g.SetExtra([]byte("test")) g.SetExtra([]byte("test"))
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
Nonce: 0,
To: &testAddr,
Value: big.NewInt(1000),
Gas: params.TxGas,
GasPrice: g.BaseFee(),
Data: nil,
}), signer, testKey)
g.AddTx(tx)
testTransactionHash = tx.Hash()
} }
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate) _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate)
blocks = append([]*types.Block{genesis.ToBlock()}, blocks...) blocks = append([]*types.Block{genesis.ToBlock()}, blocks...)
testBlocks = blocks
return genesis, blocks return genesis, blocks
} }
@ -129,6 +154,24 @@ func TestGethClient(t *testing.T) {
}, { }, {
"TestCallContractWithBlockOverrides", "TestCallContractWithBlockOverrides",
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) }, func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
}, {
"TestTraceTransaction",
func(t *testing.T) { testTraceTransaction(t, client) },
}, {
"TestTraceCall",
func(t *testing.T) { testTraceCall(t, client) },
}, {
"TestTraceChain",
func(t *testing.T) { testTraceChain(t, client) },
}, {
"TestTraceBlock",
func(t *testing.T) { testTraceBlock(t, client) },
}, {
"TestTraceBlockByNumber",
func(t *testing.T) { testTraceBlockByNumber(t, client) },
}, {
"TestTraceBlockByHash",
func(t *testing.T) { testTraceBlockByHash(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 `testAcessList` fails if the miner has not yet created a // one block. The `testAcessList` fails if the miner has not yet created a
@ -154,7 +197,7 @@ func testAccessList(t *testing.T, client *rpc.Client) {
From: testAddr, From: testAddr,
To: &common.Address{}, To: &common.Address{},
Gas: 21000, Gas: 21000,
GasPrice: big.NewInt(765625000), GasPrice: big.NewInt(766599825),
Value: big.NewInt(1), Value: big.NewInt(1),
} }
al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg) al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg)
@ -317,7 +360,7 @@ func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) {
t.Fatal(err) t.Fatal(err)
} }
// Create transaction // Create transaction
tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
signer := types.LatestSignerForChainID(chainID) signer := types.LatestSignerForChainID(chainID)
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil { if err != nil {
@ -351,7 +394,7 @@ func testSubscribeFullPendingTransactions(t *testing.T, client *rpc.Client) {
t.Fatal(err) t.Fatal(err)
} }
// Create transaction // Create transaction
tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil) tx := types.NewTransaction(2, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
signer := types.LatestSignerForChainID(chainID) signer := types.LatestSignerForChainID(chainID)
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
if err != nil { if err != nil {
@ -518,3 +561,290 @@ func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
t.Fatalf("unexpected result: %x", res) t.Fatalf("unexpected result: %x", res)
} }
} }
func testTraceTransaction(t *testing.T, client *rpc.Client) {
ec := New(client)
var testSuite = []struct {
txHash common.Hash
config *tracers.TraceConfig
expectErr error
expect interface{}
}{
{
txHash: testTransactionHash,
config: nil,
expectErr: nil,
expect: map[string]interface{}{
"gas": 21000,
"failed": false,
"returnValue": "",
"structLogs": nil,
},
},
{
txHash: testTransactionHash,
config: &tracers.TraceConfig{
Config: &logger.Config{
EnableMemory: true,
},
},
expectErr: nil,
expect: map[string]interface{}{
"gas": 21000,
"failed": false,
"returnValue": "",
"structLogs": nil,
},
},
}
for i, testspec := range testSuite {
result, err := ec.TraceTransaction(context.Background(), testspec.txHash, testspec.config)
if testspec.expectErr != nil {
if err == nil {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
if !reflect.DeepEqual(err, testspec.expectErr) {
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
}
} else {
if err != nil {
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
if reflect.DeepEqual(testspec, result) {
t.Errorf("test %d: result mismatch, want %v, get %v", i, testspec.expect, result)
}
}
}
}
func testTraceCall(t *testing.T, client *rpc.Client) {
ec := New(client)
var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.TransactionArgs
config *tracers.TraceCallConfig
expectErr error
expect interface{}
}{
{
call: ethapi.TransactionArgs{
From: &testAddr,
To: &testAddr,
},
config: nil,
expectErr: nil,
expect: map[string]interface{}{
"gas": 21000,
"failed": false,
"returnValue": "",
"structLogs": nil,
},
},
// with config
{
call: ethapi.TransactionArgs{
From: &testAddr,
To: &testAddr,
},
config: &tracers.TraceCallConfig{
TraceConfig: tracers.TraceConfig{
Config: &logger.Config{
EnableMemory: true,
},
},
},
expectErr: nil,
expect: map[string]interface{}{
"gas": 21000,
"failed": false,
"returnValue": "",
"structLogs": nil,
}},
}
for i, testspec := range testSuite {
result, err := ec.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
if testspec.expectErr != nil {
if err == nil {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
if !reflect.DeepEqual(err, testspec.expectErr) {
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
}
} else {
if err != nil {
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
if reflect.DeepEqual(testspec, result) {
t.Errorf("test %d: result mismatch, want %v, get %v", i, testspec.expect, result)
}
}
}
}
func testTraceChain(t *testing.T, client *rpc.Client) {
ec := New(client)
ch := make(chan BlockTraceResult)
_, err := ec.TraceChain(context.Background(), ch, 0, 1, nil)
if err != nil {
t.Fatalf("testTraceChain error: %v", err)
}
traceBlock := <-ch
traceTxHash := common.HexToHash(traceBlock.Traces[0].(map[string]interface{})["txHash"].(string))
if traceTxHash != testTransactionHash {
t.Errorf("result mismatch, want %v, get %v", testTransactionHash, traceTxHash)
}
}
func testTraceBlock(t *testing.T, client *rpc.Client) {
ec := New(client)
block := testBlocks[1]
b, err := rlp.EncodeToBytes(block)
if err != nil {
t.Fatalf("")
}
var testSuite = []struct {
blob hexutil.Bytes
config *tracers.TraceConfig
expectErr error
expect []*TxTraceResult
}{
{
blob: b,
config: nil,
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
{
blob: b,
config: nil,
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
}
for i, testspec := range testSuite {
result, err := ec.TraceBlock(context.Background(), testspec.blob, testspec.config)
if testspec.expectErr != nil {
if err == nil {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
if !reflect.DeepEqual(err, testspec.expectErr) {
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
}
} else {
if err != nil {
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
if testspec.expect[0].TxHash != result[0].TxHash {
t.Errorf("test %d: result mismatch, want %v, get %v", i, testspec.expect[0].TxHash, testspec.expect[0].TxHash)
}
}
}
}
func testTraceBlockByNumber(t *testing.T, client *rpc.Client) {
ec := New(client)
var testSuite = []struct {
blockNumber rpc.BlockNumber
config *tracers.TraceConfig
expectErr error
expect []*TxTraceResult
}{
{
blockNumber: 1,
config: nil,
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
{
blockNumber: 1,
config: &tracers.TraceConfig{
Config: &logger.Config{
EnableMemory: true,
},
},
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
}
for i, testspec := range testSuite {
result, err := ec.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config)
if testspec.expectErr != nil {
if err == nil {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
if !reflect.DeepEqual(err, testspec.expectErr) {
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
}
} else {
if err != nil {
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
if testspec.expect[0].TxHash != result[0].TxHash {
t.Errorf("test %d: result mismatch, want %v, get %v", i, testspec.expect[0].TxHash, testspec.expect[0].TxHash)
}
}
}
}
func testTraceBlockByHash(t *testing.T, client *rpc.Client) {
ec := New(client)
var testSuite = []struct {
blockNumber rpc.BlockNumber
hash common.Hash
config *tracers.TraceConfig
expectErr error
expect []*TxTraceResult
}{
{
hash: testBlocks[1].Hash(),
config: nil,
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
{
hash: testBlocks[1].Hash(),
config: &tracers.TraceConfig{
Config: &logger.Config{
EnableMemory: true,
},
},
expectErr: nil,
expect: []*TxTraceResult{{TxHash: testTransactionHash}},
},
}
for i, testspec := range testSuite {
result, err := ec.TraceBlockByHash(context.Background(), testspec.hash, testspec.config)
if testspec.expectErr != nil {
if err == nil {
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
continue
}
if !reflect.DeepEqual(err, testspec.expectErr) {
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
}
} else {
if err != nil {
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
if testspec.expect[0].TxHash != result[0].TxHash {
t.Errorf("test %d: result mismatch, want %v, get %v", i, testspec.expect[0].TxHash, testspec.expect[0].TxHash)
}
}
}
}