github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/core/trace.go (about) 1 package core 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "runtime" 8 "sync" 9 10 "github.com/scroll-tech/go-ethereum/common" 11 "github.com/scroll-tech/go-ethereum/common/hexutil" 12 "github.com/scroll-tech/go-ethereum/consensus" 13 "github.com/scroll-tech/go-ethereum/core/rawdb" 14 "github.com/scroll-tech/go-ethereum/core/state" 15 "github.com/scroll-tech/go-ethereum/core/types" 16 "github.com/scroll-tech/go-ethereum/core/vm" 17 "github.com/scroll-tech/go-ethereum/ethdb" 18 "github.com/scroll-tech/go-ethereum/log" 19 "github.com/scroll-tech/go-ethereum/params" 20 "github.com/scroll-tech/go-ethereum/rollup/fees" 21 "github.com/scroll-tech/go-ethereum/rollup/rcfg" 22 "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" 23 "github.com/scroll-tech/go-ethereum/trie/zkproof" 24 ) 25 26 type TraceEnv struct { 27 logConfig *vm.LogConfig 28 commitAfterApply bool 29 chainConfig *params.ChainConfig 30 31 coinbase common.Address 32 33 // rMu lock is used to protect txs executed in parallel. 34 signer types.Signer 35 state *state.StateDB 36 blockCtx vm.BlockContext 37 38 // pMu lock is used to protect Proofs' read and write mutual exclusion, 39 // since txs are executed in parallel, so this lock is required. 40 pMu sync.Mutex 41 // sMu is required because of txs are executed in parallel, 42 // this lock is used to protect StorageTrace's read and write mutual exclusion. 43 sMu sync.Mutex 44 *types.StorageTrace 45 TxStorageTraces []*types.StorageTrace 46 // zktrie tracer is used for zktrie storage to build additional deletion proof 47 ZkTrieTracer map[string]state.ZktrieProofTracer 48 ExecutionResults []*types.ExecutionResult 49 50 // StartL1QueueIndex is the next L1 message queue index that this block can process. 51 // Example: If the parent block included QueueIndex=9, then StartL1QueueIndex will 52 // be 10. 53 StartL1QueueIndex uint64 54 } 55 56 // Context is the same as Context in eth/tracers/tracers.go 57 type Context struct { 58 BlockHash common.Hash 59 TxIndex int 60 TxHash common.Hash 61 } 62 63 // txTraceTask is the same as txTraceTask in eth/tracers/api.go 64 type txTraceTask struct { 65 statedb *state.StateDB 66 index int 67 } 68 69 func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConfig, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv { 70 return &TraceEnv{ 71 logConfig: logConfig, 72 commitAfterApply: commitAfterApply, 73 chainConfig: chainConfig, 74 coinbase: coinbase, 75 signer: types.MakeSigner(chainConfig, block.Number()), 76 state: statedb, 77 blockCtx: blockCtx, 78 StorageTrace: &types.StorageTrace{ 79 RootBefore: rootBefore, 80 RootAfter: block.Root(), 81 Proofs: make(map[string][]hexutil.Bytes), 82 StorageProofs: make(map[string]map[string][]hexutil.Bytes), 83 }, 84 ZkTrieTracer: make(map[string]state.ZktrieProofTracer), 85 ExecutionResults: make([]*types.ExecutionResult, block.Transactions().Len()), 86 TxStorageTraces: make([]*types.StorageTrace, block.Transactions().Len()), 87 StartL1QueueIndex: startL1QueueIndex, 88 } 89 } 90 91 func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) { 92 var coinbase common.Address 93 var err error 94 if chainConfig.Scroll.FeeVaultEnabled() { 95 coinbase = *chainConfig.Scroll.FeeVaultAddress 96 } else { 97 coinbase, err = engine.Author(block.Header()) 98 if err != nil { 99 log.Warn("recover coinbase in CreateTraceEnv fail. using zero-address", "err", err, "blockNumber", block.Header().Number, "headerHash", block.Header().Hash()) 100 } 101 } 102 103 // Collect start queue index, we should always have this value for blocks 104 // that have been executed. 105 // FIXME: This value will be incorrect on the signer, since we reuse this 106 // DB entry to signal which index the worker should continue from. 107 // Example: Ledger A <-- B <-- C. Block `A` contains up to `QueueIndex=9`. 108 // For block `B`, the worker skips 10 messages and includes 0. 109 // `ReadFirstQueueIndexNotInL2Block(B)` will then return `20` on the 110 // signer to avoid re-processing the same 10 transactions again for 111 // block `C`. 112 // `ReadFirstQueueIndexNotInL1Block(B)` will return the correct value 113 // `10` on follower nodes. 114 startL1QueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(chaindb, parent.Hash()) 115 if startL1QueueIndex == nil { 116 log.Error("missing FirstQueueIndexNotInL2Block for block during trace call", "number", parent.NumberU64(), "hash", parent.Hash()) 117 return nil, fmt.Errorf("missing FirstQueueIndexNotInL2Block for block during trace call: hash=%v, parentHash=%vv", block.Hash(), parent.Hash()) 118 } 119 env := CreateTraceEnvHelper( 120 chainConfig, 121 &vm.LogConfig{ 122 EnableMemory: false, 123 EnableReturnData: true, 124 }, 125 NewEVMBlockContext(block.Header(), chainContext, chainConfig, nil), 126 *startL1QueueIndex, 127 coinbase, 128 statedb, 129 parent.Root(), 130 block, 131 commitAfterApply, 132 ) 133 134 key := coinbase.String() 135 if _, exist := env.Proofs[key]; !exist { 136 proof, err := env.state.GetProof(coinbase) 137 if err != nil { 138 log.Error("Proof for coinbase not available", "coinbase", coinbase, "error", err) 139 // but we still mark the proofs map with nil array 140 } 141 env.Proofs[key] = types.WrapProof(proof) 142 } 143 144 return env, nil 145 } 146 147 func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error) { 148 // Execute all the transaction contained within the block concurrently 149 var ( 150 txs = block.Transactions() 151 pend = new(sync.WaitGroup) 152 jobs = make(chan *txTraceTask, len(txs)) 153 errCh = make(chan error, 1) 154 ) 155 threads := runtime.NumCPU() 156 if threads > len(txs) { 157 threads = len(txs) 158 } 159 for th := 0; th < threads; th++ { 160 pend.Add(1) 161 go func() { 162 defer pend.Done() 163 // Fetch and execute the next transaction trace tasks 164 for task := range jobs { 165 if err := env.getTxResult(task.statedb, task.index, block); err != nil { 166 select { 167 case errCh <- err: 168 default: 169 } 170 log.Error( 171 "failed to trace tx", 172 "txHash", txs[task.index].Hash().String(), 173 "blockHash", block.Hash().String(), 174 "blockNumber", block.NumberU64(), 175 "err", err, 176 ) 177 } 178 } 179 }() 180 } 181 182 // Feed the transactions into the tracers and return 183 var failed error 184 for i, tx := range txs { 185 // Send the trace task over for execution 186 jobs <- &txTraceTask{statedb: env.state.Copy(), index: i} 187 188 // Generate the next state snapshot fast without tracing 189 msg, _ := tx.AsMessage(env.signer, block.BaseFee()) 190 env.state.Prepare(tx.Hash(), i) 191 vmenv := vm.NewEVM(env.blockCtx, NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{}) 192 l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) 193 if err != nil { 194 failed = err 195 break 196 } 197 if _, err = ApplyMessage(vmenv, msg, new(GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { 198 failed = err 199 break 200 } 201 if env.commitAfterApply { 202 env.state.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 203 } 204 } 205 close(jobs) 206 pend.Wait() 207 208 // after all tx has been traced, collect "deletion proof" for zktrie 209 for _, tracer := range env.ZkTrieTracer { 210 delProofs, err := tracer.GetDeletionProofs() 211 if err != nil { 212 log.Error("deletion proof failure", "error", err) 213 } else { 214 for _, proof := range delProofs { 215 env.DeletionProofs = append(env.DeletionProofs, proof) 216 } 217 } 218 } 219 220 // build dummy per-tx deletion proof 221 for _, txStorageTrace := range env.TxStorageTraces { 222 if txStorageTrace != nil { 223 txStorageTrace.DeletionProofs = env.DeletionProofs 224 } 225 } 226 227 // If execution failed in between, abort 228 select { 229 case err := <-errCh: 230 return nil, err 231 default: 232 if failed != nil { 233 return nil, failed 234 } 235 } 236 237 return env.fillBlockTrace(block) 238 } 239 240 func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.Block) error { 241 tx := block.Transactions()[index] 242 msg, _ := tx.AsMessage(env.signer, block.BaseFee()) 243 from, _ := types.Sender(env.signer, tx) 244 to := tx.To() 245 246 txctx := &Context{ 247 BlockHash: block.TxHash(), 248 TxIndex: index, 249 TxHash: tx.Hash(), 250 } 251 252 sender := &types.AccountWrapper{ 253 Address: from, 254 Nonce: state.GetNonce(from), 255 Balance: (*hexutil.Big)(state.GetBalance(from)), 256 KeccakCodeHash: state.GetKeccakCodeHash(from), 257 PoseidonCodeHash: state.GetPoseidonCodeHash(from), 258 CodeSize: state.GetCodeSize(from), 259 } 260 var receiver *types.AccountWrapper 261 if to != nil { 262 receiver = &types.AccountWrapper{ 263 Address: *to, 264 Nonce: state.GetNonce(*to), 265 Balance: (*hexutil.Big)(state.GetBalance(*to)), 266 KeccakCodeHash: state.GetKeccakCodeHash(*to), 267 PoseidonCodeHash: state.GetPoseidonCodeHash(*to), 268 CodeSize: state.GetCodeSize(*to), 269 } 270 } 271 272 tracer := vm.NewStructLogger(env.logConfig) 273 // Run the transaction with tracing enabled. 274 vmenv := vm.NewEVM(env.blockCtx, NewEVMTxContext(msg), state, env.chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) 275 276 // Call Prepare to clear out the statedb access list 277 state.Prepare(txctx.TxHash, txctx.TxIndex) 278 279 // Computes the new state by applying the given message. 280 l1DataFee, err := fees.CalculateL1DataFee(tx, state) 281 if err != nil { 282 return fmt.Errorf("tracing failed: %w", err) 283 } 284 result, err := ApplyMessage(vmenv, msg, new(GasPool).AddGas(msg.Gas()), l1DataFee) 285 if err != nil { 286 return fmt.Errorf("tracing failed: %w", err) 287 } 288 // If the result contains a revert reason, return it. 289 returnVal := result.Return() 290 if len(result.Revert()) > 0 { 291 returnVal = result.Revert() 292 } 293 294 createdAcc := tracer.CreatedAccount() 295 var after []*types.AccountWrapper 296 if to == nil { 297 if createdAcc == nil { 298 return errors.New("unexpected tx: address for created contract unavailable") 299 } 300 to = &createdAcc.Address 301 } 302 // collect affected account after tx being applied 303 for _, acc := range []common.Address{from, *to, env.coinbase} { 304 after = append(after, &types.AccountWrapper{ 305 Address: acc, 306 Nonce: state.GetNonce(acc), 307 Balance: (*hexutil.Big)(state.GetBalance(acc)), 308 KeccakCodeHash: state.GetKeccakCodeHash(acc), 309 PoseidonCodeHash: state.GetPoseidonCodeHash(acc), 310 CodeSize: state.GetCodeSize(acc), 311 }) 312 } 313 314 txStorageTrace := &types.StorageTrace{ 315 Proofs: make(map[string][]hexutil.Bytes), 316 StorageProofs: make(map[string]map[string][]hexutil.Bytes), 317 } 318 // still we have no state root for per tx, only set the head and tail 319 if index == 0 { 320 txStorageTrace.RootBefore = state.GetRootHash() 321 } 322 if index == len(block.Transactions())-1 { 323 txStorageTrace.RootAfter = block.Root() 324 } 325 326 // merge required proof data 327 proofAccounts := tracer.UpdatedAccounts() 328 proofAccounts[vmenv.FeeRecipient()] = struct{}{} 329 for addr := range proofAccounts { 330 addrStr := addr.String() 331 332 env.pMu.Lock() 333 checkedProof, existed := env.Proofs[addrStr] 334 if existed { 335 txStorageTrace.Proofs[addrStr] = checkedProof 336 } 337 env.pMu.Unlock() 338 if existed { 339 continue 340 } 341 proof, err := state.GetProof(addr) 342 if err != nil { 343 log.Error("Proof not available", "address", addrStr, "error", err) 344 // but we still mark the proofs map with nil array 345 } 346 wrappedProof := types.WrapProof(proof) 347 env.pMu.Lock() 348 env.Proofs[addrStr] = wrappedProof 349 txStorageTrace.Proofs[addrStr] = wrappedProof 350 env.pMu.Unlock() 351 } 352 353 proofStorages := tracer.UpdatedStorages() 354 for addr, keys := range proofStorages { 355 if _, existed := txStorageTrace.StorageProofs[addr.String()]; !existed { 356 txStorageTrace.StorageProofs[addr.String()] = make(map[string][]hexutil.Bytes) 357 } 358 359 env.sMu.Lock() 360 trie, err := state.GetStorageTrieForProof(addr) 361 if err != nil { 362 // but we still continue to next address 363 log.Error("Storage trie not available", "error", err, "address", addr) 364 env.sMu.Unlock() 365 continue 366 } 367 zktrieTracer := state.NewProofTracer(trie) 368 env.sMu.Unlock() 369 370 for key, values := range keys { 371 addrStr := addr.String() 372 keyStr := key.String() 373 isDelete := bytes.Equal(values.Bytes(), common.Hash{}.Bytes()) 374 375 txm := txStorageTrace.StorageProofs[addrStr] 376 env.sMu.Lock() 377 m, existed := env.StorageProofs[addrStr] 378 if !existed { 379 m = make(map[string][]hexutil.Bytes) 380 env.StorageProofs[addrStr] = m 381 } 382 if zktrieTracer.Available() && !env.ZkTrieTracer[addrStr].Available() { 383 env.ZkTrieTracer[addrStr] = state.NewProofTracer(trie) 384 } 385 386 if proof, existed := m[keyStr]; existed { 387 txm[keyStr] = proof 388 // still need to touch tracer for deletion 389 if isDelete && zktrieTracer.Available() { 390 env.ZkTrieTracer[addrStr].MarkDeletion(key) 391 } 392 env.sMu.Unlock() 393 continue 394 } 395 env.sMu.Unlock() 396 397 var proof [][]byte 398 var err error 399 if zktrieTracer.Available() { 400 proof, err = state.GetSecureTrieProof(zktrieTracer, key) 401 } else { 402 proof, err = state.GetSecureTrieProof(trie, key) 403 } 404 if err != nil { 405 log.Error("Storage proof not available", "error", err, "address", addrStr, "key", keyStr) 406 // but we still mark the proofs map with nil array 407 } 408 wrappedProof := types.WrapProof(proof) 409 env.sMu.Lock() 410 txm[keyStr] = wrappedProof 411 m[keyStr] = wrappedProof 412 if zktrieTracer.Available() { 413 if isDelete { 414 zktrieTracer.MarkDeletion(key) 415 } 416 env.ZkTrieTracer[addrStr].Merge(zktrieTracer) 417 } 418 env.sMu.Unlock() 419 } 420 } 421 422 env.ExecutionResults[index] = &types.ExecutionResult{ 423 From: sender, 424 To: receiver, 425 AccountCreated: createdAcc, 426 AccountsAfter: after, 427 L1DataFee: (*hexutil.Big)(result.L1DataFee), 428 Gas: result.UsedGas, 429 Failed: result.Failed(), 430 ReturnValue: fmt.Sprintf("%x", returnVal), 431 StructLogs: vm.FormatLogs(tracer.StructLogs()), 432 } 433 env.TxStorageTraces[index] = txStorageTrace 434 435 return nil 436 } 437 438 // fillBlockTrace content after all the txs are finished running. 439 func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, error) { 440 statedb := env.state 441 442 txs := make([]*types.TransactionData, block.Transactions().Len()) 443 for i, tx := range block.Transactions() { 444 txs[i] = types.NewTransactionData(tx, block.NumberU64(), env.chainConfig) 445 } 446 447 intrinsicStorageProofs := map[common.Address][]common.Hash{ 448 rcfg.L2MessageQueueAddress: {rcfg.WithdrawTrieRootSlot}, 449 rcfg.L1GasPriceOracleAddress: { 450 rcfg.L1BaseFeeSlot, 451 rcfg.OverheadSlot, 452 rcfg.ScalarSlot, 453 }, 454 } 455 456 for addr, storages := range intrinsicStorageProofs { 457 if _, existed := env.Proofs[addr.String()]; !existed { 458 if proof, err := statedb.GetProof(addr); err != nil { 459 log.Error("Proof for intrinstic address not available", "error", err, "address", addr) 460 } else { 461 env.Proofs[addr.String()] = types.WrapProof(proof) 462 } 463 } 464 465 if _, existed := env.StorageProofs[addr.String()]; !existed { 466 env.StorageProofs[addr.String()] = make(map[string][]hexutil.Bytes) 467 } 468 469 for _, slot := range storages { 470 if _, existed := env.StorageProofs[addr.String()][slot.String()]; !existed { 471 if trie, err := statedb.GetStorageTrieForProof(addr); err != nil { 472 log.Error("Storage proof for intrinstic address not available", "error", err, "address", addr) 473 } else if proof, _ := statedb.GetSecureTrieProof(trie, slot); err != nil { 474 log.Error("Get storage proof for intrinstic address failed", "error", err, "address", addr, "slot", slot) 475 } else { 476 env.StorageProofs[addr.String()][slot.String()] = types.WrapProof(proof) 477 } 478 } 479 } 480 } 481 482 var chainID uint64 483 if env.chainConfig.ChainID != nil { 484 chainID = env.chainConfig.ChainID.Uint64() 485 } 486 blockTrace := &types.BlockTrace{ 487 ChainID: chainID, 488 Version: params.ArchiveVersion(params.CommitHash), 489 Coinbase: &types.AccountWrapper{ 490 Address: env.coinbase, 491 Nonce: statedb.GetNonce(env.coinbase), 492 Balance: (*hexutil.Big)(statedb.GetBalance(env.coinbase)), 493 KeccakCodeHash: statedb.GetKeccakCodeHash(env.coinbase), 494 PoseidonCodeHash: statedb.GetPoseidonCodeHash(env.coinbase), 495 CodeSize: statedb.GetCodeSize(env.coinbase), 496 }, 497 Header: block.Header(), 498 StorageTrace: env.StorageTrace, 499 ExecutionResults: env.ExecutionResults, 500 TxStorageTraces: env.TxStorageTraces, 501 Transactions: txs, 502 StartL1QueueIndex: env.StartL1QueueIndex, 503 } 504 505 for i, tx := range block.Transactions() { 506 evmTrace := env.ExecutionResults[i] 507 // Contract is created. 508 if tx.To() == nil { 509 evmTrace.ByteCode = hexutil.Encode(tx.Data()) 510 } else { // contract call be included at this case, specially fallback call's data is empty. 511 evmTrace.ByteCode = hexutil.Encode(statedb.GetCode(*tx.To())) 512 // Get tx.to address's code hash. 513 codeHash := statedb.GetPoseidonCodeHash(*tx.To()) 514 evmTrace.PoseidonCodeHash = &codeHash 515 } 516 } 517 518 // only zktrie model has the ability to get `mptwitness`. 519 if env.chainConfig.Scroll.ZktrieEnabled() { 520 // we use MPTWitnessNothing by default and do not allow switch among MPTWitnessType atm. 521 // MPTWitness will be removed from traces in the future. 522 if err := zkproof.FillBlockTraceForMPTWitness(zkproof.MPTWitnessNothing, blockTrace); err != nil { 523 log.Error("fill mpt witness fail", "error", err) 524 } 525 } 526 527 blockTrace.WithdrawTrieRoot = withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, env.state) 528 529 return blockTrace, nil 530 }