Skip to content

Commit

Permalink
add debug_traceCall rpc (#1437)
Browse files Browse the repository at this point in the history
* add debug_traceCall rpc

* fix compile

* fix cycle deps
  • Loading branch information
laizy authored Dec 15, 2023
1 parent 037df5b commit c0189e3
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 71 deletions.
11 changes: 10 additions & 1 deletion core/store/ledgerstore/ledger_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* along with The ontology. If not, see <http://www.gnu.org/licenses/>.
*/
//Storage of ledger

package ledgerstore

import (
Expand Down Expand Up @@ -1447,7 +1448,15 @@ func (this *LedgerStoreImp) PreExecuteContract(tx *types.Transaction) (*sstate.P
return this.PreExecuteContractWithParam(tx, param)
}

func (this *LedgerStoreImp) TraceEip155Tx(msg types3.Message, tracer evm2.Tracer) (*types5.ExecutionResult, error) {
return this.executeEip155Tx(msg, evm2.Config{Debug: true, Tracer: tracer})
}

func (this *LedgerStoreImp) PreExecuteEip155Tx(msg types3.Message) (*types5.ExecutionResult, error) {
return this.executeEip155Tx(msg, evm2.Config{})
}

func (this *LedgerStoreImp) executeEip155Tx(msg types3.Message, conf evm2.Config) (*types5.ExecutionResult, error) {
height := this.GetCurrentBlockHeight()
// use previous block time to make it predictable for easy test
blockTime := uint32(time.Now().Unix())
Expand All @@ -1466,7 +1475,7 @@ func (this *LedgerStoreImp) PreExecuteEip155Tx(msg types3.Message) (*types5.Exec
blockContext := evm.NewEVMBlockContext(height, blockTime, this)
cache := this.GetCacheDB()
statedb := storage.NewStateDB(cache, common2.Hash{}, common2.Hash(ctx.BlockHash), ong.OngBalanceHandle{})
vmenv := evm2.NewEVM(blockContext, txContext, statedb, config, evm2.Config{})
vmenv := evm2.NewEVM(blockContext, txContext, statedb, config, conf)
res, err := evm.ApplyMessage(vmenv, msg, common2.Address(utils.GovernanceContractAddress))
return res, err
}
Expand Down
2 changes: 2 additions & 0 deletions core/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
types3 "github.com/ontio/ontology/smartcontract/service/evm/types"
cstates "github.com/ontio/ontology/smartcontract/states"
"github.com/ontio/ontology/smartcontract/storage"
"github.com/ontio/ontology/vm/evm"
)

type ExecuteResult struct {
Expand Down Expand Up @@ -78,6 +79,7 @@ type LedgerStore interface {
PreExecuteContract(tx *types.Transaction) (*cstates.PreExecResult, error)
PreExecuteContractBatch(txes []*types.Transaction, atomic bool) ([]*cstates.PreExecResult, uint32, error)
PreExecuteEip155Tx(msg types2.Message) (*types3.ExecutionResult, error)
TraceEip155Tx(msg types2.Message, tracer evm.Tracer) (*types3.ExecutionResult, error)
GetEventNotifyByTx(tx common.Uint256) (*event.ExecuteNotify, error)
GetEventNotifyByBlock(height uint32) ([]*event.ExecuteNotify, error)
GetEthCode(hash common2.Hash) ([]byte, error)
Expand Down
129 changes: 129 additions & 0 deletions http/ethrpc/debug/tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 The ontology Authors
* This file is part of The ontology library.
*
* The ontology is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ontology 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with The ontology. If not, see <http://www.gnu.org/licenses/>.
*/

package debug

import (
"fmt"

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ontio/ontology/core/ledger"
"github.com/ontio/ontology/http/ethrpc/eth"
types2 "github.com/ontio/ontology/http/ethrpc/types"
"github.com/ontio/ontology/vm/evm"
"github.com/ontio/ontology/vm/evm/tracers"
)

// DebugAPI is the collection of tracing APIs exposed over the private debugging endpoint.
type DebugAPI struct {
}

// NewDebugAPI creates a new DebugAPI definition for the tracing methods of the Ethereum service.
func NewDebugAPI() *DebugAPI {
return &DebugAPI{}
}

// TraceConfig holds extra parameters to trace functions.
type TraceConfig struct {
*evm.LogConfig
Tracer *string
Timeout *string
Reexec *uint64
}

// TraceCallConfig is the config for traceCall DebugAPI. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
*evm.LogConfig
Tracer *string
Timeout *string
Reexec *uint64
//StateOverrides *ethapi.StateOverride
}

// 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.
// You can provide -2 as a block number to trace on top of the pending block.
func (api *DebugAPI) TraceCall(args types2.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
// Execute the trace
msg := args.AsMessage(eth.RPCGasCap)

var traceConfig *TraceConfig
if config != nil {
traceConfig = &TraceConfig{
LogConfig: config.LogConfig,
Tracer: config.Tracer,
Timeout: config.Timeout,
Reexec: config.Reexec,
}
}
return api.traceTx(msg, 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(message types.Message, config *TraceConfig) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
tracer evm.Tracer
err error
)
switch {
case config == nil:
tracer = evm.NewStructLogger(nil)
case config.Tracer != nil:
switch *config.Tracer {
case "callTracer":
tracer = tracers.NewCallTracer()
default:
return nil, fmt.Errorf("unkown tracer type: %s", *config.Tracer)
}
default:
tracer = evm.NewStructLogger(config.LogConfig)
}

result, err := ledger.DefLedger.TraceEip155Tx(message, nil)
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}

// Depending on the tracer type, format and return the output.
switch tracer := tracer.(type) {
case *evm.StructLogger:
// If the result contains a revert reason, return it.
returnVal := fmt.Sprintf("%x", result.Return())
if len(result.Revert()) > 0 {
returnVal = fmt.Sprintf("%x", result.Revert())
}
return &ExecutionResult{
Gas: result.UsedGas,
Failed: result.Failed(),
ReturnValue: returnVal,
StructLogs: FormatLogs(tracer.StructLogs()),
}, nil

case *tracers.CallTracer:
return tracer.GetResult()

default:
panic(fmt.Sprintf("bad tracer type %T", tracer))
}
}
87 changes: 87 additions & 0 deletions http/ethrpc/debug/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2018 The ontology Authors
* This file is part of The ontology library.
*
* The ontology is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ontology 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with The ontology. If not, see <http://www.gnu.org/licenses/>.
*/

package debug

import (
"fmt"

"github.com/ethereum/go-ethereum/common/math"
"github.com/ontio/ontology/vm/evm"
)

// ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}

// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}

// FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []evm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32))
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}
4 changes: 4 additions & 0 deletions http/ethrpc/rpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/ontio/ontology/core/store/ledgerstore"
"github.com/ontio/ontology/http/base/actor"
backend2 "github.com/ontio/ontology/http/ethrpc/backend"
"github.com/ontio/ontology/http/ethrpc/debug"
"github.com/ontio/ontology/http/ethrpc/eth"
filters2 "github.com/ontio/ontology/http/ethrpc/filters"
"github.com/ontio/ontology/http/ethrpc/net"
Expand Down Expand Up @@ -68,6 +69,9 @@ func StartEthServer(txpool *tp.TXPoolServer) error {
if err := server.RegisterName("web3", web3.NewAPI()); err != nil {
return err
}
if err := server.RegisterName("debug", debug.NewDebugAPI()); err != nil {
return err
}

// add cors wrapper
wrappedCORSHandler := node.NewHTTPHandlerStack(server, cors, vhosts)
Expand Down
6 changes: 4 additions & 2 deletions vm/evm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/holiman/uint256"
common2 "github.com/ontio/ontology/common"
"github.com/ontio/ontology/core/types"
"github.com/ontio/ontology/smartcontract/service/native/utils"
"github.com/ontio/ontology/vm/evm/errors"
"github.com/ontio/ontology/vm/evm/params"
)
Expand Down Expand Up @@ -554,8 +554,10 @@ func MakeOngTransferLog(stateDB StateDB, from, to common.Address, value *big.Int
topic[1] = common.BytesToHash(from[:])
topic[2] = common.BytesToHash(to[:])
val := common.BytesToHash(value.Bytes())
// copied from smartcontract/service/native/utils to avoid cycle dependencies
OngContractAddress, _ := common2.AddressParseFromBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02})
sl := &types.StorageLog{
Address: common.BytesToAddress(utils.OngContractAddress[:]),
Address: common.BytesToAddress(OngContractAddress[:]),
Topics: topic,
Data: val[:],
}
Expand Down
68 changes: 0 additions & 68 deletions vm/evm/gas_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,9 @@
package evm

