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