Skip to content

Commit 4f65eeb

Browse files
authored
Merge pull request #3521 from vitaliy-io/task/improve-evm-tracer-performance
Task/improve evm tracer performance
2 parents de3c619 + 27ae5d3 commit 4f65eeb

File tree

7 files changed

+373
-135
lines changed

7 files changed

+373
-135
lines changed

packages/evm/evmutil/tx.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package evmutil
2+
3+
import (
4+
"slices"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/ethereum/go-ethereum/core/types"
8+
)
9+
10+
func IsFakeTransaction(tx *types.Transaction) bool {
11+
sender, err := GetSender(tx)
12+
13+
// the error will fire when the transaction is invalid. This is most of the time a fake evm tx we use for internal calls, therefore it's fine to assume both.
14+
if slices.Equal(sender.Bytes(), common.Address{}.Bytes()) || err != nil {
15+
return true
16+
}
17+
18+
return false
19+
}

packages/evm/jsonrpc/evmchain.go

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"math"
1111
"math/big"
1212
"path"
13-
"slices"
1413

1514
"github.com/ethereum/go-ethereum"
1615
"github.com/ethereum/go-ethereum/common"
@@ -666,17 +665,6 @@ func (e *EVMChain) iscRequestsInBlock(evmBlockNumber uint64) (*blocklog.BlockInf
666665
return blocklog.GetRequestsInBlock(blocklogStatePartition, iscBlockIndex)
667666
}
668667

669-
func (e *EVMChain) isFakeTransaction(tx *types.Transaction) bool {
670-
sender, err := evmutil.GetSender(tx)
671-
672-
// the error will fire when the transaction is invalid. This is most of the time a fake evm tx we use for internal calls, therefore it's fine to assume both.
673-
if slices.Equal(sender.Bytes(), common.Address{}.Bytes()) || err != nil {
674-
return true
675-
}
676-
677-
return false
678-
}
679-
680668
// traceTransaction allows the tracing of a single EVM transaction.
681669
// "Fake" transactions that are emitted e.g. for L1 deposits return some mocked trace.
682670
func (e *EVMChain) traceTransaction(
@@ -699,12 +687,12 @@ func (e *EVMChain) traceTransaction(
699687
BlockNumber: new(big.Int).SetUint64(blockNumber),
700688
TxIndex: int(txIndex),
701689
TxHash: tx.Hash(),
702-
}, config.TracerConfig)
690+
}, config.TracerConfig, false, nil)
703691
if err != nil {
704692
return nil, err
705693
}
706694