import (
"math"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ontio/ontology/core/store/leveldbstore"
"github.com/ontio/ontology/core/store/overlaydb"
"github.com/ontio/ontology/smartcontract/service/native/ong"
"github.com/ontio/ontology/smartcontract/storage"
"github.com/ontio/ontology/vm/evm/errors"
"github.com/ontio/ontology/vm/evm/params"
)

func TestMemoryGasCost(t *testing.T) {
Expand All @@ -51,62 +42,3 @@ func TestMemoryGasCost(t *testing.T) {
}
}
}

var eip2200Tests = []struct {
original byte
gaspool uint64
input string
used uint64
refund uint64
failure error
}{
{0, math.MaxUint64, "0x60006000556000600055", 1612, 0, nil}, // 0 -> 0 -> 0
{0, math.MaxUint64, "0x60006000556001600055", 20812, 0, nil}, // 0 -> 0 -> 1
{0, math.MaxUint64, "0x60016000556000600055", 20812, 19200, nil}, // 0 -> 1 -> 0
{0, math.MaxUint64, "0x60016000556002600055", 20812, 0, nil}, // 0 -> 1 -> 2
{0, math.MaxUint64, "0x60016000556001600055", 20812, 0, nil}, // 0 -> 1 -> 1
{1, math.MaxUint64, "0x60006000556000600055", 5812, 15000, nil}, // 1 -> 0 -> 0
{1, math.MaxUint64, "0x60006000556001600055", 5812, 4200, nil}, // 1 -> 0 -> 1
{1, math.MaxUint64, "0x60006000556002600055", 5812, 0, nil}, // 1 -> 0 -> 2
{1, math.MaxUint64, "0x60026000556000600055", 5812, 15000, nil}, // 1 -> 2 -> 0
{1, math.MaxUint64, "0x60026000556003600055", 5812, 0, nil}, // 1 -> 2 -> 3
{1, math.MaxUint64, "0x60026000556001600055", 5812, 4200, nil}, // 1 -> 2 -> 1
{1, math.MaxUint64, "0x60026000556002600055", 5812, 0, nil}, // 1 -> 2 -> 2
{1, math.MaxUint64, "0x60016000556000600055", 5812, 15000, nil}, // 1 -> 1 -> 0
{1, math.MaxUint64, "0x60016000556002600055", 5812, 0, nil}, // 1 -> 1 -> 2
{1, math.MaxUint64, "0x60016000556001600055", 1612, 0, nil}, // 1 -> 1 -> 1
{0, math.MaxUint64, "0x600160005560006000556001600055", 40818, 19200, nil}, // 0 -> 1 -> 0 -> 1
{1, math.MaxUint64, "0x600060005560016000556000600055", 10818, 19200, nil}, // 1 -> 0 -> 1 -> 0
{1, 2306, "0x6001600055", 2306, 0, errors.ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH)
{1, 2307, "0x6001600055", 806, 0, nil}, // 1 -> 1 (2301 sentry + 2xPUSH)
}

func TestEIP2200(t *testing.T) {
for i, tt := range eip2200Tests {
address := common.BytesToAddress([]byte("contract"))

db := storage.NewCacheDB(overlaydb.NewOverlayDB(leveldbstore.NewMemLevelDBStore()))
statedb := storage.NewStateDB(db, common.Hash{}, common.Hash{}, ong.OngBalanceHandle{})
statedb.CreateAccount(address)
statedb.SetCode(address, hexutil.MustDecode(tt.input))
statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original}))
_ = statedb.Commit() // Push the state into the "original" slot

vmctx := BlockContext{
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
}
vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})

_, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int))
if err != tt.failure {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
}
if used := tt.gaspool - gas; used != tt.used {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
}
if refund := vmenv.StateDB.GetRefund(); refund != tt.refund {
t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund)
}
}
}
Loading

0 comments on commit c0189e3

Please sign in to comment.