github.com/klaytn/klaytn@v1.12.1/blockchain/blockchain_sethead_test.go (about)

     1  // Copyright 2021 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn 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 klaytn 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 klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package blockchain
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/klaytn/klaytn/blockchain/types"
    23  	"github.com/klaytn/klaytn/consensus/gxhash"
    24  	"github.com/klaytn/klaytn/params"
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  // Tests a sethead for a short canonical chain where a recent block was already
    29  // committed to disk and then the sethead called. In this case we expect the full
    30  // chain to be rolled back to the committed block. Everything above the sethead
    31  // point should be deleted. In between the committed block and the requested head
    32  // the data can remain as "fast sync" data to avoid redownloading it.
    33  type rewindTest struct {
    34  	canonicalBlocks int     // Number of blocks to generate for the canonical chain (heavier)
    35  	sidechainBlocks int     // Number of blocks to generate for the side chain (lighter)
    36  	commitBlock     uint64  // Block number for which to commit the state to disk
    37  	pivotBlock      *uint64 // Pivot block number in case of fast sync
    38  
    39  	setheadBlock       uint64 // Block number to set head back to
    40  	expCanonicalBlocks int    // Number of canonical blocks expected to remain in the database (excl. genesis)
    41  	expSidechainBlocks int    // Number of sidechain blocks expected to remain in the database (excl. genesis)
    42  	expHeadHeader      uint64 // Block number of the expected head header
    43  	expHeadFastBlock   uint64 // Block number of the expected head fast sync block
    44  	expHeadBlock       uint64 // Block number of the expected head full block
    45  }
    46  
    47  func TestShortSetHead(t *testing.T) { testShortSetHead(t) }
    48  
    49  func testShortSetHead(t *testing.T) {
    50  	// Chain:
    51  	//   G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
    52  	//
    53  	// Frozen: none
    54  	// Commit: G, C4
    55  	// Pivot : none
    56  	//
    57  	// SetHead(7)
    58  	//
    59  	// ------------------------------
    60  	//
    61  	// Expected in leveldb:
    62  	//   G->C1->C2->C3->C4->C5->C6->C7
    63  	//
    64  	// Expected head header    : C7
    65  	// Expected head fast block: C7
    66  	// Expected head block     : C7
    67  	testSetHead(t, &rewindTest{
    68  		canonicalBlocks:    8,
    69  		sidechainBlocks:    0,
    70  		commitBlock:        4,
    71  		pivotBlock:         nil,
    72  		setheadBlock:       7,
    73  		expCanonicalBlocks: 7,
    74  		expSidechainBlocks: 0,
    75  		expHeadHeader:      7,
    76  		expHeadFastBlock:   7,
    77  		expHeadBlock:       7,
    78  	})
    79  }
    80  
    81  // Tests a sethead for a short canonical chain and a shorter side chain, where a
    82  // recent block was already committed to disk and then sethead was called. In this
    83  // test scenario the side chain is below the committed block. In this case we expect
    84  // the canonical full chain to be rolled back to the committed block. Everything
    85  // above the sethead point should be deleted. In between the committed block and
    86  // the requested head the data can remain as "fast sync" data to avoid redownloading
    87  // it. The side chain should be left alone as it was shorter.
    88  
    89  func TestLongDeepSetHead(t *testing.T) { testLongDeepSetHead(t, false) }
    90  
    91  func testLongDeepSetHead(t *testing.T, snapshots bool) {
    92  	// Chain:
    93  	//   G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
    94  	//
    95  	//
    96  	// Commit: G, C4
    97  	// Pivot : none
    98  	//
    99  	// SetHead(6)
   100  	//
   101  	// -----------------------------
   102  	//
   103  	// Expected in leveldb: none
   104  	//
   105  	// Expected head header    : C4
   106  	// Expected head fast block: C4
   107  	// Expected head block     : C4
   108  	testSetHead(t, &rewindTest{
   109  		canonicalBlocks:    24,
   110  		sidechainBlocks:    0,
   111  		commitBlock:        4,
   112  		pivotBlock:         nil,
   113  		setheadBlock:       6,
   114  		expCanonicalBlocks: 6,
   115  		expSidechainBlocks: 0,
   116  		expHeadHeader:      6,
   117  		expHeadFastBlock:   6,
   118  		expHeadBlock:       6,
   119  	})
   120  }
   121  
   122  func testSetHead(t *testing.T, tt *rewindTest) {
   123  	db, chain, err := newCanonical(gxhash.NewFullFaker(), 0, true)
   124  	if err != nil {
   125  		t.Fatalf("failed to create pristine chain: %v", err)
   126  	}
   127  	defer chain.Stop()
   128  	chain.Config().Istanbul = params.GetDefaultIstanbulConfig()
   129  
   130  	// If sidechain blocks are needed, make a light chain and import it
   131  	var sideblocks types.Blocks
   132  	if tt.sidechainBlocks > 0 {
   133  		sideblocks, _ = GenerateChain(params.TestChainConfig, chain.CurrentBlock(), gxhash.NewFaker(), db, tt.canonicalBlocks, func(i int, b *BlockGen) {})
   134  		if _, err := chain.InsertChain(sideblocks); err != nil {
   135  			t.Fatalf("Failed to import side chain: %v", err)
   136  		}
   137  	}
   138  
   139  	canonblocks, _ := GenerateChain(params.TestChainConfig, chain.CurrentBlock(), gxhash.NewFaker(), db, tt.canonicalBlocks, func(i int, b *BlockGen) {})
   140  	if _, err := chain.InsertChain(canonblocks[:tt.commitBlock]); err != nil {
   141  		t.Fatalf("Failed to import canonical chain start: %v", err)
   142  	}
   143  	if tt.commitBlock > 0 {
   144  		chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), true, tt.commitBlock)
   145  	}
   146  	if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil {
   147  		t.Fatalf("Failed to import canonical chain tail: %v", err)
   148  	}
   149  	// Manually dereference anything not committed to not have to work with 128+ tries
   150  	for _, block := range sideblocks {
   151  		chain.stateCache.TrieDB().Dereference(block.Root())
   152  	}
   153  	for _, block := range canonblocks {
   154  		chain.stateCache.TrieDB().Dereference(block.Root())
   155  	}
   156  
   157  	// Set the head of the chain back to the requested number
   158  	chain.SetHead(tt.setheadBlock)
   159  
   160  	// Iterate over all the remaining blocks and ensure there are no gaps
   161  	verifyNoGaps(t, chain, true, canonblocks)
   162  	verifyNoGaps(t, chain, false, sideblocks)
   163  	verifyCutoff(t, chain, true, canonblocks, tt.expCanonicalBlocks)
   164  	verifyCutoff(t, chain, false, sideblocks, tt.expSidechainBlocks)
   165  
   166  	if head := chain.CurrentHeader(); head.Number.Uint64() != tt.expHeadHeader {
   167  		t.Errorf("Head header mismatch!: have %d, want %d", head.Number, tt.expHeadHeader)
   168  	}
   169  	if head := chain.CurrentFastBlock(); head.NumberU64() != tt.expHeadFastBlock {
   170  		t.Errorf("Head fast block mismatch: have %d, want %d", head.NumberU64(), tt.expHeadFastBlock)
   171  	}
   172  	if head := chain.CurrentBlock(); head.NumberU64() != tt.expHeadBlock {
   173  		t.Errorf("Head block mismatch!!: have %d, want %d", head.NumberU64(), tt.expHeadBlock)
   174  	}
   175  }
   176  
   177  // verifyNoGaps checks that there are no gaps after the initial set of blocks in
   178  // the database and errors if found.
   179  func verifyNoGaps(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks) {
   180  	t.Helper()
   181  
   182  	var end uint64
   183  	for i := uint64(0); i <= uint64(len(inserted)); i++ {
   184  		header := chain.GetHeaderByNumber(i)
   185  		if header == nil && end == 0 {
   186  			end = i
   187  		}
   188  		if header != nil && end > 0 {
   189  			if canonical {
   190  				t.Errorf("Canonical header gap between #%d-#%d", end, i-1)
   191  			} else {
   192  				t.Errorf("Sidechain header gap between #%d-#%d", end, i-1)
   193  			}
   194  			end = 0 // Reset for further gap detection
   195  		}
   196  	}
   197  	end = 0
   198  	for i := uint64(0); i <= uint64(len(inserted)); i++ {
   199  		block := chain.GetBlockByNumber(i)
   200  		if block == nil && end == 0 {
   201  			end = i
   202  		}
   203  		if block != nil && end > 0 {
   204  			if canonical {
   205  				t.Errorf("Canonical block gap between #%d-#%d", end, i-1)
   206  			} else {
   207  				t.Errorf("Sidechain block gap between #%d-#%d", end, i-1)
   208  			}
   209  			end = 0 // Reset for further gap detection
   210  		}
   211  	}
   212  	end = 0
   213  	for i := uint64(1); i <= uint64(len(inserted)); i++ {
   214  		receipts := chain.GetReceiptsByBlockHash(inserted[i-1].Hash())
   215  		if receipts == nil && end == 0 {
   216  			end = i
   217  		}
   218  		if receipts != nil && end > 0 {
   219  			if canonical {
   220  				t.Errorf("Canonical receipt gap between #%d-#%d", end, i-1)
   221  			} else {
   222  				t.Errorf("Sidechain receipt gap between #%d-#%d", end, i-1)
   223  			}
   224  			end = 0 // Reset for further gap detection
   225  		}
   226  	}
   227  }
   228  
   229  // verifyCutoff checks that there are no chain data available in the chain after
   230  // the specified limit, but that it is available before.
   231  func verifyCutoff(t *testing.T, chain *BlockChain, canonical bool, inserted types.Blocks, head int) {
   232  	t.Helper()
   233  
   234  	for i := 1; i <= len(inserted); i++ {
   235  		if i <= head {
   236  			if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header == nil {
   237  				if canonical {
   238  					t.Errorf("Canonical header   #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   239  				} else {
   240  					t.Errorf("Sidechain header   #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   241  				}
   242  			}
   243  			if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block == nil {
   244  				if canonical {
   245  					t.Errorf("Canonical block    #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   246  				} else {
   247  					t.Errorf("Sidechain block    #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   248  				}
   249  			}
   250  			if receipts := chain.GetReceiptsByBlockHash(inserted[i-1].Hash()); receipts == nil {
   251  				if canonical {
   252  					t.Errorf("Canonical receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   253  				} else {
   254  					t.Errorf("Sidechain receipts #%2d [%x...] missing before cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   255  				}
   256  			}
   257  		} else {
   258  			if header := chain.GetHeader(inserted[i-1].Hash(), uint64(i)); header != nil {
   259  				if canonical {
   260  					t.Errorf("Canonical header   #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   261  				} else {
   262  					t.Errorf("Sidechain header   #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   263  				}
   264  			}
   265  			if block := chain.GetBlock(inserted[i-1].Hash(), uint64(i)); block != nil {
   266  				if canonical {
   267  					t.Errorf("Canonical block    #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   268  				} else {
   269  					t.Errorf("Sidechain block    #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   270  				}
   271  			}
   272  			if receipts := chain.GetReceiptsByBlockHash(inserted[i-1].Hash()); receipts != nil {
   273  				if canonical {
   274  					t.Errorf("Canonical receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   275  				} else {
   276  					t.Errorf("Sidechain receipts #%2d [%x...] present after cap %d", inserted[i-1].Number(), inserted[i-1].Hash().Bytes()[:3], head)
   277  				}
   278  			}
   279  		}
   280  	}
   281  }
   282  
   283  func TestSetHeadEarlyExit(t *testing.T) {
   284  	testSetHeadEarlyExit(t, &rewindTest{
   285  		canonicalBlocks: 8,
   286  		setheadBlock:    3,
   287  	})
   288  }
   289  
   290  func testSetHeadEarlyExit(t *testing.T, tt *rewindTest) {
   291  	db, chain, err := newCanonical(gxhash.NewFullFaker(), 0, true)
   292  	if err != nil {
   293  		t.Fatalf("failed to create pristine chain: %v", err)
   294  	}
   295  	defer chain.Stop()
   296  	chain.Config().Istanbul = params.GetDefaultIstanbulConfig()
   297  
   298  	canonblocks, _ := GenerateChain(params.TestChainConfig, chain.CurrentBlock(), gxhash.NewFaker(), db, tt.canonicalBlocks, func(i int, b *BlockGen) {})
   299  	if _, err := chain.InsertChain(canonblocks); err != nil {
   300  		t.Fatalf("Failed to import canonical chain start: %v", err)
   301  	}
   302  
   303  	db.WriteLastPrunedBlockNumber(tt.setheadBlock + 1)
   304  	assert.Error(t, chain.SetHead(tt.setheadBlock))
   305  
   306  	db.WriteLastPrunedBlockNumber(tt.setheadBlock)
   307  	assert.Error(t, chain.SetHead(tt.setheadBlock))
   308  
   309  	db.WriteLastPrunedBlockNumber(tt.setheadBlock - 1)
   310  	assert.Nil(t, chain.SetHead(tt.setheadBlock))
   311  }