Skip to content

Commit

Permalink
[R4R] added debug_traceCall (#940)
Browse files Browse the repository at this point in the history
* added debug_traceCall

* review on api_tracer

* resolved comments

---------

Co-authored-by: HaoyangLiu <[email protected]>
  • Loading branch information
libevm and HaoyangLiu authored Jun 16, 2023
1 parent 36ff1dc commit 3bd5a3a
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 4 deletions.
143 changes: 143 additions & 0 deletions l2geth/eth/api_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"errors"
"fmt"
"io/ioutil"
"math"
"math/big"
"os"
"runtime"
"sync"
Expand All @@ -40,6 +42,7 @@ import (
"github.com/mantlenetworkio/mantle/l2geth/log"
"github.com/mantlenetworkio/mantle/l2geth/params"
"github.com/mantlenetworkio/mantle/l2geth/rlp"
"github.com/mantlenetworkio/mantle/l2geth/rollup/rcfg"
"github.com/mantlenetworkio/mantle/l2geth/rpc"
"github.com/mantlenetworkio/mantle/l2geth/trie"
)
Expand All @@ -63,6 +66,12 @@ type TraceConfig struct {
Reexec *uint64
}

// TraceCallConfig is the config for traceCall API
type TraceCallConfig struct {
TraceConfig
StateOverrides *map[common.Address]ethapi.Account
}

// StdTraceConfig holds extra parameters to standard-json trace functions.
type StdTraceConfig struct {
vm.LogConfig
Expand Down Expand Up @@ -518,6 +527,140 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
return results, nil
}

// 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 *PrivateDebugAPI) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
statedb, header, err := api.eth.APIBackend.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)

if statedb == nil && err != nil {
return nil, fmt.Errorf("Error retrieving state: %s", err)
} else if statedb == nil && err == nil {
return nil, fmt.Errorf("Error retrieving state, no error code returned")
}

// Set sender address or use a default if none specified
var addr common.Address
if args.From == nil {
if !rcfg.UsingBVM {
if wallets := api.eth.AccountManager().Wallets(); len(wallets) > 0 {
if accounts := wallets[0].Accounts(); len(accounts) > 0 {
addr = accounts[0].Address
}
}
}
} else {
addr = *args.From
}

// Override the fields of specified contracts before execution.
if config != nil {
for addr, account := range *config.StateOverrides {
// Override account nonce.
if account.Nonce != nil {
statedb.SetNonce(addr, uint64(*account.Nonce))
}
// Override account(contract) code.
if account.Code != nil {
statedb.SetCode(addr, *account.Code)
}
// Override account balance.
if account.Balance != nil {
statedb.SetBalance(addr, (*big.Int)(*account.Balance))
}
if account.State != nil && account.StateDiff != nil {
return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
}
// Replace entire state if caller requires.
if account.State != nil {
statedb.SetStorage(addr, *account.State)
}
// Apply state diff into specified accounts.
if account.StateDiff != nil {
for key, value := range *account.StateDiff {
statedb.SetState(addr, key, value)
}
}
}

// https://github.com/ethereum/go-ethereum/blob/281e8cd5abaac86ed3f37f98250ff147b3c9fe62/internal/ethapi/api.go#L939
statedb.Finalise(false)
}

// Set default gas & gas price if none were set
gas := uint64(math.MaxUint64 / 2)
if args.Gas != nil {
gas = uint64(*args.Gas)
}

// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
// chain doesn't have miners, we just return a gas price of 1 for any call unless specified differently
gasPrice := big.NewInt(1)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}

value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}

var data []byte
if args.Data != nil {
data = *args.Data
}

// Currently, the blocknumber and timestamp actually refer to the L1BlockNumber and L1Timestamp
// attached to each transaction. We need to modify the blocknumber and timestamp to reflect this,
// or else the result of `eth_call` will not be correct.
blockNumber := header.Number
timestamp := header.Time
if rcfg.UsingBVM {
block := api.eth.blockchain.GetBlockByNumber(header.Number.Uint64())
if block == nil {
return nil, fmt.Errorf("Error retrieving block")
}

txs := block.Transactions()
if header.Number.Uint64() != 0 {
if len(txs) != 1 {
return nil, fmt.Errorf("block %d has more than 1 transaction", header.Number.Uint64())
}
tx := txs[0]
blockNumber = tx.L1BlockNumber()
timestamp = tx.L1Timestamp()
}
}

// Create new call message
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false, blockNumber, timestamp, types.QueueOriginSequencer)

// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
timeout := defaultTraceTimeout
if config != nil {
if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
}
ctx, cancel = context.WithTimeout(ctx, timeout)

// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()

vmctx := core.NewEVMContext(msg, header, api.eth.blockchain, nil)

var traceConfig *TraceConfig
if config != nil {
traceConfig = &config.TraceConfig
}
return api.traceTx(ctx, msg, vmctx, statedb, traceConfig)
}

// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
// and traces either a full block or an individual transaction. The return value will
// be one filename per transaction traced.
Expand Down
8 changes: 4 additions & 4 deletions l2geth/internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,15 +822,15 @@ type CallArgs struct {
// set, message execution will only use the data in the given state. Otherwise
// if statDiff is set, all diff will be applied first and then execute the call
// message.
type account struct {
type Account struct {
Nonce *hexutil.Uint64 `json:"nonce"`
Code *hexutil.Bytes `json:"code"`
Balance **hexutil.Big `json:"balance"`
State *map[common.Hash]common.Hash `json:"state"`
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
}

func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg *vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]Account, vmCfg *vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
Expand Down Expand Up @@ -972,8 +972,8 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
//
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
var accounts map[common.Address]account
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]Account) (hexutil.Bytes, error) {
var accounts map[common.Address]Account
if overrides != nil {
accounts = *overrides
}
Expand Down

0 comments on commit 3bd5a3a

Please sign in to comment.