707-
if e.isFakeTransaction(tx) {
695+
if evmutil.IsFakeTransaction(tx) {
708696
return tracer.TraceFakeTx(tx)
709697
}
710698

@@ -729,32 +717,36 @@ func (e *EVMChain) debugTraceBlock(config *tracers.TraceConfig, block *types.Blo
729717
return nil, err
730718
}
731719

732-
blockTxs, err := e.txsByBlockNumber(new(big.Int).SetUint64(block.NumberU64()))
720+
tracerType := "callTracer"
721+
if config.Tracer != nil {
722+
tracerType = *config.Tracer
723+
}
724+
725+
blockNumber := uint64(iscBlock.BlockIndex())
726+
727+
blockTxs := block.Transactions()
728+
729+
tracer, err := newTracer(tracerType, &tracers.Context{
730+
BlockHash: block.Hash(),
731+
BlockNumber: new(big.Int).SetUint64(blockNumber),
732+
}, config.TracerConfig, true, blockTxs)
733733
if err != nil {
734734
return nil, err
735735
}
736736

737-
results := make([]TxTraceResult, 0)
738-
for i, tx := range blockTxs {
739-
result, err := e.traceTransaction(
740-
config,
741-
iscBlock,
742-
iscRequestsInBlock,
743-
tx,
744-
uint64(i),
745-
block.Hash(),
746-
)
747-
748-
// Transactions which failed tracing will be omitted, so the rest of the block can be returned
749-
if err == nil {
750-
results = append(results, TxTraceResult{
751-
TxHash: tx.Hash(),
752-
Result: result,
753-
})
754-
}
737+
err = e.backend.EVMTrace(
738+
iscBlock.PreviousAliasOutput,
739+
iscBlock.Timestamp,
740+
iscRequestsInBlock,
741+
nil,
742+
&blockNumber,
743+
tracer.Tracer,
744+
)
745+
if err != nil {
746+
return nil, err
755747
}
756748

757-
return results, nil
749+
return tracer.GetResult()
758750
}
759751

760752
func (e *EVMChain) TraceTransaction(txHash common.Hash, config *tracers.TraceConfig) (any, error) {

packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go

Lines changed: 127 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
package jsonrpctest
55

66
import (
7+
"bytes"
78
"context"
9+
"crypto/ecdsa"
810
"encoding/hex"
911
"encoding/json"
12+
"fmt"
1013
"math/big"
1114
"slices"
1215
"strings"
@@ -624,20 +627,136 @@ func TestRPCTraceEVMDeposit(t *testing.T) {
624627
require.NoError(t, err)
625628
require.EqualValues(t, types.ReceiptStatusSuccessful, rc.Status)
626629

627-
trace, err := env.traceTransactionWithCallTracer(tx.Hash())
630+
t.Run("callTracer_tx", func(t *testing.T) {
631+
var trace jsonrpc.CallFrame
632+
trace, err = env.traceTransactionWithCallTracer(tx.Hash())
633+
require.NoError(t, err)
634+
require.Equal(t, evmAddr.String(), trace.To.String())
635+
require.Equal(t, hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens*1e12), trace.Value.String())
636+
})
637+
638+
t.Run("prestateTracer_tx", func(t *testing.T) {
639+
var prestate jsonrpc.PrestateAccountMap
640+
prestate, err = env.traceTransactionWithPrestate(tx.Hash())
641+
require.NoError(t, err)
642+
require.Empty(t, prestate)
643+
})
644+
645+
t.Run("prestateTracerDiff_tx", func(t *testing.T) {
646+
var prestateDiff jsonrpc.PrestateDiffResult
647+
prestateDiff, err = env.traceTransactionWithPrestateDiff(tx.Hash())
648+
require.NoError(t, err)
649+
require.Empty(t, prestateDiff.Pre)
650+
require.Empty(t, prestateDiff.Post)
651+
})
652+
653+
t.Run("callTracer_block", func(t *testing.T) {
654+
callTracer := "callTracer"
655+
var res1 json.RawMessage
656+
// we have to use the raw client, because the normal client does not support debug methods
657+
err = env.RawClient.CallContext(
658+
context.Background(),
659+
&res1,
660+
"debug_traceBlockByNumber",
661+
hexutil.Uint64(env.BlockNumber()).String(),
662+
tracers.TraceConfig{Tracer: &callTracer},
663+
)
664+
require.NoError(t, err)
665+
666+
traces := make([]jsonrpc.TxTraceResult, 0)
667+
err = json.Unmarshal(res1, &traces)
668+
require.NoError(t, err)
669+
require.Len(t, traces, 1)
670+
require.Equal(t, tx.Hash(), traces[0].TxHash)
671+
672+
cs := jsonrpc.CallFrame{}
673+
err = json.Unmarshal(traces[0].Result, &cs)
674+
require.NoError(t, err)
675+
require.Equal(t, evmAddr.String(), cs.To.String())
676+
require.Equal(t, hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens*1e12), cs.Value.String())
677+
})
678+
679+
t.Run("prestateTracer_block", func(t *testing.T) {
680+
tracer := "prestateTracer"
681+
var res1 json.RawMessage
682+
// we have to use the raw client, because the normal client does not support debug methods
683+
err = env.RawClient.CallContext(
684+
context.Background(),
685+
&res1,
686+
"debug_traceBlockByNumber",
687+
hexutil.Uint64(env.BlockNumber()).String(),
688+
tracers.TraceConfig{Tracer: &tracer},
689+
)
690+
require.NoError(t, err)
691+
692+
traces := make([]jsonrpc.TxTraceResult, 0)
693+
err = json.Unmarshal(res1, &traces)
694+
require.NoError(t, err)
695+
require.Len(t, traces, 1)
696+
require.Equal(t, tx.Hash(), traces[0].TxHash)
697+
698+
prestate := jsonrpc.PrestateAccountMap{}
699+
err = json.Unmarshal(traces[0].Result, &prestate)
700+
require.NoError(t, err)
701+
require.Empty(t, prestate)
702+
})
703+
}
704+
705+
func addNRequests(n int, env *soloTestEnv, creator *ecdsa.PrivateKey, creatorAddress common.Address, contractABI abi.ABI, contractAddress common.Address) {
706+
rqs := make([]isc.Request, 0, n)
707+
for i := 0; i < n; i++ {
708+
tx1 := types.MustSignNewTx(creator, types.NewEIP155Signer(big.NewInt(int64(env.ChainID))),
709+
&types.LegacyTx{
710+
Nonce: env.NonceAt(creatorAddress) + uint64(i),
711+
To: &contractAddress,
712+
Value: big.NewInt(123),
713+
Gas: 100000,
714+
GasPrice: big.NewInt(10000000000),
715+
Data: lo.Must(contractABI.Pack("sendTo", common.Address{0x1}, big.NewInt(2))),
716+
})
717+
718+
req1 := lo.Must(isc.NewEVMOffLedgerTxRequest(env.soloChain.ChainID, tx1))
719+
rqs = append(rqs, req1)
720+
}
721+
722+
env.soloChain.WaitForRequestsMark()
723+
env.soloChain.Env.AddRequestsToMempool(env.soloChain, rqs)
724+
}
725+
726+
// TestRPCTraceBlockForLargeN requires a large number of requests to be added to the mempool, for that set solo.MaxRequestsInBlock to a large value (>500)
727+
func TestRPCTraceBlockForLargeN(t *testing.T) {
728+
t.Skip("skipping because it requires solo parameters to be set")
729+
730+
n := 400
731+
env := newSoloTestEnv(t)
732+
creator, creatorAddress := env.soloChain.NewEthereumAccountWithL2Funds()
733+
contractABI, err := abi.JSON(strings.NewReader(evmtest.ISCTestContractABI))
628734
require.NoError(t, err)
735+
_, _, contractAddress := env.DeployEVMContract(creator, contractABI, evmtest.ISCTestContractBytecode)
736+
737+
addNRequests(n, env, creator, creatorAddress, contractABI, contractAddress)
629738

630-
require.Equal(t, evmAddr.String(), trace.To.String())
631-
require.Equal(t, hexutil.EncodeUint64(isc.NewAssetsBaseTokens(1000).BaseTokens*1e12), trace.Value.String())
739+
require.True(t, env.soloChain.WaitForRequestsThrough(n, 5*time.Minute))
632740

633-
prestate, err := env.traceTransactionWithPrestate(tx.Hash())
741+
bi := env.soloChain.GetLatestBlockInfo()
742+
require.EqualValues(t, n, bi.NumSuccessfulRequests)
743+
744+
callTracer := "callTracer"
745+
var res1 json.RawMessage
746+
// we have to use the raw client, because the normal client does not support debug methods
747+
err = env.RawClient.CallContext(
748+
context.Background(),
749+
&res1,
750+
"debug_traceBlockByNumber",
751+
hexutil.Uint64(env.BlockNumber()).String(),
752+
tracers.TraceConfig{Tracer: &callTracer},
753+
)
634754
require.NoError(t, err)
635-
require.Empty(t, prestate)
636755

637-
prestateDiff, err := env.traceTransactionWithPrestateDiff(tx.Hash())
756+
var prettyJSON bytes.Buffer
757+
err = json.Indent(&prettyJSON, res1, "", " ")
638758
require.NoError(t, err)
639-
require.Empty(t, prestateDiff.Pre)
640-
require.Empty(t, prestateDiff.Post)
759+
fmt.Println(prettyJSON.String())
641760
}
642761

643762
func TestRPCTraceBlock(t *testing.T) {

packages/evm/jsonrpc/tracer.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,70 @@ import (
66

77
"github.com/ethereum/go-ethereum/core/types"
88
"github.com/ethereum/go-ethereum/eth/tracers"
9+
10+
"github.com/iotaledger/wasp/packages/evm/evmutil"
911
)
1012

1113
type Tracer struct {
1214
*tracers.Tracer
1315
TraceFakeTx func(tx *types.Transaction) (json.RawMessage, error)
1416
}
1517

16-
type tracerFactory func(*tracers.Context, json.RawMessage) (*Tracer, error)
18+
type tracerFactory func(traceCtx *tracers.Context, cfg json.RawMessage, traceBlock bool, initValue any) (*Tracer, error)
1719

1820
var allTracers = map[string]tracerFactory{}
1921

2022
func registerTracer(tracerType string, fn tracerFactory) {
2123
allTracers[tracerType] = fn
2224
}
2325

24-
func newTracer(tracerType string, ctx *tracers.Context, cfg json.RawMessage) (*Tracer, error) {
26+
func newTracer(tracerType string, ctx *tracers.Context, cfg json.RawMessage, traceBlock bool, initValue any) (*Tracer, error) {
2527
fn := allTracers[tracerType]
2628
if fn == nil {
2729
return nil, fmt.Errorf("unsupported tracer type: %s", tracerType)
2830
}
29-
return fn(ctx, cfg)
31+
return fn(ctx, cfg, traceBlock, initValue)
32+
}
33+
34+
func GetTraceResults(
35+
blockTxs []*types.Transaction,
36+
traceBlock bool,
37+
getFakeTxTrace func(tx *types.Transaction) (json.RawMessage, error),
38+
getTxTrace func(tx *types.Transaction) (json.RawMessage, error),
39+
getSingleTxTrace func() (json.RawMessage, error),
40+
reason error,
41+
) (json.RawMessage, error) {
42+
var traceResult []byte
43+
var err error
44+
if traceBlock {
45+
results := make([]TxTraceResult, 0, len(blockTxs))
46+
var jsResult json.RawMessage
47+
for _, tx := range blockTxs {
48+
if evmutil.IsFakeTransaction(tx) {
49+
jsResult, err = getFakeTxTrace(tx)
50+
if err != nil {
51+
return nil, err
52+
}
53+
} else {
54+
jsResult, err = getTxTrace(tx)
55+
if err != nil {
56+
return nil, err
57+
}
58+
}
59+
60+
results = append(results, TxTraceResult{TxHash: tx.Hash(), Result: jsResult})
61+
}
62+
63+
traceResult, err = json.Marshal(results)
64+
if err != nil {
65+
return nil, err
66+
}
67+
} else {
68+
traceResult, err = getSingleTxTrace()
69+
if err != nil {
70+
return nil, err
71+
}
72+
}
73+
74+
return traceResult, reason
3075
}

0 commit comments

Comments
 (0)