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 }