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