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