eth/tracers/live: rename filter to live

Signed-off-by: jsvisa <delweng@gmail.com>
This commit is contained in:
jsvisa 2024-09-27 13:57:07 +00:00
parent 5bb32c5a6f
commit f4114d4047
7 changed files with 138 additions and 194 deletions

View file

@ -26,7 +26,7 @@ import (
) )
func init() { func init() {
tracers.LiveDirectory.Register("filter", newFilter) tracers.LiveDirectory.Register("live", newLive)
} }
const ( const (
@ -57,7 +57,7 @@ func (tr *traceResult) DecodeRLP(s *rlp.Stream) error {
return json.Unmarshal(temp.Result, &tr.Result) return json.Unmarshal(temp.Result, &tr.Result)
} }
type filter struct { type live struct {
backend tracing.Backend backend tracing.Backend
kvdb ethdb.Database kvdb ethdb.Database
frdb *rawdb.Freezer frdb *rawdb.Freezer
@ -74,7 +74,7 @@ type filter struct {
offsetFile string offsetFile string
} }
type filterTracerConfig struct { type liveTracerConfig struct {
Path string `json:"path"` // Path to the directory where the tracer logs will be stored Path string `json:"path"` // Path to the directory where the tracer logs will be stored
Config json.RawMessage `json:"config"` Config json.RawMessage `json:"config"`
} }
@ -111,15 +111,15 @@ func toKVKey(name string, number uint64, hash common.Hash) []byte {
return key return key
} }
func newFilter(cfg json.RawMessage, stack tracers.LiveApiRegister, backend tracing.Backend) (*tracing.Hooks, error) { func newLive(cfg json.RawMessage, stack tracers.LiveApiRegister, backend tracing.Backend) (*tracing.Hooks, error) {
var config filterTracerConfig var config liveTracerConfig
if cfg != nil { if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil { if err := json.Unmarshal(cfg, &config); err != nil {
return nil, fmt.Errorf("failed to parse config: %v", err) return nil, fmt.Errorf("failed to parse config: %v", err)
} }
} }
if config.Path == "" { if config.Path == "" {
return nil, errors.New("filter tracer output path is required") return nil, errors.New("live tracer output path is required")
} }
t, err := native.NewMuxTracer(config.Config) t, err := native.NewMuxTracer(config.Config)
@ -159,7 +159,7 @@ func newFilter(cfg json.RawMessage, stack tracers.LiveApiRegister, backend traci
return nil, fmt.Errorf("failed to read the frozen block numbers from the freezer db: %v", err) return nil, fmt.Errorf("failed to read the frozen block numbers from the freezer db: %v", err)
} }
f := &filter{ l := &live{
backend: backend, backend: backend,
kvdb: kvdb, kvdb: kvdb,
frdb: frdb, frdb: frdb,
@ -171,8 +171,8 @@ func newFilter(cfg json.RawMessage, stack tracers.LiveApiRegister, backend traci
offsetFile: path.Join(frpath, "OFFSET"), offsetFile: path.Join(frpath, "OFFSET"),
} }
offset := 0 offset := 0
if _, err := os.Stat(f.offsetFile); err == nil || os.IsExist(err) { if _, err := os.Stat(l.offsetFile); err == nil || os.IsExist(err) {
data, err := os.ReadFile(f.offsetFile) data, err := os.ReadFile(l.offsetFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read the offset from the freezer db: %v", err) return nil, fmt.Errorf("failed to read the offset from the freezer db: %v", err)
} }
@ -181,15 +181,15 @@ func newFilter(cfg json.RawMessage, stack tracers.LiveApiRegister, backend traci
return nil, fmt.Errorf("failed to convert offset: %v", err) return nil, fmt.Errorf("failed to convert offset: %v", err)
} }
} }
log.Info("Open filter tracer", "path", config.Path, "offset", offset, "tables", tables) log.Info("Open live tracer", "path", config.Path, "offset", offset, "tables", tables)
f.latest.Store(tail + frozen + uint64(offset)) l.latest.Store(tail + frozen + uint64(offset))
f.offset.Store(uint64(offset)) l.offset.Store(uint64(offset))
hooks := &tracing.Hooks{ hooks := &tracing.Hooks{
OnBlockStart: f.OnBlockStart, OnBlockStart: l.OnBlockStart,
OnBlockEnd: f.OnBlockEnd, OnBlockEnd: l.OnBlockEnd,
OnTxStart: f.OnTxStart, OnTxStart: l.OnTxStart,
OnTxEnd: f.OnTxEnd, OnTxEnd: l.OnTxEnd,
// reuse the mux's hooks // reuse the mux's hooks
OnEnter: t.OnEnter, OnEnter: t.OnEnter,
@ -206,17 +206,21 @@ func newFilter(cfg json.RawMessage, stack tracers.LiveApiRegister, backend traci
apis := []rpc.API{ apis := []rpc.API{
{ {
Namespace: "trace", Namespace: "trace",
Service: &filterAPI{backend: backend, filter: f}, Service: &traceAPI{backend: backend, live: l},
},
{
Namespace: "eth",
Service: &ethAPI{backend: backend, live: l},
}, },
} }
stack.RegisterAPIs(apis) stack.RegisterAPIs(apis)
go f.freeze() go l.freeze()
return hooks, nil return hooks, nil
} }
func (f *filter) OnBlockStart(ev tracing.BlockEvent) { func (f *live) OnBlockStart(ev tracing.BlockEvent) {
// track the latest block number // track the latest block number
blknum := ev.Block.NumberU64() blknum := ev.Block.NumberU64()
f.latest.Store(blknum) f.latest.Store(blknum)
@ -240,11 +244,16 @@ func (f *filter) OnBlockStart(ev tracing.BlockEvent) {
}) })
} }
func (f *filter) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { func (f *live) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
key := append(from.Bytes(), encodeNumber(tx.Nonce())...)
val := tx.Hash().Bytes()
if err := f.kvdb.Put(key, val); err != nil {
log.Warn("Failed to put nonce into kvdb", "err", err)
}
f.tracer.OnTxStart(env, tx, from) f.tracer.OnTxStart(env, tx, from)
} }
func (f *filter) OnTxEnd(receipt *types.Receipt, err error) { func (f *live) OnTxEnd(receipt *types.Receipt, err error) {
f.tracer.OnTxEnd(receipt, err) f.tracer.OnTxEnd(receipt, err)
for name, tt := range f.tracer.Tracers() { for name, tt := range f.tracer.Tracers() {
@ -260,7 +269,7 @@ func (f *filter) OnTxEnd(receipt *types.Receipt, err error) {
} }
} }
func (f *filter) OnBlockEnd(err error) { func (f *live) OnBlockEnd(err error) {
if err != nil { if err != nil {
log.Warn("OnBlockEnd", "latest", f.latest.Load(), "error", err) log.Warn("OnBlockEnd", "latest", f.latest.Load(), "error", err)
} }
@ -289,7 +298,7 @@ func (f *filter) OnBlockEnd(err error) {
} }
} }
func (f *filter) readBlockTraces(ctx context.Context, name string, blknum uint64) ([]*traceResult, error) { func (f *live) readBlockTraces(ctx context.Context, name string, blknum uint64) ([]*traceResult, error) {
if blknum > f.latest.Load() { if blknum > f.latest.Load() {
return nil, errors.New("notfound") return nil, errors.New("notfound")
} }
@ -320,7 +329,7 @@ func (f *filter) readBlockTraces(ctx context.Context, name string, blknum uint64
return traces, err return traces, err
} }
func (f *filter) readFromKVDB(ctx context.Context, name string, blknum uint64) ([]byte, error) { func (f *live) readFromKVDB(ctx context.Context, name string, blknum uint64) ([]byte, error) {
header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(blknum)) header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(blknum))
if err != nil { if err != nil {
return nil, err return nil, err
@ -334,7 +343,7 @@ func (f *filter) readFromKVDB(ctx context.Context, name string, blknum uint64) (
return data, err return data, err
} }
func (f *filter) readFromFRDB(name string, blknum uint64) ([]byte, error) { func (f *live) readFromFRDB(name string, blknum uint64) ([]byte, error) {
table := toTraceTable(name) table := toTraceTable(name)
var data []byte var data []byte
err := f.frdb.ReadAncients(func(reader ethdb.AncientReaderOp) error { err := f.frdb.ReadAncients(func(reader ethdb.AncientReaderOp) error {
@ -348,7 +357,7 @@ func (f *filter) readFromFRDB(name string, blknum uint64) ([]byte, error) {
return data, nil return data, nil
} }
func (f *filter) Close() { func (f *live) Close() {
close(f.stopCh) close(f.stopCh)
if err := f.kvdb.Close(); err != nil { if err := f.kvdb.Close(); err != nil {

View file

@ -0,0 +1,40 @@
package live
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/internal/ethapi"
)
type ethAPI struct {
backend tracing.Backend
live *live
}
func (n *ethAPI) GetTransactionBySenderAndNonce(ctx context.Context, sender common.Address, nonce hexutil.Uint) (*ethapi.RPCTransaction, error) {
// TODO:
// 1. return nil if sender is a contract
// 2. check with txpool first
txHash, err := n.live.kvdb.Get(append(sender.Bytes(), encodeNumber(uint64(nonce))...))
if err != nil {
return nil, nil
}
found, tx, blockHash, blockNumber, index, err := n.backend.GetTransaction(ctx, common.BytesToHash(txHash))
if err != nil {
return nil, err
}
if !found {
return nil, errors.New("transaction not found")
}
header, err := n.backend.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return ethapi.NewRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, n.backend.ChainConfig()), nil
}

View file

@ -14,9 +14,9 @@ import (
var errTxNotFound = errors.New("transaction not found") var errTxNotFound = errors.New("transaction not found")
type filterAPI struct { type traceAPI struct {
backend tracing.Backend backend tracing.Backend
filter *filter live *live
} }
type traceConfig struct { type traceConfig struct {
@ -27,12 +27,12 @@ var defaultTraceConfig = &traceConfig{
Tracer: "callTracer", Tracer: "callTracer",
} }
func (api *filterAPI) isSupportedTracer(tracer string) bool { func (api *traceAPI) isSupportedTracer(tracer string) bool {
_, ok := api.filter.tracer.Tracers()[tracer] _, ok := api.live.tracer.Tracers()[tracer]
return ok return ok
} }
func (api *filterAPI) Block(ctx context.Context, blockNr rpc.BlockNumber, cfg *traceConfig) ([]interface{}, error) { func (api *traceAPI) Block(ctx context.Context, blockNr rpc.BlockNumber, cfg *traceConfig) ([]interface{}, error) {
tracer, err := api.getTracerOrDefault(cfg) tracer, err := api.getTracerOrDefault(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -40,14 +40,14 @@ func (api *filterAPI) Block(ctx context.Context, blockNr rpc.BlockNumber, cfg *t
blknum := uint64(blockNr.Int64()) blknum := uint64(blockNr.Int64())
if blockNr == rpc.LatestBlockNumber { if blockNr == rpc.LatestBlockNumber {
blknum = api.filter.latest.Load() blknum = api.live.latest.Load()
} }
return api.readBlockTraces(ctx, tracer, blknum, tracer == "parityTracer") return api.readBlockTraces(ctx, tracer, blknum, tracer == "parityTracer")
} }
func (api *filterAPI) readBlockTraces(ctx context.Context, tracer string, blknum uint64, isParity bool) ([]interface{}, error) { func (api *traceAPI) readBlockTraces(ctx context.Context, tracer string, blknum uint64, isParity bool) ([]interface{}, error) {
traces, err := api.filter.readBlockTraces(ctx, tracer, blknum) traces, err := api.live.readBlockTraces(ctx, tracer, blknum)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -85,7 +85,7 @@ func (api *filterAPI) readBlockTraces(ctx context.Context, tracer string, blknum
return results, nil return results, nil
} }
func (api *filterAPI) Transaction(ctx context.Context, hash common.Hash, cfg *traceConfig) (interface{}, error) { func (api *traceAPI) Transaction(ctx context.Context, hash common.Hash, cfg *traceConfig) (interface{}, error) {
tracer, err := api.getTracerOrDefault(cfg) tracer, err := api.getTracerOrDefault(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,7 +98,7 @@ func (api *filterAPI) Transaction(ctx context.Context, hash common.Hash, cfg *tr
if !found { if !found {
return nil, errTxNotFound return nil, errTxNotFound
} }
traces, err := api.filter.readBlockTraces(ctx, tracer, blknum) traces, err := api.live.readBlockTraces(ctx, tracer, blknum)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -132,7 +132,7 @@ const (
) )
// Filter returns traces for the given filter configuration. // Filter returns traces for the given filter configuration.
func (api *filterAPI) Filter(ctx context.Context, req traceFilterConfig, cfg *traceConfig) (interface{}, error) { func (api *traceAPI) Filter(ctx context.Context, req traceFilterConfig, cfg *traceConfig) (interface{}, error) {
tracer, err := api.getTracerOrDefault(cfg) tracer, err := api.getTracerOrDefault(cfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -158,7 +158,7 @@ func (api *filterAPI) Filter(ctx context.Context, req traceFilterConfig, cfg *tr
if req.ToBlock != nil { if req.ToBlock != nil {
toBlock = uint64(*req.ToBlock) toBlock = uint64(*req.ToBlock)
} else { } else {
toBlock = api.filter.latest.Load() toBlock = api.live.latest.Load()
} }
if fromBlock > toBlock { if fromBlock > toBlock {
@ -175,7 +175,7 @@ func (api *filterAPI) Filter(ctx context.Context, req traceFilterConfig, cfg *tr
return exportLimitedTraces(func(blknum uint64) ([]interface{}, error) { return api.readBlockTraces(ctx, tracer, blknum, isParity) }, fromBlock, toBlock, count, after) return exportLimitedTraces(func(blknum uint64) ([]interface{}, error) { return api.readBlockTraces(ctx, tracer, blknum, isParity) }, fromBlock, toBlock, count, after)
} }
func (api *filterAPI) getTracerOrDefault(cfg *traceConfig) (string, error) { func (api *traceAPI) getTracerOrDefault(cfg *traceConfig) (string, error) {
if cfg == nil { if cfg == nil {
return defaultTraceConfig.Tracer, nil return defaultTraceConfig.Tracer, nil
} }
@ -186,45 +186,3 @@ func (api *filterAPI) getTracerOrDefault(cfg *traceConfig) (string, error) {
} }
return tracer, nil return tracer, nil
} }
func extractAddres(addrs []*common.Address) map[common.Address]struct{} {
result := make(map[common.Address]struct{}, len(addrs))
for _, addr := range addrs {
if addr != nil {
result[*addr] = struct{}{}
}
}
return result
}
func exportLimitedTraces(gen func(blknum uint64) ([]interface{}, error), fromBlock, toBlock, count, after uint64) ([]interface{}, error) {
var (
nExported uint64 // Number of traces exported
nSkipped uint64 // Number of traces skipped
results = make([]interface{}, 0, 1024) // 1024 is the initial capacity
)
for blknum := fromBlock; blknum <= toBlock && nExported < count; blknum++ {
traces, err := gen(uint64(blknum))
if err != nil {
return nil, err
}
nTraces := uint64(len(traces))
if after > nSkipped {
skip := min(after-nSkipped, nTraces)
nSkipped += skip
if skip == nTraces {
// Skip if the whole block is skipped
continue
}
traces = traces[skip:]
}
// Export at most the remaining traces
maxExport := min(count-nExported, uint64(len(traces)))
results = append(results, traces[:maxExport]...)
nExported += maxExport
}
return results, nil
}

View file

@ -15,7 +15,7 @@ const (
kvdbTailKey = "FilterFreezerTail" kvdbTailKey = "FilterFreezerTail"
) )
func (f *filter) freeze() { func (f *live) freeze() {
var lastFinalized uint64 var lastFinalized uint64
for { for {
select { select {
@ -52,7 +52,7 @@ func (f *filter) freeze() {
} }
} }
func (f *filter) getFreezerTail() (tail uint64) { func (f *live) getFreezerTail() (tail uint64) {
tailBytes, _ := f.kvdb.Get([]byte(kvdbTailKey)) tailBytes, _ := f.kvdb.Get([]byte(kvdbTailKey))
if len(tailBytes) > 0 { if len(tailBytes) > 0 {
@ -64,13 +64,13 @@ func (f *filter) getFreezerTail() (tail uint64) {
return return
} }
func (f *filter) updateFreezerTail(tail uint64) error { func (f *live) updateFreezerTail(tail uint64) error {
tailBytes := make([]byte, 8) tailBytes := make([]byte, 8)
binary.BigEndian.PutUint64(tailBytes, tail) binary.BigEndian.PutUint64(tailBytes, tail)
return f.kvdb.Put([]byte(kvdbTailKey), tailBytes) return f.kvdb.Put([]byte(kvdbTailKey), tailBytes)
} }
func (f *filter) moveBlockToFreezer(blknum uint64) error { func (f *live) moveBlockToFreezer(blknum uint64) error {
header, err := f.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(blknum)) header, err := f.backend.HeaderByNumber(context.Background(), rpc.BlockNumber(blknum))
if err != nil { if err != nil {
return err return err
@ -107,7 +107,7 @@ func (f *filter) moveBlockToFreezer(blknum uint64) error {
return nil return nil
} }
func (f *filter) deleteKVDBEntriesWithPrefix(blknum uint64) error { func (f *live) deleteKVDBEntriesWithPrefix(blknum uint64) error {
prefix := encodeNumber(blknum) prefix := encodeNumber(blknum)
batch := f.kvdb.NewBatch() batch := f.kvdb.NewBatch()
it := f.kvdb.NewIterator(prefix, nil) it := f.kvdb.NewIterator(prefix, nil)

View file

@ -1,108 +0,0 @@
package live
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
)
func init() {
tracers.LiveDirectory.Register("nonce", newNonce)
}
type nonce struct {
backend tracing.Backend
kvdb ethdb.Database
latest atomic.Uint64
}
type nonceTracerConfig struct {
Path string `json:"path"` // Path to the directory where the tracer logs will be stored
}
func newNonce(cfg json.RawMessage, stack tracers.LiveApiRegister, backend tracing.Backend) (*tracing.Hooks, error) {
var config nonceTracerConfig
if cfg != nil {
if err := json.Unmarshal(cfg, &config); err != nil {
return nil, fmt.Errorf("failed to parse config: %v", err)
}
}
if config.Path == "" {
return nil, errors.New("nonce tracer output path is required")
}
kvdb, err := rawdb.NewPebbleDBDatabase(config.Path, 128, 1024, "nonce", false, false)
if err != nil {
return nil, err
}
n := &nonce{
backend: backend,
kvdb: kvdb,
}
log.Info("Open nonce tracer", "path", config.Path)
apis := []rpc.API{{Namespace: "eth", Service: n}}
stack.RegisterAPIs(apis)
return &tracing.Hooks{
OnBlockStart: n.onBlockStart,
OnTxStart: n.onTxStart,
}, nil
}
func (n *nonce) onBlockStart(ev tracing.BlockEvent) {
blknum := ev.Block.NumberU64()
n.latest.Store(blknum)
}
func (n *nonce) onTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) {
key := append(from.Bytes(), encodeNumber(tx.Nonce())...)
val := tx.Hash().Bytes()
if err := n.kvdb.Put(key, val); err != nil {
log.Warn("Failed to put nonce kvdb", "err", err)
}
}
func (n *nonce) GetTransactionBySenderAndNonce(ctx context.Context, sender common.Address, nonce hexutil.Uint) (*ethapi.RPCTransaction, error) {
// TODO:
// 1. return nil if sender is a contract
// 2. check with txpool first
txHash, err := n.kvdb.Get(append(sender.Bytes(), encodeNumber(uint64(nonce))...))
if err != nil {
return nil, nil
}
found, tx, blockHash, blockNumber, index, err := n.backend.GetTransaction(ctx, common.BytesToHash(txHash))
if err != nil {
return nil, err
}
if !found {
return nil, errors.New("transaction not found")
}
header, err := n.backend.HeaderByHash(ctx, blockHash)
if err != nil {
return nil, err
}
return ethapi.NewRPCTransaction(tx, blockHash, blockNumber, header.Time, index, header.BaseFee, n.backend.ChainConfig()), nil
}
func (n *nonce) Close() {
if err := n.kvdb.Close(); err != nil {
log.Error("Close kvdb failed", "err", err)
}
}

45
eth/tracers/live/utils.go Normal file
View file

@ -0,0 +1,45 @@
package live
import "github.com/ethereum/go-ethereum/common"
func extractAddres(addrs []*common.Address) map[common.Address]struct{} {
result := make(map[common.Address]struct{}, len(addrs))
for _, addr := range addrs {
if addr != nil {
result[*addr] = struct{}{}
}
}
return result
}
func exportLimitedTraces(gen func(blknum uint64) ([]interface{}, error), fromBlock, toBlock, count, after uint64) ([]interface{}, error) {
var (
nExported uint64 // Number of traces exported
nSkipped uint64 // Number of traces skipped
results = make([]interface{}, 0, 1024) // 1024 is the initial capacity
)
for blknum := fromBlock; blknum <= toBlock && nExported < count; blknum++ {
traces, err := gen(uint64(blknum))
if err != nil {
return nil, err
}
nTraces := uint64(len(traces))
if after > nSkipped {
skip := min(after-nSkipped, nTraces)
nSkipped += skip
if skip == nTraces {
// Skip if the whole block is skipped
continue
}
traces = traces[skip:]
}
// Export at most the remaining traces
maxExport := min(count-nExported, uint64(len(traces)))
results = append(results, traces[:maxExport]...)
nExported += maxExport
}
return results, nil
}