Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add debug_traceCall rpc #1437

Merged
merged 3 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading