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

[jsonrpc] Add debug_traceTransaction endpoint #202

Merged
merged 29 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8a92c33
Add a default dummy tracer in transition
DarianShawn Sep 22, 2022
5e54d90
Refactor EVMLogger interface
DarianShawn Sep 23, 2022
7d64991
Expose opcode and runtime type trans function
DarianShawn Sep 23, 2022
8453223
runtime calltype type assertion
DarianShawn Sep 24, 2022
b6e9e1e
Move EVMLogger to runtime module to break import cycle
DarianShawn Sep 24, 2022
4b1c3b3
Capture entrance and exit in create and call
DarianShawn Sep 24, 2022
e7f40ae
Fix evm test
DarianShawn Sep 26, 2022
a6941cd
Add struct logger
DarianShawn Sep 26, 2022
6bb149b
Implement StructLog CaptureState feature
DarianShawn Sep 26, 2022
2391b2e
Use defer param to prevent panic
DarianShawn Sep 27, 2022
456b720
Implement CaptureState
DarianShawn Sep 27, 2022
2107001
Record state in evm
DarianShawn Sep 27, 2022
9c043ab
Import a new debug endpoint
DarianShawn Sep 27, 2022
7e0e115
Hide memory debugging
DarianShawn Sep 28, 2022
3f76ba2
Fix opcode and err not print
DarianShawn Sep 28, 2022
5f69150
Stack value print hex
DarianShawn Sep 28, 2022
1818880
Fix gas cost always zero
DarianShawn Sep 28, 2022
1276353
Fix stack not right
DarianShawn Sep 28, 2022
5031c06
Copy memory before executing
DarianShawn Sep 28, 2022
2ae12b7
Fix lint error
DarianShawn Sep 28, 2022
b252a23
Fix stack swallow copy
DarianShawn Sep 28, 2022
209ad7f
Use flag field for less debug execution
DarianShawn Sep 28, 2022
fa01035
Remove unused dummy tracer
DarianShawn Sep 28, 2022
f20733c
Remove too complicated bechmark test
DarianShawn Sep 29, 2022
3cab6b7
Fix storage and memory print ascii string instead of hex string
DarianShawn Oct 9, 2022
6fc9673
Set formatLog inner function
DarianShawn Oct 9, 2022
803c983
Unit tests on formatLogs
DarianShawn Oct 9, 2022
0bd7f87
Fix debug not return message when revert
DarianShawn Oct 10, 2022
7c7fb92
Fix lint error
DarianShawn Oct 10, 2022
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
168 changes: 168 additions & 0 deletions jsonrpc/debug_endpoint.go
Original file line number Diff line number Diff line change
@@ -1 +1,169 @@
package jsonrpc

import (
"errors"
"fmt"

"github.com/dogechain-lab/dogechain/helper/hex"
"github.com/dogechain-lab/dogechain/state"
"github.com/dogechain-lab/dogechain/state/runtime"
"github.com/dogechain-lab/dogechain/state/tracer/structlogger"
"github.com/dogechain-lab/dogechain/types"
)

var (
ErrTransactionNotSeal = errors.New("transaction not sealed")
ErrGenesisNotTracable = errors.New("genesis is not traceable")
ErrTransactionNotFoundInBlock = errors.New("transaction not found in block")
)

type Debug struct {
store ethStore
}

func (d *Debug) TraceTransaction(hash types.Hash) (interface{}, error) {
// Check the chain state for the transaction
blockHash, ok := d.store.ReadTxLookup(hash)
if !ok {
// Block not found in storage
return nil, ErrBlockNotFound
}

block, ok := d.store.GetBlockByHash(blockHash, true)
if !ok {
// Block receipts not found in storage
return nil, ErrTransactionNotSeal
}
// It shouldn't happen in practice.
if block.Number() == 0 {
return nil, ErrGenesisNotTracable
}

var (
tx *types.Transaction
txIdx = -1
)

// Find the transaction within the block
for idx, txn := range block.Transactions {
if txn.Hash == hash {
tx = txn
txIdx = idx

break
}
}

if txIdx < 0 {
// it shouldn't be
return nil, ErrTransactionNotFoundInBlock
}

txn, err := d.store.StateAtTransaction(block, txIdx)
if err != nil {
return nil, err
}

return d.traceTx(txn, tx)
}

func (d *Debug) traceTx(txn *state.Transition, tx *types.Transaction) (interface{}, error) {
var tracer runtime.EVMLogger = structlogger.NewStructLogger(txn.Txn())

txn.SetEVMLogger(tracer)

result, err := txn.Apply(tx)
if err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}

switch tracer := tracer.(type) {
case *structlogger.StructLogger:
returnVal := fmt.Sprintf("%x", result.Return())
// If the result contains a revert reason, return it.
if result.Reverted() {
returnVal = fmt.Sprintf("%x", result.Revert())
}

return &ExecutionResult{
Gas: result.GasUsed,
Failed: result.Failed(),
ReturnValue: returnVal,
StructLogs: formatLogs(tracer.StructLogs()),
}, nil
default:
panic(fmt.Sprintf("bad tracer type %T", tracer))
}
}

// 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 string `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 []*structlogger.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))

for index, trace := range logs {
trace.OpName = trace.GetOpName()
trace.ErrorString = trace.GetErrorString()

formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.OpName,
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.ErrorString,
}

if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = hex.EncodeBig(stackValue)
}

formatted[index].Stack = &stack
}

