Skip to content

Commit

Permalink
CallTracer implementation for debug endpoints (#2001)
Browse files Browse the repository at this point in the history
* CallTracer implementation

* Comments fix

* Comments fix

* Comments fix
  • Loading branch information
goran-ethernal authored Oct 26, 2023
1 parent 3377295 commit 7001f4e
Show file tree
Hide file tree
Showing 3 changed files with 462 additions and 7 deletions.
24 changes: 17 additions & 7 deletions jsonrpc/debug_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (

"github.com/0xPolygon/polygon-edge/helper/hex"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer/calltracer"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer/structtracer"
"github.com/0xPolygon/polygon-edge/types"
)

const callTracerName = "callTracer"

var (
defaultTraceTimeout = 5 * time.Second

Expand Down Expand Up @@ -83,6 +86,7 @@ type TraceConfig struct {
EnableReturnData bool `json:"enableReturnData"`
DisableStructLogs bool `json:"disableStructLogs"`
Timeout *string `json:"timeout"`
Tracer string `json:"tracer"`
}

func (d *Debug) TraceBlockByNumber(
Expand Down Expand Up @@ -248,13 +252,19 @@ func newTracer(config *TraceConfig) (
}
}

tracer := structtracer.NewStructTracer(structtracer.Config{
EnableMemory: config.EnableMemory && !config.DisableStructLogs,
EnableStack: !config.DisableStack && !config.DisableStructLogs,
EnableStorage: !config.DisableStorage && !config.DisableStructLogs,
EnableReturnData: config.EnableReturnData,
EnableStructLogs: !config.DisableStructLogs,
})
var tracer tracer.Tracer

if config.Tracer == callTracerName {
tracer = &calltracer.CallTracer{}
} else {
tracer = structtracer.NewStructTracer(structtracer.Config{
EnableMemory: config.EnableMemory && !config.DisableStructLogs,
EnableStack: !config.DisableStack && !config.DisableStructLogs,
EnableStorage: !config.DisableStorage && !config.DisableStructLogs,
EnableReturnData: config.EnableReturnData,
EnableStructLogs: !config.DisableStructLogs,
})
}

timeoutCtx, cancel := context.WithTimeout(context.Background(), timeout)

Expand Down
157 changes: 157 additions & 0 deletions state/runtime/tracer/calltracer/call_tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package calltracer

import (
"math/big"
"sync"

"github.com/0xPolygon/polygon-edge/helper/hex"
"github.com/0xPolygon/polygon-edge/state/runtime/tracer"
"github.com/0xPolygon/polygon-edge/types"
)

var (
callTypes = map[int]string{
0: "CALL",
1: "CALLCODE",
2: "DELEGATECALL",
3: "STATICCALL",
4: "CREATE",
5: "CREATE2",
}
)

type Call struct {
Type string `json:"type"`
From string `json:"from"`
To string `json:"to"`
Value string `json:"value,omitempty"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output"`
Calls []*Call `json:"calls,omitempty"`

parent *Call
startGas uint64
}

type CallTracer struct {
call *Call
activeCall *Call
activeGas uint64
activeAvailableGas uint64

cancelLock sync.RWMutex
reason error
stop bool
}

func (c *CallTracer) Cancel(err error) {
c.cancelLock.Lock()
defer c.cancelLock.Unlock()

c.reason = err
c.stop = true
}

func (c *CallTracer) cancelled() bool {
c.cancelLock.RLock()
defer c.cancelLock.RUnlock()

return c.stop
}

func (c *CallTracer) Clear() {
c.call = nil
c.activeCall = nil
}

func (c *CallTracer) GetResult() (interface{}, error) {
c.cancelLock.RLock()
defer c.cancelLock.RUnlock()

if c.reason != nil {
return nil, c.reason
}

return c.call, nil
}

func (c *CallTracer) TxStart(gasLimit uint64) {
}

func (c *CallTracer) TxEnd(gasLeft uint64) {
}

func (c *CallTracer) CallStart(depth int, from, to types.Address, callType int,
gas uint64, value *big.Int, input []byte) {
if c.cancelled() {
return
}

typ, ok := callTypes[callType]
if !ok {
typ = "UNKNOWN"
}

val := "0x0"
if value != nil {
val = hex.EncodeBig(value)
}

call := &Call{
Type: typ,
From: from.String(),
To: to.String(),
Value: val,
Gas: hex.EncodeUint64(gas),
GasUsed: "",
Input: hex.EncodeToHex(input),
Output: "",
Calls: nil,
startGas: gas,
}

if depth == 1 {
c.call = call
c.activeCall = call
} else {
call.parent = c.activeCall
c.activeCall.Calls = append(c.activeCall.Calls, call)
c.activeCall = call
}
}

func (c *CallTracer) CallEnd(depth int, output []byte, err error) {
c.activeCall.Output = hex.EncodeToHex(output)

gasUsed := uint64(0)

if c.activeCall.startGas > c.activeAvailableGas {
gasUsed = c.activeCall.startGas - c.activeAvailableGas
}

c.activeCall.GasUsed = hex.EncodeUint64(gasUsed)
c.activeGas = 0

if depth > 1 {
c.activeCall = c.activeCall.parent
}

if err != nil {
c.Cancel(err)
}
}

func (c *CallTracer) CaptureState(memory []byte, stack []*big.Int, opCode int,
contractAddress types.Address, sp int, host tracer.RuntimeHost, state tracer.VMState) {
if c.cancelled() {
state.Halt()
}
}

func (c *CallTracer) ExecuteState(contractAddress types.Address, ip uint64, opcode string,
availableGas uint64, cost uint64, lastReturnData []byte, depth int, err error, host tracer.RuntimeHost) {
c.activeGas += cost
c.activeAvailableGas = availableGas
}
Loading

0 comments on commit 7001f4e

Please sign in to comment.