Skip to content

Commit

Permalink
eth/tracers: use non-threaded tracechain (ethereum#24283)
Browse files Browse the repository at this point in the history
This makes non-JS tracers execute all block txs on a single goroutine.
In the previous implementation, we used to prepare every tx pre-state
on one goroutine, and then run the transactions again with tracing enabled.
Native tracers are usually faster, so it is faster overall to use their output as
the pre-state for tracing the next transaction.

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
  • Loading branch information
2 people authored and MoonShiesty committed Aug 30, 2023
1 parent e7188ed commit 91ae1dc
Show file tree
Hide file tree
Showing 12 changed files with 121 additions and 129 deletions.
6 changes: 3 additions & 3 deletions core/vm/runtime/runtime_test.go
Expand Up @@ -333,7 +333,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
if len(tracerCode) > 0 {
tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil)
if err != nil {
b.Fatal(err)
}
Expand Down Expand Up @@ -832,7 +832,7 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)

tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -868,7 +868,7 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}

statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
57 changes: 49 additions & 8 deletions eth/tracers/api.go
Expand Up @@ -593,6 +593,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
if block.NumberU64() == 0 {
return nil, errors.New("genesis is not traceable")
}
// Prepare base state
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
if err != nil {
return nil, err
Expand All @@ -607,23 +608,64 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
}
defer release()

// JS tracers have high overhead. In this case run a parallel
// process that generates states in one thread and traces txes
// in separate worker threads.
if config != nil && config.Tracer != nil && *config.Tracer != "" {
if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS {
return api.traceBlockParallel(ctx, block, statedb, config)
}
}
// Native tracers have low overhead
var (
txs = block.Transactions()
blockHash = block.Hash()
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
results = make([]*txTraceResult, len(txs))
)
for i, tx := range txs {
// Generate the next state snapshot fast without tracing
msg, _ := tx.AsMessage(signer, block.BaseFee())
txctx := &Context{
BlockHash: blockHash,
TxIndex: i,
TxHash: tx.Hash(),
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config)
if err != nil {
return nil, err
}
results[i] = &txTraceResult{Result: res}
// Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(is158)
}
return results, nil
}

// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread
// runs along and executes txes without tracing enabled to generate their prestate.
// Worker threads take the tasks and the prestate and trace them.
func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, statedb *state.StateDB, config *TraceConfig) ([]*txTraceResult, error) {
// Execute all the transaction contained within the block concurrently
var (
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
txs = block.Transactions()
results = make([]*txTraceResult, len(txs))
pend sync.WaitGroup
txs = block.Transactions()
blockHash = block.Hash()
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
results = make([]*txTraceResult, len(txs))
pend sync.WaitGroup
)
threads := runtime.NumCPU()
if threads > len(txs) {
threads = len(txs)
}
jobs := make(chan *txTraceTask, threads)
blockHash := block.Hash()
for th := 0; th < threads; th++ {
pend.Add(1)
go func() {
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
defer pend.Done()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
Expand All @@ -645,7 +687,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac

// Feed the transactions into the tracers and return
var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
txloop:
for i, tx := range txs {
// Send the trace task over for execution
Expand Down Expand Up @@ -923,7 +964,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config)
if config.Tracer != nil {
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions eth/tracers/internal/tracetest/calltrace_test.go
Expand Up @@ -140,7 +140,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -243,7 +243,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil)
if err != nil {
b.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -309,7 +309,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
tracer, err := tracers.New("callTracer", nil, nil)
tracer, err := tracers.DefaultDirectory.New("callTracer", nil, nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/internal/tracetest/prestate_test.go
Expand Up @@ -110,7 +110,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down
21 changes: 14 additions & 7 deletions eth/tracers/js/goja.go
Expand Up @@ -45,7 +45,16 @@ func init() {
if err != nil {
panic(err)
}
tracers.RegisterLookup(true, newJsTracer)
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
lookup := func(code string) ctorFn {
return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
return newJsTracer(code, ctx, cfg)
}
}
for name, code := range assetTracers {
tracers.DefaultDirectory.Register(name, lookup(code), true)
}
tracers.DefaultDirectory.RegisterJSEval(newJsTracer)
}

// bigIntProgram is compiled once and the exported function mostly invoked to convert
Expand Down Expand Up @@ -122,16 +131,14 @@ type jsTracer struct {
frameResultValue goja.Value
}

// newJsTracer instantiates a new JS tracer instance. code is either
// the name of a built-in JS tracer or a Javascript snippet which
// evaluates to an expression returning an object with certain methods.
// newJsTracer instantiates a new JS tracer instance. code is a
// Javascript snippet which evaluates to an expression returning
// an object with certain methods:
//
// The methods `result` and `fault` are required to be present.
// The methods `step`, `enter`, and `exit` are optional, but note that
// `enter` and `exit` always go together.
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
if c, ok := assetTracers[code]; ok {
code = c
}
vm := goja.New()
// By default field names are exported to JS as is, i.e. capitalized.
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/native/4byte.go
Expand Up @@ -28,7 +28,7 @@ import (
)

func init() {
register("4byteTracer", newFourByteTracer)
tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false)
}

// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/native/call.go
Expand Up @@ -32,7 +32,7 @@ import (
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go

func init() {
register("callTracer", newCallTracer)
tracers.DefaultDirectory.Register("callTracer", newCallTracer, false)
}

type callLog struct {
Expand Down
4 changes: 2 additions & 2 deletions eth/tracers/native/mux.go
Expand Up @@ -26,7 +26,7 @@ import (
)

func init() {
register("muxTracer", newMuxTracer)
tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
}

// muxTracer is a go implementation of the Tracer interface which
Expand All @@ -47,7 +47,7 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
objects := make([]tracers.Tracer, 0, len(config))
names := make([]string, 0, len(config))
for k, v := range config {
t, err := tracers.New(k, ctx, v)
t, err := tracers.DefaultDirectory.New(k, ctx, v)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/native/noop.go
Expand Up @@ -26,7 +26,7 @@ import (
)

func init() {
register("noopTracer", newNoopTracer)
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
}

// noopTracer is a go implementation of the Tracer interface which
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/native/prestate.go
Expand Up @@ -32,7 +32,7 @@ import (
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go

func init() {
register("prestateTracer", newPrestateTracer)
tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
}

type state = map[common.Address]*account
Expand Down
79 changes: 0 additions & 79 deletions eth/tracers/native/tracer.go

This file was deleted.

0 comments on commit 91ae1dc

Please sign in to comment.