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

Traces api use local state index #555

Draft
wants to merge 1 commit into
base: feature/state
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
247 changes: 225 additions & 22 deletions api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,61 @@ package api

import (
"context"
"fmt"

"github.com/goccy/go-json"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/eth/tracers"
"github.com/onflow/go-ethereum/rpc"
"github.com/rs/zerolog"

"github.com/onflow/flow-evm-gateway/config"
"github.com/onflow/flow-evm-gateway/metrics"
errs "github.com/onflow/flow-evm-gateway/models/errors"
"github.com/onflow/flow-evm-gateway/services/state"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-evm-gateway/storage/pebble"
)

const (
tracerConfig = `{ "onlyTopCall": true }`
tracerName = "callTracer"
)

// txTraceResult is the result of a single transaction trace.
type txTraceResult struct {
TxHash gethCommon.Hash `json:"txHash"` // transaction hash
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
}

type DebugAPI struct {
logger zerolog.Logger
tracer storage.TraceIndexer
blocks storage.BlockIndexer
collector metrics.Collector
logger zerolog.Logger
blocks storage.BlockIndexer
transactions storage.TransactionIndexer
receipts storage.ReceiptIndexer
collector metrics.Collector
store *pebble.Storage
config *config.Config
}

func NewDebugAPI(tracer storage.TraceIndexer, blocks storage.BlockIndexer, logger zerolog.Logger, collector metrics.Collector) *DebugAPI {
func NewDebugAPI(
blocks storage.BlockIndexer,
transactions storage.TransactionIndexer,
receipts storage.ReceiptIndexer,
store *pebble.Storage,
config *config.Config,
logger zerolog.Logger,
collector metrics.Collector,
) *DebugAPI {
return &DebugAPI{
logger: logger,
tracer: tracer,
blocks: blocks,
collector: collector,
logger: logger,
blocks: blocks,
transactions: transactions,
receipts: receipts,
collector: collector,
store: store,
config: config,
}
}

Expand All @@ -36,28 +67,100 @@ func (d *DebugAPI) TraceTransaction(
hash gethCommon.Hash,
_ *tracers.TraceConfig,
) (json.RawMessage, error) {
res, err := d.tracer.GetTransaction(hash)
receipt, err := d.receipts.GetByTransactionID(hash)
if err != nil {
return nil, err
}

block, err := d.blocks.GetByHeight(receipt.BlockNumber.Uint64())
if err != nil {
return nil, err
}

// start a new database batch
batch := d.store.NewBatch()
defer func() {
if err := batch.Close(); err != nil {
d.logger.Warn().Err(err).Msg("failed to close batch")
}
}()

tracerConfig := json.RawMessage(tracerConfig)
tracerCtx := &tracers.Context{
BlockHash: receipt.BlockHash,
BlockNumber: receipt.BlockNumber,
TxIndex: int(receipt.TransactionIndex),
TxHash: receipt.TxHash,
}
tracer, err := tracers.DefaultDirectory.New(tracerName, tracerCtx, tracerConfig)
if err != nil {
return nil, err
}

registerHeight := block.Height - 1

registers := pebble.NewRegister(d.store, registerHeight, batch)
state, err := state.NewBlockState(
block,
registers,
d.config.FlowNetworkID,
d.blocks,
d.receipts,
d.logger,
tracer,
)
if err != nil {
return handleError[json.RawMessage](err, d.logger, d.collector)
return nil, err
}
return res, nil

executed := false
var txTracer *tracers.Tracer
for _, h := range block.TransactionHashes {
if executed {
break
}

tx, err := d.transactions.Get(h)
if err != nil {
return nil, err
}

if h == hash {
txTracer = tracer
}

_, err = state.ExecuteWithTracer(tx, txTracer)
if err != nil {
return nil, err
}

if h == hash {
executed = true
}
}

result, err := tracer.GetResult()

return result, err
}

func (d *DebugAPI) TraceBlockByNumber(
ctx context.Context,
number rpc.BlockNumber,
_ *tracers.TraceConfig,
) ([]json.RawMessage, error) {
) ([]*txTraceResult, error) {
block, err := d.blocks.GetByHeight(uint64(number.Int64()))
if err != nil {
return handleError[[]json.RawMessage](err, d.logger, d.collector)
return nil, err
}

results := make([]json.RawMessage, len(block.TransactionHashes))
results := make([]*txTraceResult, len(block.TransactionHashes))
for i, h := range block.TransactionHashes {
results[i], err = d.TraceTransaction(ctx, h, nil)
txTrace, err := d.TraceTransaction(ctx, h, nil)
if err != nil {
return nil, err
results[i] = &txTraceResult{TxHash: h, Error: err.Error()}
} else {
results[i] = &txTraceResult{TxHash: h, Result: txTrace}
}
}

Expand All @@ -68,19 +171,119 @@ func (d *DebugAPI) TraceBlockByHash(
ctx context.Context,
hash gethCommon.Hash,
_ *tracers.TraceConfig,
) ([]json.RawMessage, error) {
) ([]*txTraceResult, error) {
block, err := d.blocks.GetByID(hash)
if err != nil {
return handleError[[]json.RawMessage](err, d.logger, d.collector)
return nil, err
}

results := make([]json.RawMessage, len(block.TransactionHashes))
results := make([]*txTraceResult, len(block.TransactionHashes))
for i, h := range block.TransactionHashes {
results[i], err = d.TraceTransaction(ctx, h, nil)
txTrace, err := d.TraceTransaction(ctx, h, nil)
if err != nil {
return nil, err
results[i] = &txTraceResult{TxHash: h, Error: err.Error()}
} else {
results[i] = &txTraceResult{TxHash: h, Result: txTrace}
}
}

return results, nil
}

func (d *DebugAPI) TraceCall(
ctx context.Context,
args TransactionArgs,
blockNrOrHash rpc.BlockNumberOrHash,
config *tracers.TraceCallConfig,
) (interface{}, error) {
tx, err := encodeTxFromArgs(args)
if err != nil {
return handleError[interface{}](err, d.logger, d.collector)
}

// Default address in case user does not provide one
from := d.config.Coinbase
if args.From != nil {
from = *args.From
}

height, err := d.resolveBlockNumberOrHash(&blockNrOrHash)
if err != nil {
return nil, err
}

// start a new database batch
batch := d.store.NewBatch()
defer func() {
if err := batch.Close(); err != nil {
d.logger.Warn().Err(err).Msg("failed to close batch")
}
}()

tracerConfig := json.RawMessage(tracerConfig)
tracer, err := tracers.DefaultDirectory.New(tracerName, &tracers.Context{}, tracerConfig)
if err != nil {
return nil, err
}

block, err := d.blocks.GetByHeight(height)
if err != nil {
return handleError[interface{}](err, d.logger, d.collector)
}

registers := pebble.NewRegister(d.store, height, batch)
state, err := state.NewBlockState(
block,
registers,
d.config.FlowNetworkID,
d.blocks,
d.receipts,
d.logger,
tracer,
)
if err != nil {
return handleError[interface{}](err, d.logger, d.collector)
}

_, err = state.Call(from, tx)
if err != nil {
return handleError[interface{}](err, d.logger, d.collector)
}

return tracer.GetResult()
}

func (d *DebugAPI) resolveBlockNumberOrHash(block *rpc.BlockNumberOrHash) (uint64, error) {
err := fmt.Errorf("%w: neither block number nor hash specified", errs.ErrInvalid)
if block == nil {
return 0, err
}
if number, ok := block.Number(); ok {
return d.resolveBlockNumber(number)
}

if hash, ok := block.Hash(); ok {
evmHeight, err := d.blocks.GetHeightByID(hash)
if err != nil {
return 0, err
}
return evmHeight, nil
}

return 0, err
}

func (d *DebugAPI) resolveBlockNumber(number rpc.BlockNumber) (uint64, error) {
height := number.Int64()

// if special values (latest) we return latest executed height
if height < 0 {
executed, err := d.blocks.LatestExecutedHeight()
if err != nil {
return 0, err
}
height = int64(executed)
}

return uint64(height), nil
}
13 changes: 9 additions & 4 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,15 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
ratelimiter,
)

var debugAPI *api.DebugAPI
if b.config.TracesEnabled {
debugAPI = api.NewDebugAPI(b.Storages.Traces, b.Storages.Blocks, b.logger, b.collector)
}
debugAPI := api.NewDebugAPI(
b.Storages.Blocks,
b.Storages.Transactions,
b.Storages.Receipts,
b.Storages.Storage,
b.config,
b.logger,
b.collector,
)

var walletAPI *api.WalletAPI
if b.config.WalletEnabled {
Expand Down
1 change: 1 addition & 0 deletions services/requester/client_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func (c *ClientHandler) localClient(height uint64) (*LocalClient, error) {
c.blocks,
c.receipts,
c.logger,
nil,
)
if err != nil {
return nil, err
Expand Down
10 changes: 9 additions & 1 deletion services/state/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,15 @@ func (e *Engine) executeBlock(block *models.Block) error {
registers = NewRegisterValidator(registers, e.execution)
}

state, err := NewBlockState(block, registers, e.config.FlowNetworkID, e.blocks, e.receipts, e.logger)
state, err := NewBlockState(
block,
registers,
e.config.FlowNetworkID,
e.blocks,
e.receipts,
e.logger,
nil,
)
if err != nil {
return err
}
Expand Down
Loading