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 }