github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/native/prestate.go (about) 1 // Copyright 2022 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 "bytes" 21 "encoding/json" 22 "errors" 23 "math/big" 24 "sync/atomic" 25 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/crypto" 32 "github.com/ethereum/go-ethereum/eth/tracers" 33 "github.com/ethereum/go-ethereum/eth/tracers/internal" 34 "github.com/ethereum/go-ethereum/log" 35 "github.com/ethereum/go-ethereum/params" 36 ) 37 38 //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go 39 40 func init() { 41 tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false) 42 } 43 44 type stateMap = map[common.Address]*account 45 46 type account struct { 47 Balance *big.Int `json:"balance,omitempty"` 48 Code []byte `json:"code,omitempty"` 49 Nonce uint64 `json:"nonce,omitempty"` 50 Storage map[common.Hash]common.Hash `json:"storage,omitempty"` 51 empty bool 52 } 53 54 func (a *account) exists() bool { 55 return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) 56 } 57 58 type accountMarshaling struct { 59 Balance *hexutil.Big 60 Code hexutil.Bytes 61 } 62 63 type prestateTracer struct { 64 env *tracing.VMContext 65 pre stateMap 66 post stateMap 67 to common.Address 68 config prestateTracerConfig 69 chainConfig *params.ChainConfig 70 interrupt atomic.Bool // Atomic flag to signal execution interruption 71 reason error // Textual reason for the interruption 72 created map[common.Address]bool 73 deleted map[common.Address]bool 74 } 75 76 type prestateTracerConfig struct { 77 DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications 78 DisableCode bool `json:"disableCode"` // If true, this tracer will not return the contract code 79 DisableStorage bool `json:"disableStorage"` // If true, this tracer will not return the contract storage 80 IncludeEmpty bool `json:"includeEmpty"` // If true, this tracer will return empty state objects 81 } 82 83 func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) { 84 var config prestateTracerConfig 85 if err := json.Unmarshal(cfg, &config); err != nil { 86 return nil, err 87 } 88 // Diff mode has special semantics around account creating and deletion which 89 // requires it to include empty accounts and storage. 90 if config.DiffMode && config.IncludeEmpty { 91 return nil, errors.New("cannot use diffMode with includeEmpty") 92 } 93 t := &prestateTracer{ 94 pre: stateMap{}, 95 post: stateMap{}, 96 config: config, 97 chainConfig: chainConfig, 98 created: make(map[common.Address]bool), 99 deleted: make(map[common.Address]bool), 100 } 101 return &tracers.Tracer{ 102 Hooks: &tracing.Hooks{ 103 OnTxStart: t.OnTxStart, 104 OnTxEnd: t.OnTxEnd, 105 OnOpcode: t.OnOpcode, 106 }, 107 GetResult: t.GetResult, 108 Stop: t.Stop, 109 }, nil 110 } 111 112 // OnOpcode implements the EVMLogger interface to trace a single step of VM execution. 113 func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 114 if err != nil { 115 return 116 } 117 // Skip if tracing was interrupted 118 if t.interrupt.Load() { 119 return 120 } 121 op := vm.OpCode(opcode) 122 stackData := scope.StackData() 123 stackLen := len(stackData) 124 caller := scope.Address() 125 switch { 126 case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): 127 slot := common.Hash(stackData[stackLen-1].Bytes32()) 128 t.lookupStorage(caller, slot) 129 case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): 130 addr := common.Address(stackData[stackLen-1].Bytes20()) 131 t.lookupAccount(addr) 132 if op == vm.SELFDESTRUCT { 133 t.deleted[caller] = true 134 } 135 case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): 136 addr := common.Address(stackData[stackLen-2].Bytes20()) 137 t.lookupAccount(addr) 138 // Lookup the delegation target 139 if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) { 140 code := t.env.StateDB.GetCode(addr) 141 if target, ok := types.ParseDelegation(code); ok { 142 t.lookupAccount(target) 143 } 144 } 145 case op == vm.CREATE: 146 nonce := t.env.StateDB.GetNonce(caller) 147 addr := crypto.CreateAddress(caller, nonce) 148 t.lookupAccount(addr) 149 t.created[addr] = true 150 case stackLen >= 4 && op == vm.CREATE2: 151 offset := stackData[stackLen-2] 152 size := stackData[stackLen-3] 153 init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64())) 154 if err != nil { 155 log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) 156 return 157 } 158 inithash := crypto.Keccak256(init) 159 salt := stackData[stackLen-4] 160 addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) 161 t.lookupAccount(addr) 162 t.created[addr] = true 163 } 164 } 165 166 func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 167 t.env = env 168 if tx.To() == nil { 169 t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from)) 170 t.created[t.to] = true 171 } else { 172 t.to = *tx.To() 173 // Lookup the delegation target 174 if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) { 175 code := t.env.StateDB.GetCode(t.to) 176 if target, ok := types.ParseDelegation(code); ok { 177 t.lookupAccount(target) 178 } 179 } 180 } 181 182 t.lookupAccount(from) 183 t.lookupAccount(t.to) 184 t.lookupAccount(env.Coinbase) 185 186 // Add accounts with authorizations to the prestate before they get applied. 187 for _, auth := range tx.SetCodeAuthorizations() { 188 addr, err := auth.Authority() 189 if err != nil { 190 continue 191 } 192 t.lookupAccount(addr) 193 } 194 } 195 196 func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) { 197 if err != nil { 198 return 199 } 200 if t.config.DiffMode { 201 t.processDiffState() 202 } 203 // Remove accounts that were empty prior to execution. Unless 204 // user requested to include empty accounts. 205 if t.config.IncludeEmpty { 206 return 207 } 208 for addr, s := range t.pre { 209 if s.empty { 210 delete(t.pre, addr) 211 } 212 } 213 } 214 215 // GetResult returns the json-encoded nested list of call traces, and any 216 // error arising from the encoding or forceful termination (via `Stop`). 217 func (t *prestateTracer) GetResult() (json.RawMessage, error) { 218 var res []byte 219 var err error 220 if t.config.DiffMode { 221 res, err = json.Marshal(struct { 222 Post stateMap `json:"post"` 223 Pre stateMap `json:"pre"` 224 }{t.post, t.pre}) 225 } else { 226 res, err = json.Marshal(t.pre) 227 } 228 if err != nil { 229 return nil, err 230 } 231 return json.RawMessage(res), t.reason 232 } 233 234 // Stop terminates execution of the tracer at the first opportune moment. 235 func (t *prestateTracer) Stop(err error) { 236 t.reason = err 237 t.interrupt.Store(true) 238 } 239 240 func (t *prestateTracer) processDiffState() { 241 for addr, state := range t.pre { 242 // The deleted account's state is pruned from `post` but kept in `pre` 243 if _, ok := t.deleted[addr]; ok { 244 continue 245 } 246 modified := false 247 postAccount := &account{Storage: make(map[common.Hash]common.Hash)} 248 newBalance := t.env.StateDB.GetBalance(addr).ToBig() 249 newNonce := t.env.StateDB.GetNonce(addr) 250 251 if newBalance.Cmp(t.pre[addr].Balance) != 0 { 252 modified = true 253 postAccount.Balance = newBalance 254 } 255 if newNonce != t.pre[addr].Nonce { 256 modified = true 257 postAccount.Nonce = newNonce 258 } 259 if !t.config.DisableCode { 260 newCode := t.env.StateDB.GetCode(addr) 261 if !bytes.Equal(newCode, t.pre[addr].Code) { 262 modified = true 263 postAccount.Code = newCode 264 } 265 } 266 267 if !t.config.DisableStorage { 268 for key, val := range state.Storage { 269 // don't include the empty slot 270 if val == (common.Hash{}) { 271 delete(t.pre[addr].Storage, key) 272 } 273 274 newVal := t.env.StateDB.GetState(addr, key) 275 if val == newVal { 276 // Omit unchanged slots 277 delete(t.pre[addr].Storage, key) 278 } else { 279 modified = true 280 if newVal != (common.Hash{}) { 281 postAccount.Storage[key] = newVal 282 } 283 } 284 } 285 } 286 287 if modified { 288 t.post[addr] = postAccount 289 } else { 290 // if state is not modified, then no need to include into the pre state 291 delete(t.pre, addr) 292 } 293 } 294 } 295 296 // lookupAccount fetches details of an account and adds it to the prestate 297 // if it doesn't exist there. 298 func (t *prestateTracer) lookupAccount(addr common.Address) { 299 if _, ok := t.pre[addr]; ok { 300 return 301 } 302 303 acc := &account{ 304 Balance: t.env.StateDB.GetBalance(addr).ToBig(), 305 Nonce: t.env.StateDB.GetNonce(addr), 306 Code: t.env.StateDB.GetCode(addr), 307 } 308 if !acc.exists() { 309 acc.empty = true 310 } 311 // The code must be fetched first for the emptiness check. 312 if t.config.DisableCode { 313 acc.Code = nil 314 } 315 if !t.config.DisableStorage { 316 acc.Storage = make(map[common.Hash]common.Hash) 317 } 318 t.pre[addr] = acc 319 } 320 321 // lookupStorage fetches the requested storage slot and adds 322 // it to the prestate of the given contract. It assumes `lookupAccount` 323 // has been performed on the contract before. 324 func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) { 325 if t.config.DisableStorage { 326 return 327 } 328 if _, ok := t.pre[addr].Storage[key]; ok { 329 return 330 } 331 t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key) 332 }