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