github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/native/call_flat.go (about) 1 // Copyright 2023 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 "fmt" 23 "math/big" 24 "slices" 25 "strings" 26 "sync/atomic" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/core/tracing" 31 "github.com/ethereum/go-ethereum/core/types" 32 "github.com/ethereum/go-ethereum/core/vm" 33 "github.com/ethereum/go-ethereum/eth/tracers" 34 "github.com/ethereum/go-ethereum/params" 35 ) 36 37 //go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go 38 //go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go 39 40 func init() { 41 tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false) 42 } 43 44 var parityErrorMapping = map[string]string{ 45 "contract creation code storage out of gas": "Out of gas", 46 "out of gas": "Out of gas", 47 "gas uint64 overflow": "Out of gas", 48 "max code size exceeded": "Out of gas", 49 "invalid jump destination": "Bad jump destination", 50 "execution reverted": "Reverted", 51 "return data out of bounds": "Out of bounds", 52 "stack limit reached 1024 (1023)": "Out of stack", 53 "precompiled failed": "Built-in failed", 54 "invalid input length": "Built-in failed", 55 } 56 57 var parityErrorMappingStartingWith = map[string]string{ 58 "out of gas:": "Out of gas", // convert OOG wrapped errors, eg `out of gas: not enough gas for reentrancy sentry` 59 "invalid opcode:": "Bad instruction", 60 "stack underflow": "Stack underflow", 61 } 62 63 // flatCallFrame is a standalone callframe. 64 type flatCallFrame struct { 65 Action flatCallAction `json:"action"` 66 BlockHash *common.Hash `json:"blockHash"` 67 BlockNumber uint64 `json:"blockNumber"` 68 Error string `json:"error,omitempty"` 69 Result *flatCallResult `json:"result,omitempty"` 70 Subtraces int `json:"subtraces"` 71 TraceAddress []int `json:"traceAddress"` 72 TransactionHash *common.Hash `json:"transactionHash"` 73 TransactionPosition uint64 `json:"transactionPosition"` 74 Type string `json:"type"` 75 } 76 77 type flatCallAction struct { 78 Author *common.Address `json:"author,omitempty"` 79 RewardType string `json:"rewardType,omitempty"` 80 SelfDestructed *common.Address `json:"address,omitempty"` 81 Balance *big.Int `json:"balance,omitempty"` 82 CallType string `json:"callType,omitempty"` 83 CreationMethod string `json:"creationMethod,omitempty"` 84 From *common.Address `json:"from,omitempty"` 85 Gas *uint64 `json:"gas,omitempty"` 86 Init *[]byte `json:"init,omitempty"` 87 Input *[]byte `json:"input,omitempty"` 88 RefundAddress *common.Address `json:"refundAddress,omitempty"` 89 To *common.Address `json:"to,omitempty"` 90 Value *big.Int `json:"value,omitempty"` 91 } 92 93 type flatCallActionMarshaling struct { 94 Balance *hexutil.Big 95 Gas *hexutil.Uint64 96 Init *hexutil.Bytes 97 Input *hexutil.Bytes 98 Value *hexutil.Big 99 } 100 101 type flatCallResult struct { 102 Address *common.Address `json:"address,omitempty"` 103 Code *[]byte `json:"code,omitempty"` 104 GasUsed *uint64 `json:"gasUsed,omitempty"` 105 Output *[]byte `json:"output,omitempty"` 106 } 107 108 type flatCallResultMarshaling struct { 109 Code *hexutil.Bytes 110 GasUsed *hexutil.Uint64 111 Output *hexutil.Bytes 112 } 113 114 // flatCallTracer reports call frame information of a tx in a flat format, i.e. 115 // as opposed to the nested format of `callTracer`. 116 type flatCallTracer struct { 117 tracer *callTracer 118 config flatCallTracerConfig 119 chainConfig *params.ChainConfig 120 ctx *tracers.Context // Holds tracer context data 121 interrupt atomic.Bool // Atomic flag to signal execution interruption 122 activePrecompiles []common.Address // Updated on tx start based on given rules 123 } 124 125 type flatCallTracerConfig struct { 126 ConvertParityErrors bool `json:"convertParityErrors"` // If true, call tracer converts errors to parity format 127 IncludePrecompiles bool `json:"includePrecompiles"` // If true, call tracer includes calls to precompiled contracts 128 } 129 130 // newFlatCallTracer returns a new flatCallTracer. 131 func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) { 132 var config flatCallTracerConfig 133 if err := json.Unmarshal(cfg, &config); err != nil { 134 return nil, err 135 } 136 137 // Create inner call tracer with default configuration, don't forward 138 // the OnlyTopCall or WithLog to inner for now 139 t, err := newCallTracerObject(ctx, json.RawMessage("{}")) 140 if err != nil { 141 return nil, err 142 } 143 144 ft := &flatCallTracer{tracer: t, ctx: ctx, config: config, chainConfig: chainConfig} 145 return &tracers.Tracer{ 146 Hooks: &tracing.Hooks{ 147 OnTxStart: ft.OnTxStart, 148 OnTxEnd: ft.OnTxEnd, 149 OnEnter: ft.OnEnter, 150 OnExit: ft.OnExit, 151 }, 152 Stop: ft.Stop, 153 GetResult: ft.GetResult, 154 }, nil 155 } 156 157 // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). 158 func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 159 if t.interrupt.Load() { 160 return 161 } 162 t.tracer.OnEnter(depth, typ, from, to, input, gas, value) 163 164 if depth == 0 { 165 return 166 } 167 // Child calls must have a value, even if it's zero. 168 // Practically speaking, only STATICCALL has nil value. Set it to zero. 169 if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil { 170 t.tracer.callstack[len(t.tracer.callstack)-1].Value = big.NewInt(0) 171 } 172 } 173 174 // OnExit is called when EVM exits a scope, even if the scope didn't 175 // execute any code. 176 func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 177 if t.interrupt.Load() { 178 return 179 } 180 t.tracer.OnExit(depth, output, gasUsed, err, reverted) 181 182 if depth == 0 { 183 return 184 } 185 // Parity traces don't include CALL/STATICCALLs to precompiles. 186 // By default we remove them from the callstack. 187 if t.config.IncludePrecompiles { 188 return 189 } 190 var ( 191 // call has been nested in parent 192 parent = t.tracer.callstack[len(t.tracer.callstack)-1] 193 call = parent.Calls[len(parent.Calls)-1] 194 typ = call.Type 195 to = call.To 196 ) 197 if typ == vm.CALL || typ == vm.STATICCALL { 198 if t.isPrecompiled(*to) { 199 t.tracer.callstack[len(t.tracer.callstack)-1].Calls = parent.Calls[:len(parent.Calls)-1] 200 } 201 } 202 } 203 204 func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 205 if t.interrupt.Load() { 206 return 207 } 208 t.tracer.OnTxStart(env, tx, from) 209 // Update list of precompiles based on current block 210 rules := t.chainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) 211 t.activePrecompiles = vm.ActivePrecompiles(rules) 212 } 213 214 func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) { 215 if t.interrupt.Load() { 216 return 217 } 218 t.tracer.OnTxEnd(receipt, err) 219 } 220 221 // GetResult returns an empty json object. 222 func (t *flatCallTracer) GetResult() (json.RawMessage, error) { 223 if len(t.tracer.callstack) < 1 { 224 return nil, errors.New("invalid number of calls") 225 } 226 227 flat, err := flatFromNested(&t.tracer.callstack[0], []int{}, t.config.ConvertParityErrors, t.ctx) 228 if err != nil { 229 return nil, err 230 } 231 232 res, err := json.Marshal(flat) 233 if err != nil { 234 return nil, err 235 } 236 return res, t.tracer.reason 237 } 238 239 // Stop terminates execution of the tracer at the first opportune moment. 240 func (t *flatCallTracer) Stop(err error) { 241 t.tracer.Stop(err) 242 t.interrupt.Store(true) 243 } 244 245 // isPrecompiled returns whether the addr is a precompile. 246 func (t *flatCallTracer) isPrecompiled(addr common.Address) bool { 247 return slices.Contains(t.activePrecompiles, addr) 248 } 249 250 func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) { 251 var frame *flatCallFrame 252 switch input.Type { 253 case vm.CREATE, vm.CREATE2: 254 frame = newFlatCreate(input) 255 case vm.SELFDESTRUCT: 256 frame = newFlatSelfdestruct(input) 257 case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL: 258 frame = newFlatCall(input) 259 default: 260 return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type) 261 } 262 263 frame.TraceAddress = traceAddress 264 frame.Error = input.Error 265 frame.Subtraces = len(input.Calls) 266 fillCallFrameFromContext(frame, ctx) 267 if convertErrs { 268 convertErrorToParity(frame) 269 } 270 271 // Revert output contains useful information (revert reason). 272 // Otherwise discard result. 273 if input.Error != "" && input.Error != vm.ErrExecutionReverted.Error() { 274 frame.Result = nil 275 } 276 277 output = append(output, *frame) 278 for i, childCall := range input.Calls { 279 childAddr := childTraceAddress(traceAddress, i) 280 childCallCopy := childCall 281 flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx) 282 if err != nil { 283 return nil, err 284 } 285 output = append(output, flat...) 286 } 287 288 return output, nil 289 } 290 291 func newFlatCreate(input *callFrame) *flatCallFrame { 292 var ( 293 actionInit = input.Input[:] 294 resultCode = input.Output[:] 295 ) 296 297 return &flatCallFrame{ 298 Type: strings.ToLower(vm.CREATE.String()), 299 Action: flatCallAction{ 300 CreationMethod: strings.ToLower(input.Type.String()), 301 From: &input.From, 302 Gas: &input.Gas, 303 Value: input.Value, 304 Init: &actionInit, 305 }, 306 Result: &flatCallResult{ 307 GasUsed: &input.GasUsed, 308 Address: input.To, 309 Code: &resultCode, 310 }, 311 } 312 } 313 314 func newFlatCall(input *callFrame) *flatCallFrame { 315 var ( 316 actionInput = input.Input[:] 317 resultOutput = input.Output[:] 318 ) 319 320 return &flatCallFrame{ 321 Type: strings.ToLower(vm.CALL.String()), 322 Action: flatCallAction{ 323 From: &input.From, 324 To: input.To, 325 Gas: &input.Gas, 326 Value: input.Value, 327 CallType: strings.ToLower(input.Type.String()), 328 Input: &actionInput, 329 }, 330 Result: &flatCallResult{ 331 GasUsed: &input.GasUsed, 332 Output: &resultOutput, 333 }, 334 } 335 } 336 337 func newFlatSelfdestruct(input *callFrame) *flatCallFrame { 338 return &flatCallFrame{ 339 Type: "suicide", 340 Action: flatCallAction{ 341 SelfDestructed: &input.From, 342 Balance: input.Value, 343 RefundAddress: input.To, 344 }, 345 } 346 } 347 348 func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) { 349 if ctx == nil { 350 return 351 } 352 if ctx.BlockHash != (common.Hash{}) { 353 callFrame.BlockHash = &ctx.BlockHash 354 } 355 if ctx.BlockNumber != nil { 356 callFrame.BlockNumber = ctx.BlockNumber.Uint64() 357 } 358 if ctx.TxHash != (common.Hash{}) { 359 callFrame.TransactionHash = &ctx.TxHash 360 } 361 callFrame.TransactionPosition = uint64(ctx.TxIndex) 362 } 363 364 func convertErrorToParity(call *flatCallFrame) { 365 if call.Error == "" { 366 return 367 } 368 369 if parityError, ok := parityErrorMapping[call.Error]; ok { 370 call.Error = parityError 371 } else { 372 for gethError, parityError := range parityErrorMappingStartingWith { 373 if strings.HasPrefix(call.Error, gethError) { 374 call.Error = parityError 375 break 376 } 377 } 378 } 379 } 380 381 func childTraceAddress(a []int, i int) []int { 382 child := make([]int, 0, len(a)+1) 383 child = append(child, a...) 384 child = append(child, i) 385 return child 386 }