github.com/ethereum/go-ethereum@v1.16.1/core/tracing/journal.go (about) 1 // Copyright 2025 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 tracing 18 19 import ( 20 "fmt" 21 "math/big" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/core/types" 25 ) 26 27 // journal is a state change journal to be wrapped around a tracer. 28 // It will emit the state change hooks with reverse values when a call reverts. 29 type journal struct { 30 hooks *Hooks 31 entries []entry 32 revisions []int 33 } 34 35 type entry interface { 36 revert(tracer *Hooks) 37 } 38 39 // WrapWithJournal wraps the given tracer with a journaling layer. 40 func WrapWithJournal(hooks *Hooks) (*Hooks, error) { 41 if hooks == nil { 42 return nil, fmt.Errorf("wrapping nil tracer") 43 } 44 // No state change to journal, return the wrapped hooks as is 45 if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil { 46 return hooks, nil 47 } 48 if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil { 49 return nil, fmt.Errorf("cannot have both OnNonceChange and OnNonceChangeV2") 50 } 51 52 // Create a new Hooks instance and copy all hooks 53 wrapped := *hooks 54 55 // Create journal 56 j := &journal{hooks: hooks} 57 // Scope hooks need to be re-implemented. 58 wrapped.OnTxEnd = j.OnTxEnd 59 wrapped.OnEnter = j.OnEnter 60 wrapped.OnExit = j.OnExit 61 // Wrap state change hooks. 62 if hooks.OnBalanceChange != nil { 63 wrapped.OnBalanceChange = j.OnBalanceChange 64 } 65 if hooks.OnNonceChange != nil || hooks.OnNonceChangeV2 != nil { 66 // Regardless of which hook version is used in the tracer, 67 // the journal will want to capture the nonce change reason. 68 wrapped.OnNonceChangeV2 = j.OnNonceChangeV2 69 // A precaution to ensure EVM doesn't call both hooks. 70 wrapped.OnNonceChange = nil 71 } 72 if hooks.OnCodeChange != nil { 73 wrapped.OnCodeChange = j.OnCodeChange 74 } 75 if hooks.OnStorageChange != nil { 76 wrapped.OnStorageChange = j.OnStorageChange 77 } 78 79 return &wrapped, nil 80 } 81 82 // reset clears the journal, after this operation the journal can be used anew. 83 // It is semantically similar to calling 'NewJournal', but the underlying slices 84 // can be reused. 85 func (j *journal) reset() { 86 j.entries = j.entries[:0] 87 j.revisions = j.revisions[:0] 88 } 89 90 // snapshot records a revision and stores it to the revision stack. 91 func (j *journal) snapshot() { 92 rev := len(j.entries) 93 j.revisions = append(j.revisions, rev) 94 } 95 96 // revert reverts all state changes up to the last tracked revision. 97 func (j *journal) revert(hooks *Hooks) { 98 // Replay the journal entries above the last revision to undo changes, 99 // then remove the reverted changes from the journal. 100 rev := j.revisions[len(j.revisions)-1] 101 for i := len(j.entries) - 1; i >= rev; i-- { 102 j.entries[i].revert(hooks) 103 } 104 j.entries = j.entries[:rev] 105 j.popRevision() 106 } 107 108 // popRevision removes an item from the revision stack. This basically forgets about 109 // the last call to snapshot() and moves to the one prior. 110 func (j *journal) popRevision() { 111 j.revisions = j.revisions[:len(j.revisions)-1] 112 } 113 114 // OnTxEnd resets the journal since each transaction has its own EVM call stack. 115 func (j *journal) OnTxEnd(receipt *types.Receipt, err error) { 116 j.reset() 117 if j.hooks.OnTxEnd != nil { 118 j.hooks.OnTxEnd(receipt, err) 119 } 120 } 121 122 // OnEnter is invoked for each EVM call frame and records a journal revision. 123 func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 124 j.snapshot() 125 if j.hooks.OnEnter != nil { 126 j.hooks.OnEnter(depth, typ, from, to, input, gas, value) 127 } 128 } 129 130 // OnExit is invoked when an EVM call frame ends. 131 // If the call has reverted, all state changes made by that frame are undone. 132 // If the call did not revert, we forget about changes in that revision. 133 func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 134 if reverted { 135 j.revert(j.hooks) 136 } else { 137 j.popRevision() 138 } 139 if j.hooks.OnExit != nil { 140 j.hooks.OnExit(depth, output, gasUsed, err, reverted) 141 } 142 } 143 144 func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) { 145 j.entries = append(j.entries, balanceChange{addr: addr, prev: prev, new: new}) 146 if j.hooks.OnBalanceChange != nil { 147 j.hooks.OnBalanceChange(addr, prev, new, reason) 148 } 149 } 150 151 func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) { 152 // When a contract is created, the nonce of the creator is incremented. 153 // This change is not reverted when the creation fails. 154 if reason != NonceChangeContractCreator { 155 j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) 156 } 157 if j.hooks.OnNonceChangeV2 != nil { 158 j.hooks.OnNonceChangeV2(addr, prev, new, reason) 159 } else if j.hooks.OnNonceChange != nil { 160 j.hooks.OnNonceChange(addr, prev, new) 161 } 162 } 163 164 func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { 165 j.entries = append(j.entries, codeChange{ 166 addr: addr, 167 prevCodeHash: prevCodeHash, 168 prevCode: prevCode, 169 newCodeHash: codeHash, 170 newCode: code, 171 }) 172 if j.hooks.OnCodeChange != nil { 173 j.hooks.OnCodeChange(addr, prevCodeHash, prevCode, codeHash, code) 174 } 175 } 176 177 func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) { 178 j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new}) 179 if j.hooks.OnStorageChange != nil { 180 j.hooks.OnStorageChange(addr, slot, prev, new) 181 } 182 } 183 184 type ( 185 balanceChange struct { 186 addr common.Address 187 prev *big.Int 188 new *big.Int 189 } 190 191 nonceChange struct { 192 addr common.Address 193 prev uint64 194 new uint64 195 } 196 197 codeChange struct { 198 addr common.Address 199 prevCodeHash common.Hash 200 prevCode []byte 201 newCodeHash common.Hash 202 newCode []byte 203 } 204 205 storageChange struct { 206 addr common.Address 207 slot common.Hash 208 prev common.Hash 209 new common.Hash 210 } 211 ) 212 213 func (b balanceChange) revert(hooks *Hooks) { 214 if hooks.OnBalanceChange != nil { 215 hooks.OnBalanceChange(b.addr, b.new, b.prev, BalanceChangeRevert) 216 } 217 } 218 219 func (n nonceChange) revert(hooks *Hooks) { 220 if hooks.OnNonceChangeV2 != nil { 221 hooks.OnNonceChangeV2(n.addr, n.new, n.prev, NonceChangeRevert) 222 } else if hooks.OnNonceChange != nil { 223 hooks.OnNonceChange(n.addr, n.new, n.prev) 224 } 225 } 226 227 func (c codeChange) revert(hooks *Hooks) { 228 if hooks.OnCodeChange != nil { 229 hooks.OnCodeChange(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode) 230 } 231 } 232 233 func (s storageChange) revert(hooks *Hooks) { 234 if hooks.OnStorageChange != nil { 235 hooks.OnStorageChange(s.addr, s.slot, s.new, s.prev) 236 } 237 }