github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/core/state/snapshot/snapshot_test.go (about) 1 // Copyright 2019 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 snapshot 18 19 import ( 20 "encoding/binary" 21 "fmt" 22 "math/big" 23 "math/rand" 24 "testing" 25 "time" 26 27 "github.com/VictoriaMetrics/fastcache" 28 29 "github.com/scroll-tech/go-ethereum/common" 30 "github.com/scroll-tech/go-ethereum/core/rawdb" 31 "github.com/scroll-tech/go-ethereum/rlp" 32 ) 33 34 // randomHash generates a random blob of data and returns it as a hash. 35 func randomHash() common.Hash { 36 var hash common.Hash 37 if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { 38 panic(err) 39 } 40 return hash 41 } 42 43 // randomAccount generates a random account and returns it RLP encoded. 44 func randomAccount() []byte { 45 root := randomHash() 46 a := Account{ 47 Balance: big.NewInt(rand.Int63()), 48 Nonce: rand.Uint64(), 49 Root: root[:], 50 KeccakCodeHash: emptyKeccakCode[:], 51 PoseidonCodeHash: emptyPoseidonCode[:], 52 CodeSize: 0, 53 } 54 data, _ := rlp.EncodeToBytes(a) 55 return data 56 } 57 58 // randomAccountSet generates a set of random accounts with the given strings as 59 // the account address hashes. 60 func randomAccountSet(hashes ...string) map[common.Hash][]byte { 61 accounts := make(map[common.Hash][]byte) 62 for _, hash := range hashes { 63 accounts[common.HexToHash(hash)] = randomAccount() 64 } 65 return accounts 66 } 67 68 // randomStorageSet generates a set of random slots with the given strings as 69 // the slot addresses. 70 func randomStorageSet(accounts []string, hashes [][]string, nilStorage [][]string) map[common.Hash]map[common.Hash][]byte { 71 storages := make(map[common.Hash]map[common.Hash][]byte) 72 for index, account := range accounts { 73 storages[common.HexToHash(account)] = make(map[common.Hash][]byte) 74 75 if index < len(hashes) { 76 hashes := hashes[index] 77 for _, hash := range hashes { 78 storages[common.HexToHash(account)][common.HexToHash(hash)] = randomHash().Bytes() 79 } 80 } 81 if index < len(nilStorage) { 82 nils := nilStorage[index] 83 for _, hash := range nils { 84 storages[common.HexToHash(account)][common.HexToHash(hash)] = nil 85 } 86 } 87 } 88 return storages 89 } 90 91 // Tests that if a disk layer becomes stale, no active external references will 92 // be returned with junk data. This version of the test flattens every diff layer 93 // to check internal corner case around the bottom-most memory accumulator. 94 func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { 95 // Create an empty base layer and a snapshot tree out of it 96 base := &diskLayer{ 97 diskdb: rawdb.NewMemoryDatabase(), 98 root: common.HexToHash("0x01"), 99 cache: fastcache.New(1024 * 500), 100 } 101 snaps := &Tree{ 102 layers: map[common.Hash]snapshot{ 103 base.root: base, 104 }, 105 } 106 // Retrieve a reference to the base and commit a diff on top 107 ref := snaps.Snapshot(base.root) 108 109 accounts := map[common.Hash][]byte{ 110 common.HexToHash("0xa1"): randomAccount(), 111 } 112 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 113 t.Fatalf("failed to create a diff layer: %v", err) 114 } 115 if n := len(snaps.layers); n != 2 { 116 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) 117 } 118 // Commit the diff layer onto the disk and ensure it's persisted 119 if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { 120 t.Fatalf("failed to merge diff layer onto disk: %v", err) 121 } 122 // Since the base layer was modified, ensure that data retrieval on the external reference fail 123 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 124 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 125 } 126 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 127 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 128 } 129 if n := len(snaps.layers); n != 1 { 130 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) 131 fmt.Println(snaps.layers) 132 } 133 } 134 135 // Tests that if a disk layer becomes stale, no active external references will 136 // be returned with junk data. This version of the test retains the bottom diff 137 // layer to check the usual mode of operation where the accumulator is retained. 138 func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { 139 // Create an empty base layer and a snapshot tree out of it 140 base := &diskLayer{ 141 diskdb: rawdb.NewMemoryDatabase(), 142 root: common.HexToHash("0x01"), 143 cache: fastcache.New(1024 * 500), 144 } 145 snaps := &Tree{ 146 layers: map[common.Hash]snapshot{ 147 base.root: base, 148 }, 149 } 150 // Retrieve a reference to the base and commit two diffs on top 151 ref := snaps.Snapshot(base.root) 152 153 accounts := map[common.Hash][]byte{ 154 common.HexToHash("0xa1"): randomAccount(), 155 } 156 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 157 t.Fatalf("failed to create a diff layer: %v", err) 158 } 159 if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { 160 t.Fatalf("failed to create a diff layer: %v", err) 161 } 162 if n := len(snaps.layers); n != 3 { 163 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) 164 } 165 // Commit the diff layer onto the disk and ensure it's persisted 166 defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) 167 aggregatorMemoryLimit = 0 168 169 if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { 170 t.Fatalf("failed to merge accumulator onto disk: %v", err) 171 } 172 // Since the base layer was modified, ensure that data retrievald on the external reference fail 173 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 174 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 175 } 176 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 177 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 178 } 179 if n := len(snaps.layers); n != 2 { 180 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) 181 fmt.Println(snaps.layers) 182 } 183 } 184 185 // Tests that if a diff layer becomes stale, no active external references will 186 // be returned with junk data. This version of the test retains the bottom diff 187 // layer to check the usual mode of operation where the accumulator is retained. 188 func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { 189 // Create an empty base layer and a snapshot tree out of it 190 base := &diskLayer{ 191 diskdb: rawdb.NewMemoryDatabase(), 192 root: common.HexToHash("0x01"), 193 cache: fastcache.New(1024 * 500), 194 } 195 snaps := &Tree{ 196 layers: map[common.Hash]snapshot{ 197 base.root: base, 198 }, 199 } 200 // Commit three diffs on top and retrieve a reference to the bottommost 201 accounts := map[common.Hash][]byte{ 202 common.HexToHash("0xa1"): randomAccount(), 203 } 204 if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { 205 t.Fatalf("failed to create a diff layer: %v", err) 206 } 207 if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { 208 t.Fatalf("failed to create a diff layer: %v", err) 209 } 210 if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { 211 t.Fatalf("failed to create a diff layer: %v", err) 212 } 213 if n := len(snaps.layers); n != 4 { 214 t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) 215 } 216 ref := snaps.Snapshot(common.HexToHash("0x02")) 217 218 // Doing a Cap operation with many allowed layers should be a no-op 219 exp := len(snaps.layers) 220 if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { 221 t.Fatalf("failed to flatten diff layer into accumulator: %v", err) 222 } 223 if got := len(snaps.layers); got != exp { 224 t.Errorf("layers modified, got %d exp %d", got, exp) 225 } 226 // Flatten the diff layer into the bottom accumulator 227 if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { 228 t.Fatalf("failed to flatten diff layer into accumulator: %v", err) 229 } 230 // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail 231 if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { 232 t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) 233 } 234 if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { 235 t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) 236 } 237 if n := len(snaps.layers); n != 3 { 238 t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) 239 fmt.Println(snaps.layers) 240 } 241 } 242 243 // TestPostCapBasicDataAccess tests some functionality regarding capping/flattening. 244 func TestPostCapBasicDataAccess(t *testing.T) { 245 // setAccount is a helper to construct a random account entry and assign it to 246 // an account slot in a snapshot 247 setAccount := func(accKey string) map[common.Hash][]byte { 248 return map[common.Hash][]byte{ 249 common.HexToHash(accKey): randomAccount(), 250 } 251 } 252 // Create a starting base layer and a snapshot tree out of it 253 base := &diskLayer{ 254 diskdb: rawdb.NewMemoryDatabase(), 255 root: common.HexToHash("0x01"), 256 cache: fastcache.New(1024 * 500), 257 } 258 snaps := &Tree{ 259 layers: map[common.Hash]snapshot{ 260 base.root: base, 261 }, 262 } 263 // The lowest difflayer 264 snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) 265 snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) 266 snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) 267 268 snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) 269 snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) 270 271 // checkExist verifies if an account exiss in a snapshot 272 checkExist := func(layer *diffLayer, key string) error { 273 if data, _ := layer.Account(common.HexToHash(key)); data == nil { 274 return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) 275 } 276 return nil 277 } 278 // shouldErr checks that an account access errors as expected 279 shouldErr := func(layer *diffLayer, key string) error { 280 if data, err := layer.Account(common.HexToHash(key)); err == nil { 281 return fmt.Errorf("expected error, got data %x", data) 282 } 283 return nil 284 } 285 // check basics 286 snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer) 287 288 if err := checkExist(snap, "0xa1"); err != nil { 289 t.Error(err) 290 } 291 if err := checkExist(snap, "0xb2"); err != nil { 292 t.Error(err) 293 } 294 if err := checkExist(snap, "0xb3"); err != nil { 295 t.Error(err) 296 } 297 // Cap to a bad root should fail 298 if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil { 299 t.Errorf("expected error, got none") 300 } 301 // Now, merge the a-chain 302 snaps.Cap(common.HexToHash("0xa3"), 0) 303 304 // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is 305 // the parent of b2, b2 should no longer be able to iterate into parent. 306 307 // These should still be accessible 308 if err := checkExist(snap, "0xb2"); err != nil { 309 t.Error(err) 310 } 311 if err := checkExist(snap, "0xb3"); err != nil { 312 t.Error(err) 313 } 314 // But these would need iteration into the modified parent 315 if err := shouldErr(snap, "0xa1"); err != nil { 316 t.Error(err) 317 } 318 if err := shouldErr(snap, "0xa2"); err != nil { 319 t.Error(err) 320 } 321 if err := shouldErr(snap, "0xa3"); err != nil { 322 t.Error(err) 323 } 324 // Now, merge it again, just for fun. It should now error, since a3 325 // is a disk layer 326 if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil { 327 t.Error("expected error capping the disk layer, got none") 328 } 329 } 330 331 // TestSnaphots tests the functionality for retrieving the snapshot 332 // with given head root and the desired depth. 333 func TestSnaphots(t *testing.T) { 334 // setAccount is a helper to construct a random account entry and assign it to 335 // an account slot in a snapshot 336 setAccount := func(accKey string) map[common.Hash][]byte { 337 return map[common.Hash][]byte{ 338 common.HexToHash(accKey): randomAccount(), 339 } 340 } 341 makeRoot := func(height uint64) common.Hash { 342 var buffer [8]byte 343 binary.BigEndian.PutUint64(buffer[:], height) 344 return common.BytesToHash(buffer[:]) 345 } 346 // Create a starting base layer and a snapshot tree out of it 347 base := &diskLayer{ 348 diskdb: rawdb.NewMemoryDatabase(), 349 root: makeRoot(1), 350 cache: fastcache.New(1024 * 500), 351 } 352 snaps := &Tree{ 353 layers: map[common.Hash]snapshot{ 354 base.root: base, 355 }, 356 } 357 // Construct the snapshots with 129 layers, flattening whatever's above that 358 var ( 359 last = common.HexToHash("0x01") 360 head common.Hash 361 ) 362 for i := 0; i < 129; i++ { 363 head = makeRoot(uint64(i + 2)) 364 snaps.Update(head, last, nil, setAccount(fmt.Sprintf("%d", i+2)), nil) 365 last = head 366 snaps.Cap(head, 128) // 130 layers (128 diffs + 1 accumulator + 1 disk) 367 } 368 var cases = []struct { 369 headRoot common.Hash 370 limit int 371 nodisk bool 372 expected int 373 expectBottom common.Hash 374 }{ 375 {head, 0, false, 0, common.Hash{}}, 376 {head, 64, false, 64, makeRoot(129 + 2 - 64)}, 377 {head, 128, false, 128, makeRoot(3)}, // Normal diff layers, no accumulator 378 {head, 129, true, 129, makeRoot(2)}, // All diff layers, including accumulator 379 {head, 130, false, 130, makeRoot(1)}, // All diff layers + disk layer 380 } 381 for i, c := range cases { 382 layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) 383 if len(layers) != c.expected { 384 t.Errorf("non-overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) 385 } 386 if len(layers) == 0 { 387 continue 388 } 389 bottommost := layers[len(layers)-1] 390 if bottommost.Root() != c.expectBottom { 391 t.Errorf("non-overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) 392 } 393 } 394 // Above we've tested the normal capping, which leaves the accumulator live. 395 // Test that if the bottommost accumulator diff layer overflows the allowed 396 // memory limit, the snapshot tree gets capped to one less layer. 397 // Commit the diff layer onto the disk and ensure it's persisted 398 defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) 399 aggregatorMemoryLimit = 0 400 401 snaps.Cap(head, 128) // 129 (128 diffs + 1 overflown accumulator + 1 disk) 402 403 cases = []struct { 404 headRoot common.Hash 405 limit int 406 nodisk bool 407 expected int 408 expectBottom common.Hash 409 }{ 410 {head, 0, false, 0, common.Hash{}}, 411 {head, 64, false, 64, makeRoot(129 + 2 - 64)}, 412 {head, 128, false, 128, makeRoot(3)}, // All diff layers, accumulator was flattened 413 {head, 129, true, 128, makeRoot(3)}, // All diff layers, accumulator was flattened 414 {head, 130, false, 129, makeRoot(2)}, // All diff layers + disk layer 415 } 416 for i, c := range cases { 417 layers := snaps.Snapshots(c.headRoot, c.limit, c.nodisk) 418 if len(layers) != c.expected { 419 t.Errorf("overflow test %d: returned snapshot layers are mismatched, want %v, got %v", i, c.expected, len(layers)) 420 } 421 if len(layers) == 0 { 422 continue 423 } 424 bottommost := layers[len(layers)-1] 425 if bottommost.Root() != c.expectBottom { 426 t.Errorf("overflow test %d: snapshot mismatch, want %v, get %v", i, c.expectBottom, bottommost.Root()) 427 } 428 } 429 } 430 431 // TestReadStateDuringFlattening tests the scenario that, during the 432 // bottom diff layers are merging which tags these as stale, the read 433 // happens via a pre-created top snapshot layer which tries to access 434 // the state in these stale layers. Ensure this read can retrieve the 435 // right state back(block until the flattening is finished) instead of 436 // an unexpected error(snapshot layer is stale). 437 func TestReadStateDuringFlattening(t *testing.T) { 438 // setAccount is a helper to construct a random account entry and assign it to 439 // an account slot in a snapshot 440 setAccount := func(accKey string) map[common.Hash][]byte { 441 return map[common.Hash][]byte{ 442 common.HexToHash(accKey): randomAccount(), 443 } 444 } 445 // Create a starting base layer and a snapshot tree out of it 446 base := &diskLayer{ 447 diskdb: rawdb.NewMemoryDatabase(), 448 root: common.HexToHash("0x01"), 449 cache: fastcache.New(1024 * 500), 450 } 451 snaps := &Tree{ 452 layers: map[common.Hash]snapshot{ 453 base.root: base, 454 }, 455 } 456 // 4 layers in total, 3 diff layers and 1 disk layers 457 snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) 458 snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) 459 snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) 460 461 // Obtain the topmost snapshot handler for state accessing 462 snap := snaps.Snapshot(common.HexToHash("0xa3")) 463 464 // Register the testing hook to access the state after flattening 465 var result = make(chan *Account) 466 snaps.onFlatten = func() { 467 // Spin up a thread to read the account from the pre-created 468 // snapshot handler. It's expected to be blocked. 469 go func() { 470 account, _ := snap.Account(common.HexToHash("0xa1")) 471 result <- account 472 }() 473 select { 474 case res := <-result: 475 t.Fatalf("Unexpected return %v", res) 476 case <-time.NewTimer(time.Millisecond * 300).C: 477 } 478 } 479 // Cap the snap tree, which will mark the bottom-most layer as stale. 480 snaps.Cap(common.HexToHash("0xa3"), 1) 481 select { 482 case account := <-result: 483 if account == nil { 484 t.Fatal("Failed to retrieve account") 485 } 486 case <-time.NewTimer(time.Millisecond * 300).C: 487 t.Fatal("Unexpected blocker") 488 } 489 }