github.com/ethereum/go-ethereum@v1.16.1/core/tracing/journal_test.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 "errors" 21 "math/big" 22 "reflect" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/common" 26 ) 27 28 type testTracer struct { 29 t *testing.T 30 bal *big.Int 31 nonce uint64 32 code []byte 33 storage map[common.Hash]common.Hash 34 } 35 36 func (t *testTracer) OnBalanceChange(addr common.Address, prev *big.Int, new *big.Int, reason BalanceChangeReason) { 37 t.t.Logf("OnBalanceChange(%v, %v -> %v, %v)", addr, prev, new, reason) 38 if t.bal != nil && t.bal.Cmp(prev) != 0 { 39 t.t.Errorf(" !! wrong prev balance (expected %v)", t.bal) 40 } 41 t.bal = new 42 } 43 44 func (t *testTracer) OnNonceChange(addr common.Address, prev uint64, new uint64) { 45 t.t.Logf("OnNonceChange(%v, %v -> %v)", addr, prev, new) 46 t.nonce = new 47 } 48 49 func (t *testTracer) OnNonceChangeV2(addr common.Address, prev uint64, new uint64, reason NonceChangeReason) { 50 t.t.Logf("OnNonceChangeV2(%v, %v -> %v, %v)", addr, prev, new, reason) 51 t.nonce = new 52 } 53 54 func (t *testTracer) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { 55 t.t.Logf("OnCodeChange(%v, %v -> %v)", addr, prevCodeHash, codeHash) 56 t.code = code 57 } 58 59 func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) { 60 t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new) 61 if t.storage == nil { 62 t.storage = make(map[common.Hash]common.Hash) 63 } 64 if new == (common.Hash{}) { 65 delete(t.storage, slot) 66 } else { 67 t.storage[slot] = new 68 } 69 } 70 71 func TestJournalIntegration(t *testing.T) { 72 tr := &testTracer{t: t} 73 wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange, OnCodeChange: tr.OnCodeChange, OnStorageChange: tr.OnStorageChange}) 74 if err != nil { 75 t.Fatalf("failed to wrap test tracer: %v", err) 76 } 77 78 addr := common.HexToAddress("0x1234") 79 { 80 wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) 81 wr.OnBalanceChange(addr, nil, big.NewInt(100), BalanceChangeUnspecified) 82 wr.OnCodeChange(addr, common.Hash{}, nil, common.Hash{}, []byte{1, 2, 3}) 83 wr.OnStorageChange(addr, common.Hash{1}, common.Hash{}, common.Hash{2}) 84 { 85 wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) 86 wr.OnNonceChangeV2(addr, 0, 1, NonceChangeUnspecified) 87 wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) 88 wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) 89 wr.OnStorageChange(addr, common.Hash{1}, common.Hash{2}, common.Hash{3}) 90 wr.OnStorageChange(addr, common.Hash{2}, common.Hash{}, common.Hash{4}) 91 wr.OnExit(1, nil, 100, errors.New("revert"), true) 92 } 93 wr.OnExit(0, nil, 150, nil, false) 94 } 95 96 if tr.bal.Cmp(big.NewInt(100)) != 0 { 97 t.Fatalf("unexpected balance: %v", tr.bal) 98 } 99 if tr.nonce != 0 { 100 t.Fatalf("unexpected nonce: %v", tr.nonce) 101 } 102 if len(tr.code) != 3 { 103 t.Fatalf("unexpected code: %v", tr.code) 104 } 105 if len(tr.storage) != 1 { 106 t.Fatalf("unexpected storage len. want %d, have %d", 1, len(tr.storage)) 107 } 108 if tr.storage[common.Hash{1}] != (common.Hash{2}) { 109 t.Fatalf("unexpected storage. want %v, have %v", common.Hash{2}, tr.storage[common.Hash{1}]) 110 } 111 } 112 113 func TestJournalTopRevert(t *testing.T) { 114 tr := &testTracer{t: t} 115 wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange}) 116 if err != nil { 117 t.Fatalf("failed to wrap test tracer: %v", err) 118 } 119 120 addr := common.HexToAddress("0x1234") 121 { 122 wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) 123 wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), BalanceChangeUnspecified) 124 { 125 wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) 126 wr.OnNonceChangeV2(addr, 0, 1, NonceChangeUnspecified) 127 wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) 128 wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(250), BalanceChangeUnspecified) 129 wr.OnExit(1, nil, 100, errors.New("revert"), true) 130 } 131 wr.OnExit(0, nil, 150, errors.New("revert"), true) 132 } 133 134 if tr.bal.Cmp(big.NewInt(0)) != 0 { 135 t.Fatalf("unexpected balance: %v", tr.bal) 136 } 137 if tr.nonce != 0 { 138 t.Fatalf("unexpected nonce: %v", tr.nonce) 139 } 140 } 141 142 // This test checks that changes in nested calls are reverted properly. 143 func TestJournalNestedCalls(t *testing.T) { 144 tr := &testTracer{t: t} 145 wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange}) 146 if err != nil { 147 t.Fatalf("failed to wrap test tracer: %v", err) 148 } 149 150 addr := common.HexToAddress("0x1234") 151 { 152 wr.OnEnter(0, 0, addr, addr, nil, 1000, big.NewInt(0)) 153 wr.OnBalanceChange(addr, big.NewInt(0), big.NewInt(100), BalanceChangeUnspecified) 154 { 155 wr.OnEnter(1, 0, addr, addr, nil, 1000, big.NewInt(0)) 156 wr.OnBalanceChange(addr, big.NewInt(100), big.NewInt(200), BalanceChangeUnspecified) 157 { 158 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 159 wr.OnExit(2, nil, 100, nil, false) 160 } 161 { 162 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 163 wr.OnBalanceChange(addr, big.NewInt(200), big.NewInt(300), BalanceChangeUnspecified) 164 wr.OnExit(2, nil, 100, nil, false) 165 } 166 { 167 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 168 wr.OnExit(2, nil, 100, nil, false) 169 } 170 wr.OnBalanceChange(addr, big.NewInt(300), big.NewInt(400), BalanceChangeUnspecified) 171 { 172 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 173 wr.OnBalanceChange(addr, big.NewInt(400), big.NewInt(500), BalanceChangeUnspecified) 174 wr.OnExit(2, nil, 100, errors.New("revert"), true) 175 } 176 { 177 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 178 wr.OnExit(2, nil, 100, errors.New("revert"), true) 179 } 180 { 181 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 182 wr.OnBalanceChange(addr, big.NewInt(400), big.NewInt(600), BalanceChangeUnspecified) 183 wr.OnExit(2, nil, 100, nil, false) 184 } 185 wr.OnExit(1, nil, 100, errors.New("revert"), true) 186 } 187 wr.OnExit(0, nil, 150, nil, false) 188 } 189 190 if tr.bal.Uint64() != 100 { 191 t.Fatalf("unexpected balance: %v", tr.bal) 192 } 193 } 194 195 func TestNonceIncOnCreate(t *testing.T) { 196 const opCREATE = 0xf0 197 198 tr := &testTracer{t: t} 199 wr, err := WrapWithJournal(&Hooks{OnNonceChange: tr.OnNonceChange}) 200 if err != nil { 201 t.Fatalf("failed to wrap test tracer: %v", err) 202 } 203 204 addr := common.HexToAddress("0x1234") 205 { 206 wr.OnEnter(0, opCREATE, addr, addr, nil, 1000, big.NewInt(0)) 207 wr.OnNonceChangeV2(addr, 0, 1, NonceChangeContractCreator) 208 wr.OnExit(0, nil, 100, errors.New("revert"), true) 209 } 210 211 if tr.nonce != 1 { 212 t.Fatalf("unexpected nonce: %v", tr.nonce) 213 } 214 } 215 216 func TestOnNonceChangeV2(t *testing.T) { 217 tr := &testTracer{t: t} 218 wr, err := WrapWithJournal(&Hooks{OnNonceChangeV2: tr.OnNonceChangeV2}) 219 if err != nil { 220 t.Fatalf("failed to wrap test tracer: %v", err) 221 } 222 223 addr := common.HexToAddress("0x1234") 224 { 225 wr.OnEnter(2, 0, addr, addr, nil, 1000, big.NewInt(0)) 226 wr.OnNonceChangeV2(addr, 0, 1, NonceChangeEoACall) 227 wr.OnExit(2, nil, 100, nil, true) 228 } 229 230 if tr.nonce != 0 { 231 t.Fatalf("unexpected nonce: %v", tr.nonce) 232 } 233 } 234 235 func TestAllHooksCalled(t *testing.T) { 236 tracer := newTracerAllHooks() 237 hooks := tracer.hooks() 238 239 wrapped, err := WrapWithJournal(hooks) 240 if err != nil { 241 t.Fatalf("failed to wrap hooks with journal: %v", err) 242 } 243 244 // Get the underlying value of the wrapped hooks 245 wrappedValue := reflect.ValueOf(wrapped).Elem() 246 wrappedType := wrappedValue.Type() 247 248 // Iterate over all fields of the wrapped hooks 249 for i := 0; i < wrappedType.NumField(); i++ { 250 field := wrappedType.Field(i) 251 252 // Skip fields that are not function types 253 if field.Type.Kind() != reflect.Func { 254 continue 255 } 256 // Skip non-hooks, i.e. Copy 257 if field.Name == "copy" { 258 continue 259 } 260 // Skip if field is not set 261 if wrappedValue.Field(i).IsNil() { 262 continue 263 } 264 265 // Get the method 266 method := wrappedValue.Field(i) 267 268 // Call the method with zero values 269 params := make([]reflect.Value, method.Type().NumIn()) 270 for j := 0; j < method.Type().NumIn(); j++ { 271 params[j] = reflect.Zero(method.Type().In(j)) 272 } 273 method.Call(params) 274 } 275 276 // Check if all hooks were called 277 if tracer.numCalled() != tracer.hooksCount() { 278 t.Errorf("Not all hooks were called. Expected %d, got %d", tracer.hooksCount(), tracer.numCalled()) 279 } 280 281 for hookName, called := range tracer.hooksCalled { 282 if !called { 283 t.Errorf("Hook %s was not called", hookName) 284 } 285 } 286 } 287 288 type tracerAllHooks struct { 289 hooksCalled map[string]bool 290 } 291 292 func newTracerAllHooks() *tracerAllHooks { 293 t := &tracerAllHooks{hooksCalled: make(map[string]bool)} 294 // Initialize all hooks to false. We will use this to 295 // get total count of hooks. 296 hooksType := reflect.TypeOf((*Hooks)(nil)).Elem() 297 for i := 0; i < hooksType.NumField(); i++ { 298 t.hooksCalled[hooksType.Field(i).Name] = false 299 } 300 delete(t.hooksCalled, "OnNonceChange") 301 return t 302 } 303 304 func (t *tracerAllHooks) hooksCount() int { 305 return len(t.hooksCalled) 306 } 307 308 func (t *tracerAllHooks) numCalled() int { 309 count := 0 310 for _, called := range t.hooksCalled { 311 if called { 312 count++ 313 } 314 } 315 return count 316 } 317 318 func (t *tracerAllHooks) hooks() *Hooks { 319 h := &Hooks{} 320 // Create a function for each hook that sets the 321 // corresponding hooksCalled field to true. 322 hooksValue := reflect.ValueOf(h).Elem() 323 for i := 0; i < hooksValue.NumField(); i++ { 324 field := hooksValue.Type().Field(i) 325 if field.Name == "OnNonceChange" { 326 continue 327 } 328 hookMethod := reflect.MakeFunc(field.Type, func(args []reflect.Value) []reflect.Value { 329 t.hooksCalled[field.Name] = true 330 return nil 331 }) 332 hooksValue.Field(i).Set(hookMethod) 333 } 334 return h 335 }