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: &params.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: &params.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  }