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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/tracing: register live tracer APIs #30308

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3a9b93e
core: bc.logger is duplicate with bc.vmconfig.tracer, use function in…
jsvisa Aug 10, 2024
770ac7e
core: set logger after inited
jsvisa Aug 10, 2024
5c88fb1
cmd: set tracing logger after blockchain inited
jsvisa Aug 10, 2024
a2360d6
eth: set live tracer after inited
jsvisa Aug 10, 2024
d75c962
cmd,eth: register live tracer with Backend interface
jsvisa Aug 10, 2024
f76f25f
fix test
jsvisa Aug 12, 2024
4881b88
eth/tracers: live tracer return apis
jsvisa Aug 12, 2024
ecc2a37
cmd: register live tracer apis
jsvisa Aug 12, 2024
e819b13
Revert "cmd: register live tracer apis"
jsvisa Aug 22, 2024
efe8364
Revert "eth/tracers: live tracer return apis"
jsvisa Aug 22, 2024
560f2dc
Revert "fix test"
jsvisa Aug 22, 2024
61ac985
Revert "cmd,eth: register live tracer with Backend interface"
jsvisa Aug 22, 2024
0daacd3
Revert "eth: set live tracer after inited"
jsvisa Aug 22, 2024
7eec558
Revert "cmd: set tracing logger after blockchain inited"
jsvisa Aug 22, 2024
0f9fe5f
Revert "core: set logger after inited"
jsvisa Aug 22, 2024
efbdfbb
eth,cmd: set tracer's backend after eth initalized
jsvisa Aug 22, 2024
561c43d
eth/tracers: pass stack into live tracer
jsvisa Sep 25, 2024
9ae1444
pass eth.Backend into live tracer
jsvisa Sep 25, 2024
2f621cb
rm tracing.Backend
jsvisa Sep 30, 2024
2cdead6
Allow adding console extensions
s1na Oct 1, 2024
5be4b07
Merge branch 'lazy-init-tracer' of github.com:jsvisa/go-ethereum into…
s1na Oct 1, 2024
8a92d5a
resolve merge conflict
s1na Oct 2, 2024
b54e788
move live constructor to core/tracing
s1na Oct 2, 2024
3738a2f
update changelog
s1na Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2190,7 +2190,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh
if ctx.IsSet(VMTraceJsonConfigFlag.Name) {
config = json.RawMessage(ctx.String(VMTraceJsonConfigFlag.Name))
}
t, err := tracers.LiveDirectory.New(name, config)
t, err := tracers.LiveDirectory.New(name, config, stack, nil)
if err != nil {
Fatalf("Failed to create tracer %q: %v", name, err)
}
Expand Down
2 changes: 1 addition & 1 deletion console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (c *Console) initExtensions() error {
continue
}
aliases[api] = struct{}{}
if file, ok := web3ext.Modules[api]; ok {
if file := web3ext.DefaultModules.Get(api); file != "" {
if err = c.jsre.Compile(api+".js", file); err != nil {
return fmt.Errorf("%s.js: %v", api, err)
}
Expand Down
55 changes: 31 additions & 24 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ type BlockChain struct {
prefetcher Prefetcher
processor Processor // Block transaction processor interface
vmConfig vm.Config
logger *tracing.Hooks
}

// NewBlockChain returns a fully initialised block chain using information
Expand Down Expand Up @@ -301,7 +300,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
txLookupCache: lru.NewCache[common.Hash, txLookup](txLookupCacheLimit),
engine: engine,
vmConfig: vmConfig,
logger: vmConfig.Tracer,
}
var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
Expand Down Expand Up @@ -411,10 +409,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// it in advance.
bc.engine.VerifyHeader(bc, bc.CurrentHeader())

if bc.logger != nil && bc.logger.OnBlockchainInit != nil {
bc.logger.OnBlockchainInit(chainConfig)
logger := bc.logger()
if logger != nil && logger.OnBlockchainInit != nil {
logger.OnBlockchainInit(chainConfig)
}
if bc.logger != nil && bc.logger.OnGenesisBlock != nil {
if logger != nil && logger.OnGenesisBlock != nil {
if block := bc.CurrentBlock(); block.Number.Uint64() == 0 {
alloc, err := getGenesisState(bc.db, block.Hash())
if err != nil {
Expand All @@ -423,7 +422,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
if alloc == nil {
return nil, errors.New("live blockchain tracer requires genesis alloc to be set")
}
bc.logger.OnGenesisBlock(bc.genesisBlock, alloc)
logger.OnGenesisBlock(bc.genesisBlock, alloc)
}
}

Expand Down Expand Up @@ -1156,8 +1155,8 @@ func (bc *BlockChain) Stop() {
}
}
// Allow tracers to clean-up and release resources.
if bc.logger != nil && bc.logger.OnClose != nil {
bc.logger.OnClose()
if logger := bc.logger(); logger != nil && logger.OnClose != nil {
logger.OnClose()
}
// Close the trie database, release all the held resources as the last step.
if err := bc.triedb.Close(); err != nil {
Expand Down Expand Up @@ -1714,6 +1713,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
// Track the singleton witness from this chain insertion (if any)
var witness *stateless.Witness

bclogger := bc.logger()
for ; block != nil && err == nil || errors.Is(err, ErrKnownBlock); block, err = it.next() {
// If the chain is terminating, stop processing blocks
if bc.insertStopped() {
Expand Down Expand Up @@ -1753,8 +1753,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
return nil, it.index, err
}
stats.processed++
if bc.logger != nil && bc.logger.OnSkippedBlock != nil {
bc.logger.OnSkippedBlock(tracing.BlockEvent{
if bclogger != nil && bclogger.OnSkippedBlock != nil {
bclogger.OnSkippedBlock(tracing.BlockEvent{
Block: block,
TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1),
Finalized: bc.CurrentFinalBlock(),
Expand All @@ -1776,7 +1776,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool, makeWitness
if err != nil {
return nil, it.index, err
}
statedb.SetLogger(bc.logger)
statedb.SetLogger(bclogger)

// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
Expand Down Expand Up @@ -1881,19 +1881,21 @@ type blockProcessingResult struct {
// processBlock executes and validates the given block. If there was no error
// it writes the block and associated state to database.
func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) {
if bc.logger != nil && bc.logger.OnBlockStart != nil {
td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
bc.logger.OnBlockStart(tracing.BlockEvent{
Block: block,
TD: td,
Finalized: bc.CurrentFinalBlock(),
Safe: bc.CurrentSafeBlock(),
})
}
if bc.logger != nil && bc.logger.OnBlockEnd != nil {
defer func() {
bc.logger.OnBlockEnd(blockEndErr)
}()
if logger := bc.logger(); logger != nil {
if logger.OnBlockStart != nil {
td := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
logger.OnBlockStart(tracing.BlockEvent{
Block: block,
TD: td,
Finalized: bc.CurrentFinalBlock(),
Safe: bc.CurrentSafeBlock(),
})
}
if logger.OnBlockEnd != nil {
defer func() {
logger.OnBlockEnd(blockEndErr)
}()
}
}

// Process block using the parent state as reference point
Expand Down Expand Up @@ -2537,3 +2539,8 @@ func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) {
func (bc *BlockChain) GetTrieFlushInterval() time.Duration {
return time.Duration(bc.flushInterval.Load())
}

// logger returns the tracing logger
func (bc *BlockChain) logger() *tracing.Hooks {
return bc.vmConfig.Tracer
}
27 changes: 24 additions & 3 deletions core/tracing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ All notable changes to the tracing interface will be documented in this file.

## [Unreleased]

This release gives live tracers the ability to register JSON-RPC APIs in existing or new namespaces.

### Modified methods

- The signature for constructing a live tracer has changed from `func(config json.RawMessage) (*Hooks, error)` to `func(config json.RawMessage, node tracing.Node, backend tracing.Backend) (*Hooks, error)`. The signature of the constructor for live tracers has been updated was previously not documented as part of the tracing interface. We have decided to include it now as changes to the constructor can break tracers.

### New types

- `tracing.Node`: A new interface which allows live tracers to register APIs.
- `tracing.Backend`: A new interface allowing the tracers to query the database for chain info, i.e. blocks, headers, and transactions.

## [v1.14.10]

### Modified types

- `OpContext`: Added `ContractCode()` method to retrieve the code of the current contract being executed. This change affects the `OpContext` interface which is an argument to the `OpcodeHook` and `FaultHook` hooks.

## [v1.14.9]

### Modified types

- `GasChangeReason` has been extended with the following reasons which will be enabled only post-Verkle. There shouldn't be any gas changes with those reasons prior to the fork.
Expand Down Expand Up @@ -93,7 +112,9 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.

[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.8...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.10...master
[v1.14.10]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.10
[v1.14.9]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.9
[v1.14.4]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.4
[v1.14.3]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.3
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0
22 changes: 22 additions & 0 deletions core/tracing/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package tracing

import (
"context"
"encoding/json"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
)

Expand Down Expand Up @@ -68,7 +71,26 @@ type BlockEvent struct {
Safe *types.Header
}

// Backend interface provides the common API services (that are provided by
// both full and light clients) with access to necessary functions.
type Backend interface {
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
GetTransaction(ctx context.Context, txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64, error)
Copy link
Contributor

@fjl fjl Oct 7, 2024

Choose a reason for hiding this comment

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

This GetTransaction method is not great for several reasons. First, it requires us to have an index of txhash -> tx, which is strictly optional. The index we have is not complete, we only keep a limited horizon worth of blocks in it. The indexing is also async with block execution and so it may not lead to a repeatable tracing result.

Another problem is the amount of return values. A signature with this number of return values implies there may be more values returned in the future. If we wanted to change this, it would break the interface. So we need to either define a new struct which is to be returned by this method, or break it up into multiple methods.

I also wonder why a tracer would need to query transactions by hash. I think it's a niche use case and we might be better off not allowing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I also wonder why a tracer would need to query transactions by hash.

I think a tracer will almost never need this in and out of itself. Only in the case where the tracer is exposing an API. Which makes me think if live tracers exposing an API is a good abstraction. It is easy to abuse the live tracing interface to expose an API that doesn't need tracing hooks at all. I wonder if we should introduce plugins that can also register live tracers and/or APIs instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wonder if we should introduce plugins that can also register live tracers and/or APIs instead.

agree with it, plugins will be more flexible and useful for different use cases.

}

// Node is the interface providing access to node-level
// infra such as APIs and DBs.
type Node interface {
RegisterAPIs(apis []rpc.API)
}

type (
// LiveConstructor is the constructor for a live tracer.
LiveConstructor = func(config json.RawMessage, stack Node, backend Backend) (*Hooks, error)

/*
- VM events -
*/
Expand Down
13 changes: 8 additions & 5 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
}
}

eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
if eth.APIBackend.allowUnprotectedTxs {
log.Info("Unprotected transactions allowed")
}

var (
vmConfig = vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording,
Expand All @@ -201,7 +207,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if config.VMTraceJsonConfig != "" {
traceConfig = json.RawMessage(config.VMTraceJsonConfig)
}
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig)
t, err := tracers.LiveDirectory.New(config.VMTrace, traceConfig, stack, eth.APIBackend)
if err != nil {
return nil, fmt.Errorf("failed to create tracer %s: %v", config.VMTrace, err)
}
Expand Down Expand Up @@ -254,10 +260,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.miner = miner.New(eth, config.Miner, eth.engine)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))

eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
if eth.APIBackend.allowUnprotectedTxs {
log.Info("Unprotected transactions allowed")
}
// Start the gas price oracle after blockchain is fully loaded
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, config.GPO, config.Miner.GasPrice)

// Start the RPC service
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/internal/tracetest/supply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ func testSupplyTracer(t *testing.T, genesis *core.Genesis, gen func(*core.BlockG
traceOutputFilename := path.Join(traceOutputPath, "supply.jsonl")

// Load supply tracer
tracer, err := tracers.LiveDirectory.New("supply", json.RawMessage(fmt.Sprintf(`{"path":"%s"}`, traceOutputPath)))
tracer, err := tracers.LiveDirectory.New("supply", json.RawMessage(fmt.Sprintf(`{"path":"%s"}`, traceOutputPath)), nil, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create call tracer: %v", err)
}
Expand Down
12 changes: 5 additions & 7 deletions eth/tracers/live.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,23 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
)

type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error)

// LiveDirectory is the collection of tracers which can be used
// during normal block import operations.
var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)}
var LiveDirectory = liveDirectory{elems: make(map[string]tracing.LiveConstructor)}

type liveDirectory struct {
elems map[string]ctorFunc
elems map[string]tracing.LiveConstructor
}

// Register registers a tracer constructor by name.
func (d *liveDirectory) Register(name string, f ctorFunc) {
func (d *liveDirectory) Register(name string, f tracing.LiveConstructor) {
d.elems[name] = f
}

// New instantiates a tracer by name.
func (d *liveDirectory) New(name string, config json.RawMessage) (*tracing.Hooks, error) {
func (d *liveDirectory) New(name string, config json.RawMessage, stack tracing.Node, backend tracing.Backend) (*tracing.Hooks, error) {
if f, ok := d.elems[name]; ok {
return f(config)
return f(config, stack, backend)
}
return nil, errors.New("not found")
}
2 changes: 1 addition & 1 deletion eth/tracers/live/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func init() {
// as soon as we have a real live tracer.
type noop struct{}

func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) {
func newNoopTracer(_ json.RawMessage, _ tracing.Node, _ tracing.Backend) (*tracing.Hooks, error) {
t := &noop{}
return &tracing.Hooks{
OnTxStart: t.OnTxStart,
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/live/supply.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type supplyTracerConfig struct {
MaxSize int `json:"maxSize"` // MaxSize is the maximum size in megabytes of the tracer log file before it gets rotated. It defaults to 100 megabytes.
}

func newSupply(cfg json.RawMessage) (*tracing.Hooks, error) {
func newSupply(cfg json.RawMessage, _ tracing.Node, _ tracing.Backend) (*tracing.Hooks, error) {
var config supplyTracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
Expand Down
45 changes: 34 additions & 11 deletions internal/web3ext/web3ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,40 @@
// Package web3ext contains geth specific web3.js extensions.
package web3ext

var Modules = map[string]string{
"admin": AdminJs,
"clique": CliqueJs,
"debug": DebugJs,
"eth": EthJs,
"miner": MinerJs,
"net": NetJs,
"personal": PersonalJs,
"rpc": RpcJs,
"txpool": TxpoolJs,
"dev": DevJs,
import "fmt"

type ModuleDirectory struct {
modules map[string]string
}

var DefaultModules = &ModuleDirectory{
modules: map[string]string{
"admin": AdminJs,
"clique": CliqueJs,
"debug": DebugJs,
"eth": EthJs,
"miner": MinerJs,
"net": NetJs,
"personal": PersonalJs,
"rpc": RpcJs,
"txpool": TxpoolJs,
"dev": DevJs,
},
}

func (d *ModuleDirectory) Add(name, code string) {
if code == "" {
return
}
prev := ""
if c, ok := d.modules[name]; ok {
prev = fmt.Sprintf("%s\n", c)
}
d.modules[name] = fmt.Sprintf("%s%s", prev, code)
}

func (d *ModuleDirectory) Get(name string) string {
return d.modules[name]
}

const CliqueJs = `
Expand Down
3 changes: 3 additions & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/web3ext"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -386,6 +387,8 @@ func (n *Node) startRPC() error {
}
}
apis = append(apis, api)
// Register console extension.
web3ext.DefaultModules.Add(api.Namespace, api.ConsoleExtension)
}
if err := n.startInProc(apis); err != nil {
return err
Expand Down
Loading