github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/native/call.go (about) 1 // Copyright 2021 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package native 18 19 import ( 20 "encoding/json" 21 "errors" 22 "math/big" 23 "sync/atomic" 24 25 "github.com/ethereum/go-ethereum/accounts/abi" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/core/tracing" 29 "github.com/ethereum/go-ethereum/core/types" 30 "github.com/ethereum/go-ethereum/core/vm" 31 "github.com/ethereum/go-ethereum/eth/tracers" 32 "github.com/ethereum/go-ethereum/params" 33 ) 34 35 //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go 36 37 func init() { 38 tracers.DefaultDirectory.Register("callTracer", newCallTracer, false) 39 } 40 41 type callLog struct { 42 Address common.Address `json:"address"` 43 Topics []common.Hash `json:"topics"` 44 Data hexutil.Bytes `json:"data"` 45 // Position of the log relative to subcalls within the same trace 46 // See https://github.com/ethereum/go-ethereum/pull/28389 for details 47 Position hexutil.Uint `json:"position"` 48 } 49 50 type callFrame struct { 51 Type vm.OpCode `json:"-"` 52 From common.Address `json:"from"` 53 Gas uint64 `json:"gas"` 54 GasUsed uint64 `json:"gasUsed"` 55 To *common.Address `json:"to,omitempty" rlp:"optional"` 56 Input []byte `json:"input" rlp:"optional"` 57 Output []byte `json:"output,omitempty" rlp:"optional"` 58 Error string `json:"error,omitempty" rlp:"optional"` 59 RevertReason string `json:"revertReason,omitempty"` 60 Calls []callFrame `json:"calls,omitempty" rlp:"optional"` 61 Logs []callLog `json:"logs,omitempty" rlp:"optional"` 62 // Placed at end on purpose. The RLP will be decoded to 0 instead of 63 // nil if there are non-empty elements after in the struct. 64 Value *big.Int `json:"value,omitempty" rlp:"optional"` 65 revertedSnapshot bool 66 } 67 68 func (f callFrame) TypeString() string { 69 return f.Type.String() 70 } 71 72 func (f callFrame) failed() bool { 73 return len(f.Error) > 0 && f.revertedSnapshot 74 } 75 76 func (f *callFrame) processOutput(output []byte, err error, reverted bool) { 77 output = common.CopyBytes(output) 78 // Clear error if tx wasn't reverted. This happened 79 // for pre-homestead contract storage OOG. 80 if err != nil && !reverted { 81 err = nil 82 } 83 if err == nil { 84 f.Output = output 85 return 86 } 87 f.Error = err.Error() 88 f.revertedSnapshot = reverted 89 if f.Type == vm.CREATE || f.Type == vm.CREATE2 { 90 f.To = nil 91 } 92 if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { 93 return 94 } 95 f.Output = output 96 if len(output) < 4 { 97 return 98 } 99 if unpacked, err := abi.UnpackRevert(output); err == nil { 100 f.RevertReason = unpacked 101 } 102 } 103 104 type callFrameMarshaling struct { 105 TypeString string `json:"type"` 106 Gas hexutil.Uint64 107 GasUsed hexutil.Uint64 108 Value *hexutil.Big 109 Input hexutil.Bytes 110 Output hexutil.Bytes 111 } 112 113 type callTracer struct { 114 callstack []callFrame 115 config callTracerConfig 116 gasLimit uint64 117 depth int 118 interrupt atomic.Bool // Atomic flag to signal execution interruption 119 reason error // Textual reason for the interruption 120 } 121 122 type callTracerConfig struct { 123 OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls 124 WithLog bool `json:"withLog"` // If true, call tracer will collect event logs 125 } 126 127 // newCallTracer returns a native go tracer which tracks 128 // call frames of a tx, and implements vm.EVMLogger. 129 func newCallTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) { 130 t, err := newCallTracerObject(ctx, cfg) 131 if err != nil { 132 return nil, err 133 } 134 return &tracers.Tracer{ 135 Hooks: &tracing.Hooks{ 136 OnTxStart: t.OnTxStart, 137 OnTxEnd: t.OnTxEnd, 138 OnEnter: t.OnEnter, 139 OnExit: t.OnExit, 140 OnLog: t.OnLog, 141 }, 142 GetResult: t.GetResult, 143 Stop: t.Stop, 144 }, nil 145 } 146 147 func newCallTracerObject(ctx *tracers.Context, cfg json.RawMessage) (*callTracer, error) { 148 var config callTracerConfig 149 if err := json.Unmarshal(cfg, &config); err != nil { 150 return nil, err 151 } 152 // First callframe contains tx context info 153 // and is populated on start and end. 154 return &callTracer{callstack: make([]callFrame, 0, 1), config: config}, nil 155 } 156 157 // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). 158 func (t *callTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 159 t.depth = depth 160 if t.config.OnlyTopCall && depth > 0 { 161 return 162 } 163 // Skip if tracing was interrupted 164 if t.interrupt.Load() { 165 return 166 } 167 168 toCopy := to 169 call := callFrame{ 170 Type: vm.OpCode(typ), 171 From: from, 172 To: &toCopy, 173 Input: common.CopyBytes(input), 174 Gas: gas, 175 Value: value, 176 } 177 if depth == 0 { 178 call.Gas = t.gasLimit 179 } 180 t.callstack = append(t.callstack, call) 181 } 182 183 // OnExit is called when EVM exits a scope, even if the scope didn't 184 // execute any code. 185 func (t *callTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 186 if depth == 0 { 187 t.captureEnd(output, gasUsed, err, reverted) 188 return 189 } 190 191 t.depth = depth - 1 192 if t.config.OnlyTopCall { 193 return 194 } 195 196 size := len(t.callstack) 197 if size <= 1 { 198 return 199 } 200 // Pop call. 201 call := t.callstack[size-1] 202 t.callstack = t.callstack[:size-1] 203 size -= 1 204 205 call.GasUsed = gasUsed 206 call.processOutput(output, err, reverted) 207 // Nest call into parent. 208 t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) 209 } 210 211 func (t *callTracer) captureEnd(output []byte, gasUsed uint64, err error, reverted bool) { 212 if len(t.callstack) != 1 { 213 return 214 } 215 t.callstack[0].processOutput(output, err, reverted) 216 } 217 218 func (t *callTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 219 t.gasLimit = tx.Gas() 220 } 221 222 func (t *callTracer) OnTxEnd(receipt *types.Receipt, err error) { 223 // Error happened during tx validation. 224 if err != nil { 225 return 226 } 227 if receipt != nil { 228 t.callstack[0].GasUsed = receipt.GasUsed 229 } 230 if t.config.WithLog { 231 // Logs are not emitted when the call fails 232 clearFailedLogs(&t.callstack[0], false) 233 } 234 } 235 236 func (t *callTracer) OnLog(log *types.Log) { 237 // Only logs need to be captured via opcode processing 238 if !t.config.WithLog { 239 return 240 } 241 // Avoid processing nested calls when only caring about top call 242 if t.config.OnlyTopCall && t.depth > 0 { 243 return 244 } 245 // Skip if tracing was interrupted 246 if t.interrupt.Load() { 247 return 248 } 249 l := callLog{ 250 Address: log.Address, 251 Topics: log.Topics, 252 Data: log.Data, 253 Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)), 254 } 255 t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, l) 256 } 257 258 // GetResult returns the json-encoded nested list of call traces, and any 259 // error arising from the encoding or forceful termination (via `Stop`). 260 func (t *callTracer) GetResult() (json.RawMessage, error) { 261 if len(t.callstack) != 1 { 262 return nil, errors.New("incorrect number of top-level calls") 263 } 264 265 res, err := json.Marshal(t.callstack[0]) 266 if err != nil { 267 return nil, err 268 } 269 return res, t.reason 270 } 271 272 // Stop terminates execution of the tracer at the first opportune moment. 273 func (t *callTracer) Stop(err error) { 274 t.reason = err 275 t.interrupt.Store(true) 276 } 277 278 // clearFailedLogs clears the logs of a callframe and all its children 279 // in case of execution failure. 280 func clearFailedLogs(cf *callFrame, parentFailed bool) { 281 failed := cf.failed() || parentFailed 282 // Clear own logs 283 if failed { 284 cf.Logs = nil 285 } 286 for i := range cf.Calls { 287 clearFailedLogs(&cf.Calls[i], failed) 288 } 289 }