if len(trace.Memory) > 0 {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, hex.EncodeToString(trace.Memory[i:i+32]))
}

formatted[index].Memory = &memory
}

if len(trace.Storage) > 0 {
storage := make(map[string]string)
for addr, val := range trace.Storage {
storage[hex.EncodeToString(addr.Bytes())] = hex.EncodeToString(val.Bytes())
}

formatted[index].Storage = &storage
}
}

return formatted
}
199 changes: 199 additions & 0 deletions jsonrpc/debug_endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package jsonrpc

import (
"math/big"
"testing"

"github.com/dogechain-lab/dogechain/helper/hex"
"github.com/dogechain-lab/dogechain/state/runtime/evm"
"github.com/dogechain-lab/dogechain/state/tracer/structlogger"
"github.com/dogechain-lab/dogechain/types"
"github.com/stretchr/testify/assert"
)

func TestDebug_FormatLogs(t *testing.T) {
//nolint:lll
var (
stackPc121 = []string{
"0x1ab06ee5",
"0x55",
"0x4",
"0x5",
"0x4",
"0x0",
}
stackPc122 = []string{
"0x1ab06ee5",
"0x55",
"0x4",
"0x5",
"0x4",
"0x0",
"0x4",
}
stackPc123 = []string{
"0x1ab06ee5",
"0x55",
"0x4",
"0x5",
"0x4",
"0x4",
"0x0",
}
stackPc124 = []string{
"0x1ab06ee5",
"0x55",
"0x4",
"0x5",
"0x4",
}
storagePc123 = map[string]string{
"0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000004",
}
memoryBytesPc124, _ = hex.DecodeHex("0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000080")
memoryPc124 = []string{
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000080",
}
)

tests := []struct {
name string
input []*structlogger.StructLog
result []StructLogRes
}{
{
name: "Stack should format right",
input: []*structlogger.StructLog{
{
Pc: 121,
Op: evm.DUP1 + 1, // DUP2
Gas: 40035,
GasCost: 3,
Depth: 1,
Stack: []*big.Int{
new(big.Int).SetUint64(0x1ab06ee5),
new(big.Int).SetUint64(0x55),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x5),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x0),
},
},
{
Pc: 122,
Op: evm.SWAP1,
Gas: 40032,
GasCost: 3,
Depth: 1,
Stack: []*big.Int{
new(big.Int).SetUint64(0x1ab06ee5),
new(big.Int).SetUint64(0x55),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x5),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x0),
new(big.Int).SetUint64(0x4),
},
},
},
result: []StructLogRes{
{
Pc: 121,
Op: evm.OpCode(evm.DUP1 + 1).String(), // DUP2
Gas: 40035,
GasCost: 3,
Depth: 1,
Stack: &stackPc121,
},
{
Pc: 122,
Op: evm.OpCode(evm.SWAP1).String(),
Gas: 40032,
GasCost: 3,
Depth: 1,
Stack: &stackPc122,
},
},
},
{
name: "Storage should format right",
input: []*structlogger.StructLog{
{
Pc: 123,
Op: evm.SSTORE,
Gas: 40029,
GasCost: 20000,
Depth: 1,
Stack: []*big.Int{
new(big.Int).SetUint64(0x1ab06ee5),
new(big.Int).SetUint64(0x55),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x5),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x0),
},
Storage: map[types.Hash]types.Hash{
types.StringToHash("0x0"): types.StringToHash("0x4"),
},
},
},
result: []StructLogRes{
{
Pc: 123,
Op: evm.OpCode(evm.SSTORE).String(),
Gas: 40029,
GasCost: 20000,
Depth: 1,
Stack: &stackPc123,
Storage: &storagePc123,
},
},
},
{
name: "Memory should format right",
input: []*structlogger.StructLog{
{
Pc: 124,
Op: evm.POP,
Gas: 20029,
GasCost: 2,
Depth: 1,
Stack: []*big.Int{
new(big.Int).SetUint64(0x1ab06ee5),
new(big.Int).SetUint64(0x55),
new(big.Int).SetUint64(0x4),
new(big.Int).SetUint64(0x5),
new(big.Int).SetUint64(0x4),
},
Memory: memoryBytesPc124,
},
},
result: []StructLogRes{
{
Pc: 124,
Op: evm.OpCode(evm.POP).String(),
Gas: 20029,
GasCost: 2,
Depth: 1,
Stack: &stackPc124,
Memory: &memoryPc124,
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// format it
v := formatLogs(tt.input)

// Assert equality
assert.Equal(t, tt.result, v)
})
}
}
3 changes: 3 additions & 0 deletions jsonrpc/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type endpoints struct {
Web3 *Web3
Net *Net
TxPool *TxPool
Debug *Debug
}

// Dispatcher handles all json rpc requests by delegating
Expand Down Expand Up @@ -84,11 +85,13 @@ func (d *Dispatcher) registerEndpoints(store JSONRPCStore) {
d.endpoints.Net = &Net{store, d.chainID}
d.endpoints.Web3 = &Web3{}
d.endpoints.TxPool = &TxPool{store}
d.endpoints.Debug = &Debug{store}

d.registerService("eth", d.endpoints.Eth)
d.registerService("net", d.endpoints.Net)
d.registerService("web3", d.endpoints.Web3)
d.registerService("txpool", d.endpoints.TxPool)
d.registerService("debug", d.endpoints.Debug)
}

func (d *Dispatcher) getFnHandler(req Request) (*serviceData, *funcData, Error) {
Expand Down
Loading