github.com/dim4egster/coreth@v0.10.2/core/blockchain_test.go (about) 1 // (c) 2020-2021, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package core 5 6 import ( 7 "fmt" 8 "math/big" 9 "testing" 10 11 "github.com/dim4egster/coreth/consensus/dummy" 12 "github.com/dim4egster/coreth/core/rawdb" 13 "github.com/dim4egster/coreth/core/state" 14 "github.com/dim4egster/coreth/core/state/pruner" 15 "github.com/dim4egster/coreth/core/types" 16 "github.com/dim4egster/coreth/core/vm" 17 "github.com/dim4egster/coreth/ethdb" 18 "github.com/dim4egster/coreth/params" 19 "github.com/ethereum/go-ethereum/common" 20 "github.com/ethereum/go-ethereum/crypto" 21 ) 22 23 var ( 24 archiveConfig = &CacheConfig{ 25 TrieCleanLimit: 256, 26 TrieDirtyLimit: 256, 27 TrieDirtyCommitTarget: 20, 28 Pruning: false, // Archive mode 29 SnapshotLimit: 256, 30 AcceptorQueueLimit: 64, 31 } 32 33 pruningConfig = &CacheConfig{ 34 TrieCleanLimit: 256, 35 TrieDirtyLimit: 256, 36 TrieDirtyCommitTarget: 20, 37 Pruning: true, // Enable pruning 38 CommitInterval: 4096, 39 SnapshotLimit: 256, 40 AcceptorQueueLimit: 64, 41 } 42 ) 43 44 func createBlockChain( 45 db ethdb.Database, 46 cacheConfig *CacheConfig, 47 chainConfig *params.ChainConfig, 48 lastAcceptedHash common.Hash, 49 ) (*BlockChain, error) { 50 // Import the chain. This runs all block validation rules. 51 blockchain, err := NewBlockChain( 52 db, 53 cacheConfig, 54 chainConfig, 55 dummy.NewDummyEngine(&dummy.ConsensusCallbacks{ 56 OnExtraStateChange: func(block *types.Block, sdb *state.StateDB) (*big.Int, *big.Int, error) { 57 sdb.SetBalanceMultiCoin(common.HexToAddress("0xdeadbeef"), common.HexToHash("0xdeadbeef"), big.NewInt(block.Number().Int64())) 58 return nil, nil, nil 59 }, 60 OnFinalizeAndAssemble: func(header *types.Header, sdb *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { 61 sdb.SetBalanceMultiCoin(common.HexToAddress("0xdeadbeef"), common.HexToHash("0xdeadbeef"), big.NewInt(header.Number.Int64())) 62 return nil, nil, nil, nil 63 }, 64 }), 65 vm.Config{}, 66 lastAcceptedHash, 67 ) 68 return blockchain, err 69 } 70 71 func TestArchiveBlockChain(t *testing.T) { 72 createArchiveBlockChain := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 73 return createBlockChain(db, archiveConfig, chainConfig, lastAcceptedHash) 74 } 75 for _, tt := range tests { 76 t.Run(tt.Name, func(t *testing.T) { 77 tt.testFunc(t, createArchiveBlockChain) 78 }) 79 } 80 } 81 82 func TestArchiveBlockChainSnapsDisabled(t *testing.T) { 83 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 84 return createBlockChain( 85 db, 86 &CacheConfig{ 87 TrieCleanLimit: 256, 88 TrieDirtyLimit: 256, 89 TrieDirtyCommitTarget: 20, 90 Pruning: false, // Archive mode 91 SnapshotLimit: 0, // Disable snapshots 92 AcceptorQueueLimit: 64, 93 }, 94 chainConfig, 95 lastAcceptedHash, 96 ) 97 } 98 for _, tt := range tests { 99 t.Run(tt.Name, func(t *testing.T) { 100 tt.testFunc(t, create) 101 }) 102 } 103 } 104 105 func TestPruningBlockChain(t *testing.T) { 106 createPruningBlockChain := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 107 return createBlockChain(db, pruningConfig, chainConfig, lastAcceptedHash) 108 } 109 for _, tt := range tests { 110 t.Run(tt.Name, func(t *testing.T) { 111 tt.testFunc(t, createPruningBlockChain) 112 }) 113 } 114 } 115 116 func TestPruningBlockChainSnapsDisabled(t *testing.T) { 117 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 118 return createBlockChain( 119 db, 120 &CacheConfig{ 121 TrieCleanLimit: 256, 122 TrieDirtyLimit: 256, 123 TrieDirtyCommitTarget: 20, 124 Pruning: true, // Enable pruning 125 CommitInterval: 4096, 126 SnapshotLimit: 0, // Disable snapshots 127 AcceptorQueueLimit: 64, 128 }, 129 chainConfig, 130 lastAcceptedHash, 131 ) 132 } 133 for _, tt := range tests { 134 t.Run(tt.Name, func(t *testing.T) { 135 tt.testFunc(t, create) 136 }) 137 } 138 } 139 140 type wrappedStateManager struct { 141 TrieWriter 142 } 143 144 func (w *wrappedStateManager) Shutdown() error { return nil } 145 146 func TestPruningBlockChainUngracefulShutdown(t *testing.T) { 147 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 148 blockchain, err := createBlockChain(db, pruningConfig, chainConfig, lastAcceptedHash) 149 if err != nil { 150 return nil, err 151 } 152 153 // Overwrite state manager, so that Shutdown is not called. 154 // This tests to ensure that the state manager handles an ungraceful shutdown correctly. 155 blockchain.stateManager = &wrappedStateManager{TrieWriter: blockchain.stateManager} 156 return blockchain, err 157 } 158 for _, tt := range tests { 159 t.Run(tt.Name, func(t *testing.T) { 160 tt.testFunc(t, create) 161 }) 162 } 163 } 164 165 func TestPruningBlockChainUngracefulShutdownSnapsDisabled(t *testing.T) { 166 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 167 blockchain, err := createBlockChain( 168 db, 169 &CacheConfig{ 170 TrieCleanLimit: 256, 171 TrieDirtyLimit: 256, 172 TrieDirtyCommitTarget: 20, 173 Pruning: true, // Enable pruning 174 CommitInterval: 4096, 175 SnapshotLimit: 0, // Disable snapshots 176 AcceptorQueueLimit: 64, 177 }, 178 chainConfig, 179 lastAcceptedHash, 180 ) 181 if err != nil { 182 return nil, err 183 } 184 185 // Overwrite state manager, so that Shutdown is not called. 186 // This tests to ensure that the state manager handles an ungraceful shutdown correctly. 187 blockchain.stateManager = &wrappedStateManager{TrieWriter: blockchain.stateManager} 188 return blockchain, err 189 } 190 for _, tt := range tests { 191 t.Run(tt.Name, func(t *testing.T) { 192 tt.testFunc(t, create) 193 }) 194 } 195 } 196 197 func TestEnableSnapshots(t *testing.T) { 198 // Set snapshots to be disabled the first time, and then enable them on the restart 199 snapLimit := 0 200 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 201 // Import the chain. This runs all block validation rules. 202 blockchain, err := createBlockChain( 203 db, 204 &CacheConfig{ 205 TrieCleanLimit: 256, 206 TrieDirtyLimit: 256, 207 TrieDirtyCommitTarget: 20, 208 Pruning: true, // Enable pruning 209 CommitInterval: 4096, 210 SnapshotLimit: snapLimit, 211 AcceptorQueueLimit: 64, 212 }, 213 chainConfig, 214 lastAcceptedHash, 215 ) 216 if err != nil { 217 return nil, err 218 } 219 snapLimit = 256 220 221 return blockchain, err 222 } 223 for _, tt := range tests { 224 t.Run(tt.Name, func(t *testing.T) { 225 tt.testFunc(t, create) 226 }) 227 } 228 } 229 230 func TestCorruptSnapshots(t *testing.T) { 231 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 232 // Delete the snapshot block hash and state root to ensure that if we die in between writing a snapshot 233 // diff layer to disk at any point, we can still recover on restart. 234 rawdb.DeleteSnapshotBlockHash(db) 235 rawdb.DeleteSnapshotRoot(db) 236 237 return createBlockChain(db, pruningConfig, chainConfig, lastAcceptedHash) 238 } 239 for _, tt := range tests { 240 t.Run(tt.Name, func(t *testing.T) { 241 tt.testFunc(t, create) 242 }) 243 } 244 } 245 246 func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { 247 create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 248 // Import the chain. This runs all block validation rules. 249 blockchain, err := createBlockChain(db, pruningConfig, chainConfig, lastAcceptedHash) 250 if err != nil { 251 return nil, err 252 } 253 254 // Overwrite state manager, so that Shutdown is not called. 255 // This tests to ensure that the state manager handles an ungraceful shutdown correctly. 256 blockchain.stateManager = &wrappedStateManager{TrieWriter: blockchain.stateManager} 257 258 if lastAcceptedHash == (common.Hash{}) { 259 return blockchain, nil 260 } 261 262 tempDir := t.TempDir() 263 if err := blockchain.CleanBlockRootsAboveLastAccepted(); err != nil { 264 return nil, err 265 } 266 pruner, err := pruner.NewPruner(db, tempDir, 256) 267 if err != nil { 268 return nil, fmt.Errorf("offline pruning failed (%s, %d): %w", tempDir, 256, err) 269 } 270 271 targetRoot := blockchain.LastAcceptedBlock().Root() 272 if err := pruner.Prune(targetRoot); err != nil { 273 return nil, fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err) 274 } 275 // Re-initialize the blockchain after pruning 276 return createBlockChain(db, pruningConfig, chainConfig, lastAcceptedHash) 277 } 278 for _, tt := range tests { 279 t.Run(tt.Name, func(t *testing.T) { 280 t.Parallel() 281 tt.testFunc(t, create) 282 }) 283 } 284 } 285 286 func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { 287 var ( 288 key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 289 key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") 290 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 291 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 292 // We use two separate databases since GenerateChain commits the state roots to its underlying 293 // database. 294 genDB = rawdb.NewMemoryDatabase() 295 chainDB = rawdb.NewMemoryDatabase() 296 lastAcceptedHash common.Hash 297 ) 298 299 // Ensure that key1 has some funds in the genesis block. 300 genesisBalance := big.NewInt(1000000) 301 gspec := &Genesis{ 302 Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, 303 Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, 304 } 305 genesis := gspec.MustCommit(genDB) 306 _ = gspec.MustCommit(chainDB) 307 308 blockchain, err := createBlockChain(chainDB, pruningConfig, gspec.Config, lastAcceptedHash) 309 if err != nil { 310 t.Fatal(err) 311 } 312 defer blockchain.Stop() 313 314 // This call generates a chain of 3 blocks. 315 signer := types.HomesteadSigner{} 316 // Generate chain of blocks using [genDB] instead of [chainDB] to avoid writing 317 // to the BlockChain's database while generating blocks. 318 chain, _, err := GenerateChain(gspec.Config, genesis, blockchain.engine, genDB, 10, 10, func(i int, gen *BlockGen) { 319 tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) 320 gen.AddTx(tx) 321 }) 322 if err != nil { 323 t.Fatal(err) 324 } 325 326 if _, err := blockchain.InsertChain(chain); err != nil { 327 t.Fatal(err) 328 } 329 for _, block := range chain { 330 if err := blockchain.Accept(block); err != nil { 331 t.Fatal(err) 332 } 333 } 334 blockchain.DrainAcceptorQueue() 335 336 lastAcceptedHash = blockchain.LastConsensusAcceptedBlock().Hash() 337 blockchain.Stop() 338 339 blockchain, err = createBlockChain(chainDB, pruningConfig, gspec.Config, lastAcceptedHash) 340 if err != nil { 341 t.Fatal(err) 342 } 343 344 // Confirm that the node does not have the state for intermediate nodes (exclude the last accepted block) 345 for _, block := range chain[:len(chain)-1] { 346 if blockchain.HasState(block.Root()) { 347 t.Fatalf("Expected blockchain to be missing state for intermediate block %d with pruning enabled", block.NumberU64()) 348 } 349 } 350 blockchain.Stop() 351 352 startHeight := uint64(1) 353 // Create a node in archival mode and re-populate the trie history. 354 blockchain, err = createBlockChain( 355 chainDB, 356 &CacheConfig{ 357 TrieCleanLimit: 256, 358 TrieDirtyLimit: 256, 359 TrieDirtyCommitTarget: 20, 360 Pruning: false, // Archive mode 361 SnapshotLimit: 256, 362 PopulateMissingTries: &startHeight, // Starting point for re-populating. 363 PopulateMissingTriesParallelism: parallelism, 364 AcceptorQueueLimit: 64, 365 }, 366 gspec.Config, 367 lastAcceptedHash, 368 ) 369 if err != nil { 370 t.Fatal(err) 371 } 372 373 for _, block := range chain { 374 if !blockchain.HasState(block.Root()) { 375 t.Fatalf("failed to re-generate state for block %d", block.NumberU64()) 376 } 377 } 378 } 379 380 func TestRepopulateMissingTries(t *testing.T) { 381 // Test with different levels of parallelism as a regression test. 382 for _, parallelism := range []int{1, 2, 4, 1024} { 383 testRepopulateMissingTriesParallel(t, parallelism) 384 } 385 } 386 387 func TestUngracefulAsyncShutdown(t *testing.T) { 388 var ( 389 create = func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { 390 blockchain, err := createBlockChain(db, &CacheConfig{ 391 TrieCleanLimit: 256, 392 TrieDirtyLimit: 256, 393 TrieDirtyCommitTarget: 20, 394 Pruning: true, 395 CommitInterval: 4096, 396 SnapshotLimit: 256, 397 SkipSnapshotRebuild: true, // Ensure the test errors if snapshot initialization fails 398 AcceptorQueueLimit: 1000, // ensure channel doesn't block 399 }, chainConfig, lastAcceptedHash) 400 if err != nil { 401 return nil, err 402 } 403 return blockchain, nil 404 } 405 406 key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 407 key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") 408 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 409 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 410 // We use two separate databases since GenerateChain commits the state roots to its underlying 411 // database. 412 genDB = rawdb.NewMemoryDatabase() 413 chainDB = rawdb.NewMemoryDatabase() 414 ) 415 416 // Ensure that key1 has some funds in the genesis block. 417 genesisBalance := big.NewInt(1000000) 418 gspec := &Genesis{ 419 Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, 420 Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, 421 } 422 genesis := gspec.MustCommit(genDB) 423 _ = gspec.MustCommit(chainDB) 424 425 blockchain, err := create(chainDB, gspec.Config, common.Hash{}) 426 if err != nil { 427 t.Fatal(err) 428 } 429 defer blockchain.Stop() 430 431 // This call generates a chain of 10 blocks. 432 signer := types.HomesteadSigner{} 433 // Generate chain of blocks using [genDB] instead of [chainDB] to avoid writing 434 // to the BlockChain's database while generating blocks. 435 chain, _, err := GenerateChain(gspec.Config, genesis, blockchain.engine, genDB, 10, 10, func(i int, gen *BlockGen) { 436 tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) 437 gen.AddTx(tx) 438 }) 439 if err != nil { 440 t.Fatal(err) 441 } 442 443 // Insert three blocks into the chain and accept only the first block. 444 if _, err := blockchain.InsertChain(chain); err != nil { 445 t.Fatal(err) 446 } 447 448 foundTxs := []common.Hash{} 449 missingTxs := []common.Hash{} 450 for i, block := range chain { 451 if err := blockchain.Accept(block); err != nil { 452 t.Fatal(err) 453 } 454 455 if i == 3 { 456 // At height 3, kill the async accepted block processor to force an 457 // ungraceful recovery 458 blockchain.stopAcceptor() 459 blockchain.acceptorQueue = nil 460 } 461 462 if i <= 3 { 463 // If <= height 3, all txs should be accessible on lookup 464 for _, tx := range block.Transactions() { 465 foundTxs = append(foundTxs, tx.Hash()) 466 } 467 } else { 468 // If > 3, all txs should be accessible on lookup 469 for _, tx := range block.Transactions() { 470 missingTxs = append(missingTxs, tx.Hash()) 471 } 472 } 473 } 474 475 // After inserting all blocks, we should confirm that txs added after the 476 // async worker shutdown cannot be found. 477 for _, tx := range foundTxs { 478 txLookup := blockchain.GetTransactionLookup(tx) 479 if txLookup == nil { 480 t.Fatalf("missing transaction: %v", tx) 481 } 482 } 483 for _, tx := range missingTxs { 484 txLookup := blockchain.GetTransactionLookup(tx) 485 if txLookup != nil { 486 t.Fatalf("transaction should be missing: %v", tx) 487 } 488 } 489 490 // check the state of the last accepted block 491 checkState := func(sdb *state.StateDB) error { 492 nonce := sdb.GetNonce(addr1) 493 if nonce != 10 { 494 return fmt.Errorf("expected nonce addr1: 10, found nonce: %d", nonce) 495 } 496 transferredFunds := big.NewInt(100000) 497 balance1 := sdb.GetBalance(addr1) 498 expectedBalance1 := new(big.Int).Sub(genesisBalance, transferredFunds) 499 if balance1.Cmp(expectedBalance1) != 0 { 500 return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) 501 } 502 503 balance2 := sdb.GetBalance(addr2) 504 expectedBalance2 := transferredFunds 505 if balance2.Cmp(expectedBalance2) != 0 { 506 return fmt.Errorf("expected addr2 balance: %d, found balance: %d", expectedBalance2, balance2) 507 } 508 509 nonce = sdb.GetNonce(addr2) 510 if nonce != 0 { 511 return fmt.Errorf("expected addr2 nonce: 0, found nonce: %d", nonce) 512 } 513 return nil 514 } 515 516 _, newChain, restartedChain := checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) 517 518 allTxs := append(foundTxs, missingTxs...) 519 for _, bc := range []*BlockChain{newChain, restartedChain} { 520 // We should confirm that snapshots were properly initialized 521 if bc.snaps == nil { 522 t.Fatal("snapshot initialization failed") 523 } 524 525 // We should confirm all transactions can now be queried 526 for _, tx := range allTxs { 527 txLookup := bc.GetTransactionLookup(tx) 528 if txLookup == nil { 529 t.Fatalf("missing transaction: %v", tx) 530 } 531 } 532 } 533 } 534 535 // TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted 536 // correctly in case reorg is called. 537 func TestCanonicalHashMarker(t *testing.T) { 538 var cases = []struct { 539 forkA int 540 forkB int 541 }{ 542 // ForkA: 10 blocks 543 // ForkB: 1 blocks 544 // 545 // reorged: 546 // markers [2, 10] should be deleted 547 // markers [1] should be updated 548 {10, 1}, 549 550 // ForkA: 10 blocks 551 // ForkB: 2 blocks 552 // 553 // reorged: 554 // markers [3, 10] should be deleted 555 // markers [1, 2] should be updated 556 {10, 2}, 557 558 // ForkA: 10 blocks 559 // ForkB: 10 blocks 560 // 561 // reorged: 562 // markers [1, 10] should be updated 563 {10, 10}, 564 565 // ForkA: 10 blocks 566 // ForkB: 11 blocks 567 // 568 // reorged: 569 // markers [1, 11] should be updated 570 {10, 11}, 571 } 572 for _, c := range cases { 573 var ( 574 db = rawdb.NewMemoryDatabase() 575 gspec = &Genesis{ 576 Config: params.TestChainConfig, 577 Alloc: GenesisAlloc{}, 578 BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), 579 } 580 genesis = gspec.MustCommit(db) 581 engine = dummy.NewFaker() 582 ) 583 forkA, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkA, 10, func(i int, gen *BlockGen) {}) 584 if err != nil { 585 t.Fatal(err) 586 } 587 forkB, _, err := GenerateChain(params.TestChainConfig, genesis, engine, db, c.forkB, 10, func(i int, gen *BlockGen) {}) 588 if err != nil { 589 t.Fatal(err) 590 } 591 592 // Initialize test chain 593 diskdb := rawdb.NewMemoryDatabase() 594 gspec.MustCommit(diskdb) 595 chain, err := NewBlockChain(diskdb, DefaultCacheConfig, params.TestChainConfig, engine, vm.Config{}, common.Hash{}) 596 if err != nil { 597 t.Fatalf("failed to create tester chain: %v", err) 598 } 599 // Insert forkA and forkB, the canonical should on forkA still 600 if n, err := chain.InsertChain(forkA); err != nil { 601 t.Fatalf("block %d: failed to insert into chain: %v", n, err) 602 } 603 if n, err := chain.InsertChain(forkB); err != nil { 604 t.Fatalf("block %d: failed to insert into chain: %v", n, err) 605 } 606 607 verify := func(head *types.Block) { 608 if chain.CurrentBlock().Hash() != head.Hash() { 609 t.Fatalf("Unexpected block hash, want %x, got %x", head.Hash(), chain.CurrentBlock().Hash()) 610 } 611 if chain.CurrentHeader().Hash() != head.Hash() { 612 t.Fatalf("Unexpected head header, want %x, got %x", head.Hash(), chain.CurrentHeader().Hash()) 613 } 614 if !chain.HasState(head.Root()) { 615 t.Fatalf("Lost block state %v %x", head.Number(), head.Hash()) 616 } 617 } 618 619 // Switch canonical chain to forkB if necessary 620 if len(forkA) < len(forkB) { 621 verify(forkB[len(forkB)-1]) 622 } else { 623 verify(forkA[len(forkA)-1]) 624 if err := chain.SetPreference(forkB[len(forkB)-1]); err != nil { 625 t.Fatal(err) 626 } 627 verify(forkB[len(forkB)-1]) 628 } 629 630 // Ensure all hash markers are updated correctly 631 for i := 0; i < len(forkB); i++ { 632 block := forkB[i] 633 hash := chain.GetCanonicalHash(block.NumberU64()) 634 if hash != block.Hash() { 635 t.Fatalf("Unexpected canonical hash %d", block.NumberU64()) 636 } 637 } 638 if c.forkA > c.forkB { 639 for i := uint64(c.forkB) + 1; i <= uint64(c.forkA); i++ { 640 hash := chain.GetCanonicalHash(i) 641 if hash != (common.Hash{}) { 642 t.Fatalf("Unexpected canonical hash %d", i) 643 } 644 } 645 } 646 } 647 }