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  }