github.com/MetalBlockchain/subnet-evm@v0.4.9/core/blockchain_snapshot_test.go (about) 1 // (c) 2019-2021, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2020 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 // Tests that abnormal program termination (i.e.crash) and restart can recovery 28 // the snapshot properly if the snapshot is enabled. 29 30 package core 31 32 import ( 33 "bytes" 34 "fmt" 35 "math/big" 36 "os" 37 "strings" 38 "testing" 39 40 "github.com/MetalBlockchain/subnet-evm/consensus" 41 "github.com/MetalBlockchain/subnet-evm/consensus/dummy" 42 "github.com/MetalBlockchain/subnet-evm/core/rawdb" 43 "github.com/MetalBlockchain/subnet-evm/core/types" 44 "github.com/MetalBlockchain/subnet-evm/core/vm" 45 "github.com/MetalBlockchain/subnet-evm/ethdb" 46 "github.com/MetalBlockchain/subnet-evm/params" 47 "github.com/ethereum/go-ethereum/common" 48 ) 49 50 // snapshotTestBasic wraps the common testing fields in the snapshot tests. 51 type snapshotTestBasic struct { 52 chainBlocks int // Number of blocks to generate for the canonical chain 53 snapshotBlock uint64 // Block number of the relevant snapshot disk layer 54 55 expCanonicalBlocks int // Number of canonical blocks expected to remain in the database (excl. genesis) 56 expHeadBlock uint64 // Block number of the expected head full block 57 expSnapshotBottom uint64 // The block height corresponding to the snapshot disk layer 58 59 // share fields, set in runtime 60 datadir string 61 db ethdb.Database 62 gendb ethdb.Database 63 engine consensus.Engine 64 65 lastAcceptedHash common.Hash 66 } 67 68 func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { 69 // Create a temporary persistent database 70 datadir := t.TempDir() 71 72 db, err := rawdb.NewLevelDBDatabase(datadir, 0, 0, "", false) 73 if err != nil { 74 t.Fatalf("Failed to create persistent database: %v", err) 75 } 76 // Initialize a fresh chain 77 var ( 78 genesis = (&Genesis{Config: params.TestChainConfig, BaseFee: big.NewInt(params.TestInitialBaseFee)}).MustCommit(db) 79 engine = dummy.NewFullFaker() 80 gendb = rawdb.NewMemoryDatabase() 81 82 // Snapshot is enabled, the first snapshot is created from the Genesis. 83 // The snapshot memory allowance is 256MB, it means no snapshot flush 84 // will happen during the block insertion. 85 cacheConfig = DefaultCacheConfig 86 ) 87 chain, err := NewBlockChain(db, cacheConfig, params.TestChainConfig, engine, vm.Config{}, common.Hash{}) 88 if err != nil { 89 t.Fatalf("Failed to create chain: %v", err) 90 } 91 blocks, _, _ := GenerateChain(params.TestChainConfig, genesis, engine, gendb, basic.chainBlocks, 10, func(i int, b *BlockGen) {}) 92 93 // genesis as last accepted 94 basic.lastAcceptedHash = chain.GetBlockByNumber(0).Hash() 95 96 // Insert the blocks with configured settings. 97 var breakpoints []uint64 98 breakpoints = append(breakpoints, basic.snapshotBlock) 99 var startPoint uint64 100 for _, point := range breakpoints { 101 if _, err := chain.InsertChain(blocks[startPoint:point]); err != nil { 102 t.Fatalf("Failed to import canonical chain start: %v", err) 103 } 104 startPoint = point 105 106 if basic.snapshotBlock > 0 && basic.snapshotBlock == point { 107 // Flushing from 0 to snapshotBlock into the disk 108 for i := uint64(0); i < point; i++ { 109 if err := chain.Accept(blocks[i]); err != nil { 110 t.Fatalf("Failed to accept block %v: %v", i, err) 111 } 112 basic.lastAcceptedHash = blocks[i].Hash() 113 } 114 chain.DrainAcceptorQueue() 115 116 diskRoot, blockRoot := chain.snaps.DiskRoot(), blocks[point-1].Root() 117 if !bytes.Equal(diskRoot.Bytes(), blockRoot.Bytes()) { 118 t.Fatalf("Failed to flush disk layer change, want %x, got %x", blockRoot, diskRoot) 119 } 120 } 121 } 122 if _, err := chain.InsertChain(blocks[startPoint:]); err != nil { 123 t.Fatalf("Failed to import canonical chain tail: %v", err) 124 } 125 126 // Set runtime fields 127 basic.datadir = datadir 128 basic.db = db 129 basic.gendb = gendb 130 basic.engine = engine 131 return chain, blocks 132 } 133 134 func (basic *snapshotTestBasic) verify(t *testing.T, chain *BlockChain, blocks []*types.Block) { 135 // Iterate over all the remaining blocks and ensure there are no gaps 136 verifyNoGaps(t, chain, true, blocks) 137 verifyCutoff(t, chain, true, blocks, basic.expCanonicalBlocks) 138 139 if head := chain.CurrentHeader(); head.Number.Uint64() != basic.expHeadBlock { 140 t.Errorf("Head header mismatch: have %d, want %d", head.Number, basic.expHeadBlock) 141 } 142 if head := chain.CurrentBlock(); head.NumberU64() != basic.expHeadBlock { 143 t.Errorf("Head block mismatch: have %d, want %d", head.NumberU64(), basic.expHeadBlock) 144 } 145 146 // Check the disk layer, ensure they are matched 147 block := chain.GetBlockByNumber(basic.expSnapshotBottom) 148 if block == nil { 149 t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) 150 } else if !bytes.Equal(chain.snaps.DiskRoot().Bytes(), block.Root().Bytes()) { 151 t.Errorf("The snapshot disk layer root is incorrect, want %x, get %x", block.Root(), chain.snaps.DiskRoot()) 152 } else if len(chain.snaps.Snapshots(block.Hash(), -1, false)) != 1 { 153 t.Errorf("The correspnding block[%d] of snapshot disk layer is missing", basic.expSnapshotBottom) 154 } 155 156 // Check the snapshot, ensure it's integrated 157 if err := chain.snaps.Verify(block.Root()); err != nil { 158 t.Errorf("The disk layer is not integrated %v", err) 159 } 160 } 161 162 //nolint:unused 163 func (basic *snapshotTestBasic) dump() string { 164 buffer := new(strings.Builder) 165 166 fmt.Fprint(buffer, "Chain:\n G") 167 for i := 0; i < basic.chainBlocks; i++ { 168 fmt.Fprintf(buffer, "->C%d", i+1) 169 } 170 fmt.Fprint(buffer, " (HEAD)\n\n") 171 172 fmt.Fprintf(buffer, "Snapshot: G") 173 if basic.snapshotBlock > 0 { 174 fmt.Fprintf(buffer, ", C%d", basic.snapshotBlock) 175 } 176 fmt.Fprint(buffer, "\n") 177 178 //if crash { 179 // fmt.Fprintf(buffer, "\nCRASH\n\n") 180 //} else { 181 // fmt.Fprintf(buffer, "\nSetHead(%d)\n\n", basic.setHead) 182 //} 183 fmt.Fprintf(buffer, "------------------------------\n\n") 184 185 fmt.Fprint(buffer, "Expected in leveldb:\n G") 186 for i := 0; i < basic.expCanonicalBlocks; i++ { 187 fmt.Fprintf(buffer, "->C%d", i+1) 188 } 189 fmt.Fprintf(buffer, "\n\n") 190 fmt.Fprintf(buffer, "Expected head header : C%d\n", basic.expHeadBlock) 191 if basic.expHeadBlock == 0 { 192 fmt.Fprintf(buffer, "Expected head block : G\n") 193 } else { 194 fmt.Fprintf(buffer, "Expected head block : C%d\n", basic.expHeadBlock) 195 } 196 if basic.expSnapshotBottom == 0 { 197 fmt.Fprintf(buffer, "Expected snapshot disk : G\n") 198 } else { 199 fmt.Fprintf(buffer, "Expected snapshot disk : C%d\n", basic.expSnapshotBottom) 200 } 201 return buffer.String() 202 } 203 204 func (basic *snapshotTestBasic) teardown() { 205 basic.db.Close() 206 basic.gendb.Close() 207 os.RemoveAll(basic.datadir) 208 } 209 210 // snapshotTest is a test case type for normal snapshot recovery. 211 // It can be used for testing that restart Geth normally. 212 type snapshotTest struct { 213 snapshotTestBasic 214 } 215 216 func (snaptest *snapshotTest) test(t *testing.T) { 217 // It's hard to follow the test case, visualize the input 218 // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) 219 // fmt.Println(tt.dump()) 220 chain, blocks := snaptest.prepare(t) 221 222 // Restart the chain normally 223 chain.Stop() 224 newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 225 if err != nil { 226 t.Fatalf("Failed to recreate chain: %v", err) 227 } 228 defer newchain.Stop() 229 230 snaptest.verify(t, newchain, blocks) 231 } 232 233 // crashSnapshotTest is a test case type for innormal snapshot recovery. 234 // It can be used for testing that restart Geth after the crash. 235 type crashSnapshotTest struct { 236 snapshotTestBasic 237 } 238 239 func (snaptest *crashSnapshotTest) test(t *testing.T) { 240 // It's hard to follow the test case, visualize the input 241 // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) 242 // fmt.Println(tt.dump()) 243 chain, blocks := snaptest.prepare(t) 244 245 // Pull the plug on the database, simulating a hard crash 246 db := chain.db 247 db.Close() 248 249 // Start a new blockchain back up and see where the repair leads us 250 newdb, err := rawdb.NewLevelDBDatabase(snaptest.datadir, 0, 0, "", false) 251 if err != nil { 252 t.Fatalf("Failed to reopen persistent database: %v", err) 253 } 254 defer newdb.Close() 255 256 // The interesting thing is: instead of starting the blockchain after 257 // the crash, we do restart twice here: one after the crash and one 258 // after the normal stop. It's used to ensure the broken snapshot 259 // can be detected all the time. 260 newchain, err := NewBlockChain(newdb, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 261 if err != nil { 262 t.Fatalf("Failed to recreate chain: %v", err) 263 } 264 newchain.Stop() 265 266 newchain, err = NewBlockChain(newdb, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 267 if err != nil { 268 t.Fatalf("Failed to recreate chain: %v", err) 269 } 270 defer newchain.Stop() 271 272 snaptest.verify(t, newchain, blocks) 273 } 274 275 // gappedSnapshotTest is a test type used to test this scenario: 276 // - have a complete snapshot 277 // - restart without enabling the snapshot 278 // - insert a few blocks 279 // - restart with enabling the snapshot again 280 type gappedSnapshotTest struct { 281 snapshotTestBasic 282 gapped int // Number of blocks to insert without enabling snapshot 283 } 284 285 func (snaptest *gappedSnapshotTest) test(t *testing.T) { 286 // It's hard to follow the test case, visualize the input 287 // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) 288 // fmt.Println(tt.dump()) 289 chain, blocks := snaptest.prepare(t) 290 291 // Insert blocks without enabling snapshot if gapping is required. 292 chain.Stop() 293 gappedBlocks, _, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.gapped, 10, func(i int, b *BlockGen) {}) 294 295 // Insert a few more blocks without enabling snapshot 296 var cacheConfig = &CacheConfig{ 297 TrieCleanLimit: 256, 298 TrieDirtyLimit: 256, 299 SnapshotLimit: 0, 300 Pruning: true, 301 CommitInterval: 4096, 302 } 303 newchain, err := NewBlockChain(snaptest.db, cacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 304 if err != nil { 305 t.Fatalf("Failed to recreate chain: %v", err) 306 } 307 newchain.InsertChain(gappedBlocks) 308 newchain.Stop() 309 310 // Restart the chain with enabling the snapshot 311 newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 312 if err != nil { 313 t.Fatalf("Failed to recreate chain: %v", err) 314 } 315 defer newchain.Stop() 316 317 snaptest.verify(t, newchain, blocks) 318 } 319 320 // wipeCrashSnapshotTest is the test type used to test this scenario: 321 // - have a complete snapshot 322 // - restart, insert more blocks without enabling the snapshot 323 // - restart again with enabling the snapshot 324 // - crash 325 type wipeCrashSnapshotTest struct { 326 snapshotTestBasic 327 newBlocks int 328 } 329 330 func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { 331 // It's hard to follow the test case, visualize the input 332 // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) 333 // fmt.Println(tt.dump()) 334 chain, blocks := snaptest.prepare(t) 335 336 // Firstly, stop the chain properly, with all snapshot journal 337 // and state committed. 338 chain.Stop() 339 340 config := &CacheConfig{ 341 TrieCleanLimit: 256, 342 TrieDirtyLimit: 256, 343 SnapshotLimit: 0, 344 Pruning: true, 345 CommitInterval: 4096, 346 } 347 newchain, err := NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 348 if err != nil { 349 t.Fatalf("Failed to recreate chain: %v", err) 350 } 351 newBlocks, _, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.gendb, snaptest.newBlocks, 10, func(i int, b *BlockGen) {}) 352 newchain.InsertChain(newBlocks) 353 newchain.Stop() 354 355 // Restart the chain, the wiper should starts working 356 config = &CacheConfig{ 357 TrieCleanLimit: 256, 358 TrieDirtyLimit: 256, 359 SnapshotLimit: 256, 360 Pruning: true, 361 CommitInterval: 4096, 362 } 363 _, err = NewBlockChain(snaptest.db, config, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 364 if err != nil { 365 t.Fatalf("Failed to recreate chain: %v", err) 366 } 367 // Simulate the blockchain crash. 368 369 newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfig, params.TestChainConfig, snaptest.engine, vm.Config{}, snaptest.lastAcceptedHash) 370 if err != nil { 371 t.Fatalf("Failed to recreate chain: %v", err) 372 } 373 snaptest.verify(t, newchain, blocks) 374 } 375 376 // Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot 377 // journal will be persisted correctly. In this case no snapshot recovery is 378 // required. 379 func TestRestartWithNewSnapshot(t *testing.T) { 380 // Chain: 381 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 382 // 383 // Snapshot: G 384 // 385 // ------------------------------ 386 // 387 // Expected in leveldb: 388 // G->C1->C2->C3->C4->C5->C6->C7->C8 389 // 390 // Expected head header : C8 391 // Expected head block : C4 392 // Expected snapshot disk : C4 393 test := &snapshotTest{ 394 snapshotTestBasic{ 395 chainBlocks: 8, 396 snapshotBlock: 4, 397 expCanonicalBlocks: 8, 398 expHeadBlock: 4, 399 expSnapshotBottom: 4, // Initial disk layer built from genesis 400 }, 401 } 402 test.test(t) 403 test.teardown() 404 } 405 406 // Tests a Geth was crashed and restarts with a broken snapshot. In this case the 407 // chain head should be rewound to the point with available state. And also the 408 // new head should must be lower than disk layer. But there is no committed point 409 // so the chain should be rewound to genesis and the disk layer should be left 410 // for recovery. 411 func TestNoCommitCrashWithNewSnapshot(t *testing.T) { 412 // Chain: 413 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 414 // 415 // Snapshot: G, C4 416 // 417 // CRASH 418 // 419 // ------------------------------ 420 // 421 // Expected in leveldb: 422 // G->C1->C2->C3->C4->C5->C6->C7->C8 423 // 424 // Expected head block : C4 425 // Expected snapshot disk : C4 426 test := &crashSnapshotTest{ 427 snapshotTestBasic{ 428 chainBlocks: 8, 429 snapshotBlock: 4, 430 expCanonicalBlocks: 8, 431 expHeadBlock: 4, 432 expSnapshotBottom: 4, // Last committed disk layer, wait recovery 433 }, 434 } 435 test.test(t) 436 test.teardown() 437 } 438 439 // Tests a Geth was crashed and restarts with a broken snapshot. In this case the 440 // chain head should be rewound to the point with available state. And also the 441 // new head should must be lower than disk layer. But there is only a low committed 442 // point so the chain should be rewound to committed point and the disk layer 443 // should be left for recovery. 444 func TestLowCommitCrashWithNewSnapshot(t *testing.T) { 445 // Chain: 446 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 447 // 448 // Snapshot: G, C4 449 // 450 // CRASH 451 // 452 // ------------------------------ 453 // 454 // Expected in leveldb: 455 // G->C1->C2->C3->C4->C5->C6->C7->C8 456 // 457 // Expected head block : C4 458 // Expected snapshot disk : C4 459 test := &crashSnapshotTest{ 460 snapshotTestBasic{ 461 chainBlocks: 8, 462 snapshotBlock: 4, 463 expCanonicalBlocks: 8, 464 expHeadBlock: 4, 465 expSnapshotBottom: 4, // Last committed disk layer, wait recovery 466 }, 467 } 468 test.test(t) 469 test.teardown() 470 } 471 472 // Tests a Geth was crashed and restarts with a broken snapshot. In this case 473 // the chain head should be rewound to the point with available state. And also 474 // the new head should must be lower than disk layer. But there is only a high 475 // committed point so the chain should be rewound to genesis and the disk layer 476 // should be left for recovery. 477 func TestHighCommitCrashWithNewSnapshot(t *testing.T) { 478 // Chain: 479 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 480 // 481 // Snapshot: G, C4 482 // 483 // CRASH 484 // 485 // ------------------------------ 486 // 487 // Expected in leveldb: 488 // G->C1->C2->C3->C4->C5->C6->C7->C8 489 // 490 // Expected head block : C4 491 // Expected snapshot disk : C4 492 test := &crashSnapshotTest{ 493 snapshotTestBasic{ 494 chainBlocks: 8, 495 snapshotBlock: 4, 496 expCanonicalBlocks: 8, 497 expHeadBlock: 4, 498 expSnapshotBottom: 4, // Last committed disk layer, wait recovery 499 }, 500 } 501 test.test(t) 502 test.teardown() 503 } 504 505 // Tests a Geth was running with snapshot enabled. Then restarts without 506 // enabling snapshot and after that re-enable the snapshot again. In this 507 // case the snapshot should be rebuilt with latest chain head. 508 func TestGappedNewSnapshot(t *testing.T) { 509 // Chain: 510 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 511 // 512 // Snapshot: G 513 // 514 // ------------------------------ 515 // 516 // Expected in leveldb: 517 // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 518 // 519 // Expected head block : G 520 // Expected snapshot disk : G 521 test := &gappedSnapshotTest{ 522 snapshotTestBasic: snapshotTestBasic{ 523 chainBlocks: 8, 524 snapshotBlock: 0, 525 expCanonicalBlocks: 10, 526 expHeadBlock: 0, 527 expSnapshotBottom: 0, // Rebuilt snapshot from the latest HEAD 528 }, 529 gapped: 2, 530 } 531 test.test(t) 532 test.teardown() 533 } 534 535 // Tests the Geth was running with a complete snapshot and then imports a few 536 // more new blocks on top without enabling the snapshot. After the restart, 537 // crash happens. Check everything is ok after the restart. 538 func TestRecoverSnapshotFromWipingCrash(t *testing.T) { 539 // Chain: 540 // G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD) 541 // 542 // Snapshot: G 543 // 544 // ------------------------------ 545 // 546 // Expected in leveldb: 547 // G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 548 // 549 // Expected head block : C4 550 // Expected snapshot disk : C4 551 test := &wipeCrashSnapshotTest{ 552 snapshotTestBasic: snapshotTestBasic{ 553 chainBlocks: 8, 554 snapshotBlock: 4, 555 expCanonicalBlocks: 10, 556 expHeadBlock: 4, 557 expSnapshotBottom: 4, 558 }, 559 newBlocks: 2, 560 } 561 test.test(t) 562 test.teardown() 563 }