Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
m-Peter committed Sep 19, 2024
1 parent c83ed0d commit 97312b8
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 34 deletions.
229 changes: 208 additions & 21 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,29 +67,85 @@ func (d *DebugAPI) TraceTransaction(
hash gethCommon.Hash,
_ *tracers.TraceConfig,
) (json.RawMessage, error) {
res, err := d.tracer.GetTransaction(hash)
tx, err := d.transactions.Get(hash)
if err != nil {
return handleError[json.RawMessage](err, d.logger, d.collector)
}

receipt, err := d.receipts.GetByTransactionID(tx.Hash())
if err != nil {
return handleError[json.RawMessage](err, d.logger, d.collector)
}

block, err := d.blocks.GetByHeight(receipt.BlockNumber.Uint64())
if err != nil {
return handleError[json.RawMessage](err, d.logger, d.collector)
}

// 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
if receipt.TransactionIndex == 0 {
registerHeight -= 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)
}

_, err = state.Execute(tx)
if err != nil {
return handleError[json.RawMessage](err, d.logger, d.collector)
}
return res, nil

return tracer.GetResult()
}

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 handleError[[]*txTraceResult](err, d.logger, d.collector)
}

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()}
}
results[i] = &txTraceResult{TxHash: h, Result: txTrace}
}

return results, nil
Expand All @@ -68,19 +155,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 handleError[[]*txTraceResult](err, d.logger, d.collector)
}

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()}
}
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")
}
}()

registers := pebble.NewRegister(d.store, height, 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)
}

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
7 changes: 5 additions & 2 deletions services/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
flowGo "github.com/onflow/flow-go/model/flow"
"github.com/onflow/go-ethereum/common"
gethTypes "github.com/onflow/go-ethereum/core/types"
"github.com/onflow/go-ethereum/eth/tracers"
"github.com/rs/zerolog"

"github.com/onflow/flow-evm-gateway/models"
Expand All @@ -32,6 +33,7 @@ type BlockState struct {
blocks storage.BlockIndexer
receipts storage.ReceiptIndexer
logger zerolog.Logger
tracer *tracers.Tracer
}

func NewBlockState(
Expand All @@ -41,6 +43,7 @@ func NewBlockState(
blocks storage.BlockIndexer,
receipts storage.ReceiptIndexer,
logger zerolog.Logger,
tracer *tracers.Tracer,
) (*BlockState, error) {
logger = logger.With().Str("component", "state-execution").Logger()
storageAddress := evm.StorageAccountAddress(chainID)
Expand All @@ -58,6 +61,7 @@ func NewBlockState(
blocks: blocks,
receipts: receipts,
logger: logger,
tracer: tracer,
}, nil
}

Expand Down Expand Up @@ -172,8 +176,7 @@ func (s *BlockState) blockContext(receipt *models.Receipt) (types.BlockContext,
Random: s.block.PrevRandao,
TxCountSoFar: s.txIndex,
TotalGasUsedSoFar: s.gasUsed,
// todo what to do with the tracer
Tracer: nil,
Tracer: s.tracer,
}

// only add precompile cadence arch mocks if we have a receipt,
Expand Down
4 changes: 4 additions & 0 deletions tests/e2e_web3js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func TestWeb3_E2E(t *testing.T) {
runWeb3Test(t, "cadence_arch_env_test")
})

t.Run("test transaction traces", func(t *testing.T) {
runWeb3Test(t, "debug_traces_test")
})

t.Run("test setup sanity check", func(t *testing.T) {
runWeb3Test(t, "setup_test")
})
Expand Down
Loading

0 comments on commit 97312b8

Please sign in to comment.