Implement debug_traceCall API

This commit is contained in:
Prathamesh Musale 2022-09-14 15:00:45 +05:30
parent 4d99ecdee3
commit 811ab17f7a
3 changed files with 201 additions and 1 deletions

View File

@ -133,7 +133,7 @@ func startServers(server s.Server, settings *s.Config) error {
if settings.HTTPEnabled {
logWithCommand.Info("starting up HTTP server")
_, err := srpc.StartHTTPEndpoint(settings.HTTPEndpoint, server.APIs(), []string{"vdb", "eth", "net"}, nil, []string{"*"}, rpc.HTTPTimeouts{})
_, err := srpc.StartHTTPEndpoint(settings.HTTPEndpoint, server.APIs(), []string{"vdb", "eth", "debug", "net"}, nil, []string{"*"}, rpc.HTTPTimeouts{})
if err != nil {
return err
}

194
pkg/debug/api.go Normal file
View File

@ -0,0 +1,194 @@
// VulcanizeDB
// Copyright © 2022 Vulcanize
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package debug
import (
"context"
"errors"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cerc-io/ipld-eth-server/v4/pkg/eth"
)
const (
// defaultTraceTimeout is the amount of time a single transaction can execute
// by default before being forcefully aborted.
defaultTraceTimeout = 30 * time.Second
)
// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
tracers.TraceConfig
StateOverrides *eth.StateOverride
}
// APIName is the namespace for the watcher's debug api
const APIName = "debug"
// APIVersion is the version of the watcher's debug api
const APIVersion = "0.0.1"
type DebugAPI struct {
// Local db backend
B *eth.Backend
}
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
func NewDebugAPI(backend *eth.Backend) *DebugAPI {
return &DebugAPI{B: backend}
}
// blockByNumber is the wrapper of the chain access function offered by the backend.
// It will return an error if the block is not found.
func (api *DebugAPI) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) {
block, err := api.B.BlockByNumber(ctx, number)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("block #%d not found", number)
}
return block, nil
}
// blockByHash is the wrapper of the chain access function offered by the backend.
// It will return an error if the block is not found.
func (api *DebugAPI) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
block, err := api.B.BlockByHash(ctx, hash)
if err != nil {
return nil, err
}
if block == nil {
return nil, fmt.Errorf("block %s not found", hash.Hex())
}
return block, nil
}
// TODO: use API implementation from geth directly
// TraceCall lets you trace a given eth_call. It collects the structured logs
// created during the execution of EVM if the given transaction was added on
// top of the provided block and returns them as a JSON object.
func (api *DebugAPI) TraceCall(ctx context.Context, args eth.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
// Try to retrieve the specified block
var (
err error
block *types.Block
)
if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
} else if number, ok := blockNrOrHash.Number(); ok {
if number == rpc.PendingBlockNumber {
// We don't have access to the miner here. For tracing 'future' transactions,
// it can be done with block- and state-overrides instead, which offers
// more flexibility and stability than trying to trace on 'pending', since
// the contents of 'pending' is unstable and probably not a true representation
// of what the next actual block is likely to contain.
return nil, errors.New("tracing on top of pending is not supported")
}
block, err = api.blockByNumber(ctx, number)
} else {
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
if err != nil {
return nil, err
}
statedb, _, err := api.B.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, err
}
vmctx := core.NewEVMBlockContext(block.Header(), api.B, nil)
// Apply the customization rules if required.
if config != nil {
if err := config.StateOverrides.Apply(statedb); err != nil {
return nil, err
}
}
// Execute the trace
msg, err := args.ToMessage(api.B.Config.RPCGasCap.Uint64(), block.BaseFee())
if err != nil {
return nil, err
}
var traceConfig *tracers.TraceConfig
if config != nil {
traceConfig = &tracers.TraceConfig{
Config: config.Config,
Tracer: config.Tracer,
Timeout: config.Timeout,
Reexec: config.Reexec,
}
}
return api.traceTx(ctx, msg, new(tracers.Context), vmctx, statedb, traceConfig)
}
// traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *tracers.Context, vmctx vm.BlockContext, statedb *state.StateDB, config *tracers.TraceConfig) (interface{}, error) {
var (
tracer tracers.Tracer
err error
timeout = defaultTraceTimeout
txContext = core.NewEVMTxContext(message)
)
if config == nil {
config = &tracers.TraceConfig{}
}
// Default tracer is the struct logger
tracer = logger.NewStructLogger(config.Config)
if config.Tracer != nil {
tracer, err = tracers.New(*config.Tracer, txctx, config.TracerConfig)
if err != nil {
return nil, err
}
}
// Define a meaningful timeout of a single transaction trace
if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
<-deadlineCtx.Done()
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
tracer.Stop(errors.New("execution timeout"))
}
}()
defer cancel()
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.B.Config.ChainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
// Call Prepare to clear out the statedb access list
statedb.Prepare(txctx.TxHash, txctx.TxIndex)
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
return tracer.GetResult()
}

View File

@ -31,6 +31,7 @@ import (
"github.com/jmoiron/sqlx"
log "github.com/sirupsen/logrus"
"github.com/cerc-io/ipld-eth-server/v4/pkg/debug"
"github.com/cerc-io/ipld-eth-server/v4/pkg/eth"
"github.com/cerc-io/ipld-eth-server/v4/pkg/net"
)
@ -138,6 +139,11 @@ func (sap *Service) APIs() []rpc.API {
Service: net.NewPublicNetAPI(networkID, sap.client),
Public: true,
},
{
Namespace: debug.APIName,
Version: debug.APIVersion,
Service: debug.NewDebugAPI(sap.backend),
},
}
ethAPI, err := eth.NewPublicEthAPI(sap.backend, sap.client, sap.supportsStateDiffing, sap.forwardEthCalls, sap.proxyOnError)
if err != nil {