Skip to content

Commit

Permalink
tracers: Add native.CallTracer
Browse files Browse the repository at this point in the history
  • Loading branch information
blukat29 committed Jun 20, 2024
1 parent fddf35a commit 79830d9
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ require (
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo=
github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE=
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY=
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
Expand All @@ -187,6 +189,8 @@ github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc=
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
Expand Down
215 changes: 215 additions & 0 deletions node/cn/tracers/native/call_tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// Modifications Copyright 2024 The Kaia Authors
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library 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 go-ethereum library 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package native

import (
"encoding/json"
"errors"
"math/big"
"sync/atomic"

"github.com/klaytn/klaytn/accounts/abi"
"github.com/klaytn/klaytn/blockchain/vm"
"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/common/hexutil"
)

var _ vm.Tracer = (*CallTracer)(nil)

//go:generate go run github.com/fjl/gencodec -type CallFrame -field-override callFrameMarshaling -out gen_callframe_json.go
type CallFrame struct {
Type vm.OpCode `json:"-"` // e.g. CALL, DELEGATECALL, CREATE
From common.Address `json:"from"`
Gas uint64 `json:"gas"` // gasLeft. for top-level call, tx.gasLimit
GasUsed uint64 `json:"gasUsed"` // gasUsed so far. for top-level call, tx.gasLimit - gasLeft = receipt.gasUsed
To *common.Address `json:"to,omitempty"` // recipient address, created contract address, or nil for failed contract creation,
Input []byte `json:"input"`
Output []byte `json:"output,omitempty"` // result of an internal call or revert message or runtime bytecode
Error string `json:"error,omitempty"`
RevertReason string `json:"revertReason,omitempty"` // decoded revert message in geth style.
Reverted *RevertedInfo `json:"reverted,omitempty"` // decoded revert message and reverted contract address in klaytn style.
Calls []CallFrame `json:"calls,omitempty"` // child calls
Value *big.Int `json:"value,omitempty"`
}

type RevertedInfo struct {
Contract *common.Address `json:"contract,omitempty"`
Message string `json:"message,omitempty"`
}

func (f CallFrame) TypeString() string { // to satisfy gencodec
return f.Type.String()
}

// FieldType overrides for callFrame that's used for JSON encoding
// Must rerun gencodec after modifying this struct
type callFrameMarshaling struct {
TypeString string `json:"type"`
Gas hexutil.Uint64
GasUsed hexutil.Uint64
Value *hexutil.Big
Input hexutil.Bytes
Output hexutil.Bytes
}

// Populate output, error, and revert-related fields
// 1. no error: {output}
// 2. non-revert error: {to: nil if CREATE, error}
// 3. revert error without message: {to: nil if CREATE, output, error, reverted{contract}}
// 4. revert error with message: {to: nil if CREATE, output, error, reverted{contract, message}, revertReason}
func (c *CallFrame) processOutput(output []byte, err error) {
// 1: return output
if err == nil {
c.Output = common.CopyBytes(output)
return
}

// 2,3,4: to = nil if CREATE failed
if c.Type == vm.CREATE || c.Type == vm.CREATE2 {
c.To = nil
}

// 2: do not return output
if !errors.Is(err, vm.ErrExecutionReverted) { // non-revert error
c.Error = err.Error()
return
}

// 3,4: return output and revert info
c.Output = common.CopyBytes(output)
c.Error = "execution reverted"
c.Reverted = &RevertedInfo{Contract: c.To} // 'To' was recorded when entering this call frame

// 4: attach revert reason
if reason, unpackErr := abi.UnpackRevert(output); unpackErr == nil {
c.RevertReason = reason
c.Reverted.Message = reason
}
}

// Implements vm.Tracer interface
type CallTracer struct {
callstack []CallFrame
gasLimit uint64 // saved tx.gasLimit
interrupt atomic.Bool
interruptReason error
}

func NewCallTracer() *CallTracer {
return &CallTracer{
callstack: make([]CallFrame, 1), // empty top-level frame
}
}

// Transaction start
func (t *CallTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit
}

// Transaction end
func (t *CallTracer) CaptureTxEnd(gasLeft uint64) {
t.callstack[0].GasUsed = t.callstack[0].Gas - gasLeft
}

// Enter top-level call frame
func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
toCopy := to
t.callstack[0] = CallFrame{
Type: vm.CALL,
From: from,
To: &toCopy,
Input: common.CopyBytes(input),
Gas: t.gasLimit, // ignore 'gas' supplied from EVM. Use tx.gasLimit that includes intrinsic gas.
Value: value,
}
if create {
t.callstack[0].Type = vm.CREATE
}
}

// Exit top-level call frame
func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) {
// gasUsed will be filled by CaptureTxEnd; just process the output
t.callstack[0].processOutput(output, err)
}

// Enter nested call frame
func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
if t.interrupt.Load() {
return
}

toCopy := to
call := CallFrame{
Type: typ,
From: from,
Gas: gas,
To: &toCopy,
Value: value,
Input: common.CopyBytes(input),
}
t.callstack = append(t.callstack, call)
}

// Exit nested call frame
func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
size := len(t.callstack)
if size <= 1 { // just in case; should never happen though because CaptureExit is only called when depth > 0
return
}

// process output into the currently exiting call
call := t.callstack[size-1]
call.GasUsed = gasUsed
call.processOutput(output, err)

// pop current frame
t.callstack = t.callstack[:size-1]

// append it to the parent frame's Calls
t.callstack[size-2].Calls = append(t.callstack[size-2].Calls, call)
}

// Each opcode
func (t *CallTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost, ccLeft, ccOpcode uint64, scope *vm.ScopeContext, depth int, err error) {
}

// Fault during opcode execution
func (t *CallTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost, ccLeft, ccOpcode uint64, scope *vm.ScopeContext, depth int, err error) {
}

func (t *CallTracer) GetResult() (json.RawMessage, error) {
if len(t.callstack) != 1 {
return nil, errors.New("incorrect number of top-level calls")
}

result, err := json.Marshal(t.callstack[0])
if err != nil {
return nil, err
}

// Return with interrupt reason if any
return result, t.interruptReason
}

// Stop terminates execution of the tracer at the first opportune moment.
// For CallTracer, it stops at CaptureEnter, which is the most repetitive operation.
func (t *CallTracer) Stop(err error) {
t.interrupt.Store(true)
t.interruptReason = err
}
107 changes: 107 additions & 0 deletions node/cn/tracers/native/gen_callframe_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 79830d9

Please sign in to comment.