Support debug_traceCall
API
#192
194
pkg/debug/api.go
194
pkg/debug/api.go
@ -1,194 +0,0 @@
|
||||
// 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()
|
||||
}
|
54
pkg/debug/backend.go
Normal file
54
pkg/debug/backend.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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"
|
||||
|
||||
"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/rpc"
|
||||
|
||||
"github.com/cerc-io/ipld-eth-server/v4/pkg/eth"
|
||||
)
|
||||
|
||||
var _ tracers.Backend = &Backend{}
|
||||
|
||||
var (
|
||||
errMethodNotSupported = errors.New("backend method not supported")
|
||||
)
|
||||
|
||||
// Backend implements tracers.Backend interface
|
||||
type Backend struct {
|
||||
eth.Backend
|
||||
}
|
||||
|
||||
// StateAtBlock retrieves the state database associated with a certain block
|
||||
func (b *Backend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) {
|
||||
rpcBlockNumber := rpc.BlockNumber(block.NumberU64())
|
||||
statedb, _, err := b.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(rpcBlockNumber))
|
||||
return statedb, err
|
||||
}
|
||||
|
||||
// StateAtTransaction returns the execution environment of a certain transaction
|
||||
func (b *Backend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) {
|
||||
return nil, vm.BlockContext{}, nil, errMethodNotSupported
|
||||
}
|
@ -258,6 +258,11 @@ func (b *Backend) GetTd(blockHash common.Hash) (*big.Int, error) {
|
||||
return td, nil
|
||||
}
|
||||
|
||||
// ChainConfig returns the active chain configuration.
|
||||
func (b *Backend) ChainConfig() *params.ChainConfig {
|
||||
return b.Config.ChainConfig
|
||||
}
|
||||
|
||||
// CurrentBlock returns the current block
|
||||
func (b *Backend) CurrentBlock() (*types.Block, error) {
|
||||
block, err := b.BlockByNumber(context.Background(), rpc.LatestBlockNumber)
|
||||
@ -849,8 +854,8 @@ func (b *Backend) ValidateTrie(stateRoot common.Hash) error {
|
||||
}
|
||||
|
||||
// RPCGasCap returns the configured gas cap for the rpc server
|
||||
func (b *Backend) RPCGasCap() *big.Int {
|
||||
return b.Config.RPCGasCap
|
||||
func (b *Backend) RPCGasCap() uint64 {
|
||||
return b.Config.RPCGasCap.Uint64()
|
||||
}
|
||||
|
||||
func (b *Backend) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription {
|
||||
|
@ -838,7 +838,7 @@ func (b *Block) Call(ctx context.Context, args struct {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
result, err := eth.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, 5*time.Second, b.backend.RPCGasCap().Uint64())
|
||||
result, err := eth.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, 5*time.Second, b.backend.RPCGasCap())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cerc-io/ipld-eth-server/v4/pkg/prom"
|
||||
)
|
||||
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
ethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
@ -139,22 +140,24 @@ 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 {
|
||||
log.Fatalf("unable to create public eth api: %v", err)
|
||||
}
|
||||
return append(apis, rpc.API{
|
||||
Namespace: eth.APIName,
|
||||
Version: eth.APIVersion,
|
||||
Service: ethAPI,
|
||||
Public: true,
|
||||
})
|
||||
|
||||
debugTracerAPI := tracers.APIs(&debug.Backend{Backend: *sap.backend})[0]
|
||||
|
||||
return append(apis,
|
||||
rpc.API{
|
||||
Namespace: eth.APIName,
|
||||
Version: eth.APIVersion,
|
||||
Service: ethAPI,
|
||||
Public: true,
|
||||
},
|
||||
debugTracerAPI,
|
||||
)
|
||||
}
|
||||
|
||||
// Serve listens for incoming converter data off the screenAndServePayload from the Sync process
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
sdtypes "github.com/ethereum/go-ethereum/statediff/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
integration "github.com/cerc-io/ipld-eth-server/v4/test"
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user