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