Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: use non-threaded traceBlock for native tracers #24283

Merged
merged 5 commits into from Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 51 additions & 7 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 @@ -606,24 +607,68 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
return nil, err
}
defer release()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick, please add a blank line afterwards.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok done

// 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 != "" {
t, err := New(*config.Tracer, nil, nil)
if err != nil {
return nil, err
}
if _, ok := t.(JSTracer); ok {
return api.traceBlockParallel(ctx, block, statedb, config)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole thing is a bit wonky, but I guess we can live with it. The alternative would be that they register with some special attribute, and then you can query IsJs(*config.Tracer) or ParallelTracing(*config.Tracer).

I'm fine with it as is, though

Copy link
Contributor Author

@holiman holiman Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I've seen that you also needed to add that wonky interface just to distinguish the type, I think a less hacky solution would be to change the registration phase, so they register with some attriubute.
Then this whole thing can become less clunky, and also does not have to run the constructor twice

if config != nil && config.Tracer != nil{
		if err, ok := IsInterpreted(config.Tracer); err  != nil {
			return nil, err
		}else if ok{
			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 +690,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
3 changes: 3 additions & 0 deletions eth/tracers/js/goja.go
Expand Up @@ -350,6 +350,9 @@ func (t *jsTracer) Stop(err error) {
t.vm.Interrupt(err)
}

// IsJS returns whether this tracer evaluates JS code.
func (t *jsTracer) IsJS() bool { return true }

// onError is called anytime the running JS code is interrupted
// and returns an error. It in turn pings the EVM to cancel its
// execution.
Expand Down
6 changes: 6 additions & 0 deletions eth/tracers/tracers.go
Expand Up @@ -42,6 +42,12 @@ type Tracer interface {
Stop(err error)
}

// JSTracer is implemented by tracers evaluating JS code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this trick to detect JS tracers. It's not very elegant. I thought about adding a Type() method which is more general, or Name(), or something. But not sure realistically where else this logic would be required.

type JSTracer interface {
Tracer
IsJS() bool
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funky interface.
You can drop the return value, though

}

type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)

var (
Expand Down