github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/common/ledger/blkstorage/snapshot_test.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package blkstorage 8 9 import ( 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "sort" 15 "testing" 16 17 "github.com/hechain20/hechain/common/ledger/snapshot" 18 "github.com/hechain20/hechain/common/ledger/testutil" 19 "github.com/hechain20/hechain/internal/pkg/txflags" 20 "github.com/hechain20/hechain/protoutil" 21 "github.com/hyperledger/fabric-protos-go/common" 22 "github.com/hyperledger/fabric-protos-go/peer" 23 "github.com/stretchr/testify/require" 24 ) 25 26 type testBlockDetails struct { 27 txIDs []string 28 validationCodes []peer.TxValidationCode 29 } 30 31 func TestImportFromSnapshot(t *testing.T) { 32 var testDir string 33 var env *testEnv 34 var blocksDetailsBeforeSnapshot, blocksDetailsAfterSnapshot []*testBlockDetails 35 var blocksBeforeSnapshot, blocksAfterSnapshot []*common.Block 36 var blocksGenerator *testutil.BlockGenerator 37 38 var snapshotDir string 39 var snapshotInfo *SnapshotInfo 40 var bootstrappedBlockStore *BlockStore 41 42 bootstrappedLedgerName := "bootstrappedLedger" 43 44 setup := func() { 45 testDir = testPath() 46 env = newTestEnv(t, NewConf(testDir, 0)) 47 snapshotDir = filepath.Join(testDir, "snapshot") 48 require.NoError(t, os.Mkdir(snapshotDir, 0o755)) 49 50 bg, genesisBlock := testutil.NewBlockGenerator(t, "testLedger", false) 51 blocksGenerator = bg 52 originalBlockStore, err := env.provider.Open("originalLedger") 53 require.NoError(t, err) 54 txIDGenesisTx, err := protoutil.GetOrComputeTxIDFromEnvelope(genesisBlock.Data.Data[0]) 55 require.NoError(t, err) 56 57 blocksDetailsBeforeSnapshot = []*testBlockDetails{ 58 { 59 txIDs: []string{txIDGenesisTx}, 60 }, 61 { 62 txIDs: []string{"txid1", "txid2"}, 63 }, 64 { 65 txIDs: []string{"txid3", "txid4", "txid5"}, 66 }, 67 } 68 69 blocksBeforeSnapshot = []*common.Block{ 70 genesisBlock, 71 generateNextTestBlock(blocksGenerator, blocksDetailsBeforeSnapshot[1]), 72 generateNextTestBlock(blocksGenerator, blocksDetailsBeforeSnapshot[2]), 73 } 74 75 blocksDetailsAfterSnapshot = []*testBlockDetails{ 76 { 77 txIDs: []string{"txid7", "txid8"}, 78 }, 79 { 80 txIDs: []string{"txid9", "txid10", "txid11"}, 81 validationCodes: []peer.TxValidationCode{ 82 peer.TxValidationCode_BAD_CHANNEL_HEADER, 83 peer.TxValidationCode_BAD_CREATOR_SIGNATURE, 84 }, 85 }, 86 } 87 88 blocksAfterSnapshot = []*common.Block{ 89 generateNextTestBlock(blocksGenerator, blocksDetailsAfterSnapshot[0]), 90 generateNextTestBlock(blocksGenerator, blocksDetailsAfterSnapshot[1]), 91 } 92 93 for _, b := range blocksBeforeSnapshot { 94 err := originalBlockStore.AddBlock(b) 95 require.NoError(t, err) 96 } 97 98 // verify blockchain info for original store (created by genesisblock) 99 lastBlock := blocksBeforeSnapshot[len(blocksBeforeSnapshot)-1] 100 prevBlock := blocksBeforeSnapshot[len(blocksBeforeSnapshot)-2] 101 bcInfo, err := originalBlockStore.GetBlockchainInfo() 102 require.NoError(t, err) 103 require.Equal(t, &common.BlockchainInfo{ 104 Height: uint64(len(blocksBeforeSnapshot)), 105 CurrentBlockHash: protoutil.BlockHeaderHash(lastBlock.Header), 106 PreviousBlockHash: protoutil.BlockHeaderHash(prevBlock.Header), 107 }, bcInfo) 108 109 _, err = originalBlockStore.ExportTxIds(snapshotDir, testNewHashFunc) 110 require.NoError(t, err) 111 lastBlockInSnapshot := blocksBeforeSnapshot[len(blocksBeforeSnapshot)-1] 112 113 snapshotInfo = &SnapshotInfo{ 114 LastBlockHash: protoutil.BlockHeaderHash(lastBlockInSnapshot.Header), 115 LastBlockNum: lastBlockInSnapshot.Header.Number, 116 PreviousBlockHash: lastBlockInSnapshot.Header.PreviousHash, 117 } 118 119 // bootstrap another blockstore from the snapshot and verify its APIs 120 importTxIDsBatchSize = uint64(2) // smaller batch size for testing 121 122 err = env.provider.ImportFromSnapshot(bootstrappedLedgerName, snapshotDir, snapshotInfo) 123 require.NoError(t, err) 124 bootstrappedBlockStore, err = env.provider.Open(bootstrappedLedgerName) 125 require.NoError(t, err) 126 } 127 128 cleanup := func() { 129 env.Cleanup() 130 } 131 132 closeBlockStore := func() { 133 env.provider.Close() 134 } 135 136 reopenBlockStore := func() error { 137 env = newTestEnv(t, env.provider.conf) 138 s, err := env.provider.Open(bootstrappedLedgerName) 139 bootstrappedBlockStore = s 140 return err 141 } 142 143 t.Run("query-just-after-bootstrap", func(t *testing.T) { 144 setup() 145 defer cleanup() 146 147 verifyQueriesOnBlocksPriorToSnapshot( 148 t, 149 bootstrappedBlockStore, 150 &common.BlockchainInfo{ 151 Height: snapshotInfo.LastBlockNum + 1, 152 CurrentBlockHash: snapshotInfo.LastBlockHash, 153 PreviousBlockHash: snapshotInfo.PreviousBlockHash, 154 BootstrappingSnapshotInfo: &common.BootstrappingSnapshotInfo{ 155 LastBlockInSnapshot: snapshotInfo.LastBlockNum, 156 }, 157 }, 158 blocksDetailsBeforeSnapshot, 159 blocksBeforeSnapshot, 160 ) 161 }) 162 163 t.Run("add-more-blocks", func(t *testing.T) { 164 setup() 165 defer cleanup() 166 167 require.EqualError(t, 168 bootstrappedBlockStore.AddBlock(blocksAfterSnapshot[1]), 169 "block number should have been 3 but was 4", 170 ) 171 172 for _, b := range blocksAfterSnapshot { 173 require.NoError(t, bootstrappedBlockStore.AddBlock(b)) 174 } 175 finalBlock := blocksAfterSnapshot[len(blocksAfterSnapshot)-1] 176 expectedBCInfo := &common.BlockchainInfo{ 177 Height: finalBlock.Header.Number + 1, 178 CurrentBlockHash: protoutil.BlockHeaderHash(finalBlock.Header), 179 PreviousBlockHash: finalBlock.Header.PreviousHash, 180 BootstrappingSnapshotInfo: &common.BootstrappingSnapshotInfo{ 181 LastBlockInSnapshot: snapshotInfo.LastBlockNum, 182 }, 183 } 184 verifyQueriesOnBlocksPriorToSnapshot(t, 185 bootstrappedBlockStore, 186 expectedBCInfo, 187 blocksDetailsBeforeSnapshot, 188 blocksBeforeSnapshot, 189 ) 190 verifyQueriesOnBlocksAddedAfterBootstrapping(t, 191 bootstrappedBlockStore, 192 expectedBCInfo, 193 blocksDetailsAfterSnapshot, 194 blocksAfterSnapshot, 195 ) 196 }) 197 198 t.Run("close-and-reopen", func(t *testing.T) { 199 setup() 200 defer cleanup() 201 202 closeBlockStore() 203 require.NoError(t, reopenBlockStore()) 204 verifyQueriesOnBlocksPriorToSnapshot(t, 205 bootstrappedBlockStore, 206 &common.BlockchainInfo{ 207 Height: snapshotInfo.LastBlockNum + 1, 208 CurrentBlockHash: snapshotInfo.LastBlockHash, 209 PreviousBlockHash: snapshotInfo.PreviousBlockHash, 210 BootstrappingSnapshotInfo: &common.BootstrappingSnapshotInfo{ 211 LastBlockInSnapshot: snapshotInfo.LastBlockNum, 212 }, 213 }, 214 blocksDetailsBeforeSnapshot, 215 blocksBeforeSnapshot, 216 ) 217 218 for _, b := range blocksAfterSnapshot { 219 require.NoError(t, bootstrappedBlockStore.AddBlock(b)) 220 } 221 closeBlockStore() 222 require.NoError(t, reopenBlockStore()) 223 finalBlock := blocksAfterSnapshot[len(blocksAfterSnapshot)-1] 224 expectedBCInfo := &common.BlockchainInfo{ 225 Height: finalBlock.Header.Number + 1, 226 CurrentBlockHash: protoutil.BlockHeaderHash(finalBlock.Header), 227 PreviousBlockHash: finalBlock.Header.PreviousHash, 228 BootstrappingSnapshotInfo: &common.BootstrappingSnapshotInfo{ 229 LastBlockInSnapshot: snapshotInfo.LastBlockNum, 230 }, 231 } 232 verifyQueriesOnBlocksAddedAfterBootstrapping(t, 233 bootstrappedBlockStore, 234 expectedBCInfo, 235 blocksDetailsAfterSnapshot, 236 blocksAfterSnapshot, 237 ) 238 }) 239 240 t.Run("export-txids", func(t *testing.T) { 241 setup() 242 defer cleanup() 243 244 anotherSnapshotDir := filepath.Join(testDir, "anotherSnapshot") 245 require.NoError(t, os.Mkdir(anotherSnapshotDir, 0o755)) 246 247 for _, b := range blocksAfterSnapshot { 248 require.NoError(t, bootstrappedBlockStore.AddBlock(b)) 249 } 250 251 fileHashes, err := bootstrappedBlockStore.ExportTxIds(anotherSnapshotDir, testNewHashFunc) 252 require.NoError(t, err) 253 expectedTxIDs := []string{} 254 for _, b := range append(blocksDetailsBeforeSnapshot, blocksDetailsAfterSnapshot...) { 255 expectedTxIDs = append(expectedTxIDs, b.txIDs...) 256 } 257 sort.Slice(expectedTxIDs, func(i, j int) bool { 258 ith := expectedTxIDs[i] 259 jth := expectedTxIDs[j] 260 if len(ith) == len(jth) { 261 return ith < jth 262 } 263 return len(ith) < len(jth) 264 }) 265 verifyExportedTxIDs(t, anotherSnapshotDir, fileHashes, expectedTxIDs...) 266 }) 267 268 t.Run("sync-up-indexes", func(t *testing.T) { 269 setup() 270 defer cleanup() 271 272 blockDetails := []*testBlockDetails{} 273 blocks := []*common.Block{} 274 for i, blockDetail := range blocksDetailsAfterSnapshot { 275 block := blocksAfterSnapshot[i] 276 blockDetails = append(blockDetails, blockDetail) 277 blocks = append(blocks, block) 278 279 // redirect index writes to some random place and add two blocks and then set the original index back 280 blkfileMgr := bootstrappedBlockStore.fileMgr 281 originalIndexDB := blkfileMgr.index.db 282 bootstrappedBlockStore.fileMgr.index.db = env.provider.leveldbProvider.GetDBHandle(filepath.Join(testDir, "someRandomPlace")) 283 require.NoError(t, blkfileMgr.addBlock(block)) 284 blkfileMgr.index.db = originalIndexDB 285 286 // before, we test for index sync-up, verify that the last set of blocks not indexed in the original index 287 _, err := blkfileMgr.retrieveBlockByNumber(block.Header.Number) 288 require.EqualError(t, err, fmt.Sprintf("no such block number [%d] in index", block.Header.Number)) 289 290 // close and open should be able to sync-up the index 291 closeBlockStore() 292 require.NoError(t, reopenBlockStore()) 293 294 finalBlock := blocks[len(blocks)-1] 295 verifyQueriesOnBlocksAddedAfterBootstrapping( 296 t, 297 bootstrappedBlockStore, 298 &common.BlockchainInfo{ 299 Height: finalBlock.Header.Number + 1, 300 CurrentBlockHash: protoutil.BlockHeaderHash(finalBlock.Header), 301 PreviousBlockHash: finalBlock.Header.PreviousHash, 302 BootstrappingSnapshotInfo: &common.BootstrappingSnapshotInfo{ 303 LastBlockInSnapshot: snapshotInfo.LastBlockNum, 304 }, 305 }, 306 blockDetails, 307 blocks, 308 ) 309 } 310 }) 311 312 t.Run("error-when-indexes-deleted", func(t *testing.T) { 313 setup() 314 defer cleanup() 315 316 closeBlockStore() 317 require.NoError(t, os.RemoveAll(env.provider.conf.getIndexDir())) 318 err := reopenBlockStore() 319 require.EqualError(t, err, 320 fmt.Sprintf( 321 "cannot sync index with block files. blockstore is bootstrapped from a snapshot and first available block=[%d]", 322 len(blocksBeforeSnapshot), 323 ), 324 ) 325 }) 326 } 327 328 func TestBootstrapFromSnapshotErrorPaths(t *testing.T) { 329 testPath := testPath() 330 env := newTestEnv(t, NewConf(testPath, 0)) 331 defer func() { 332 env.Cleanup() 333 }() 334 335 ledgerID := "bootstrappedLedger" 336 snapshotDir := filepath.Join(testPath, "snapshot") 337 metadataFile := filepath.Join(snapshotDir, snapshotMetadataFileName) 338 dataFile := filepath.Join(snapshotDir, snapshotDataFileName) 339 ledgerDir := filepath.Join(testPath, "chains", "bootstrappedLedger") 340 bootstrappingSnapshotInfoFile := filepath.Join(ledgerDir, bootstrappingSnapshotInfoFile) 341 342 snapshotInfo := &SnapshotInfo{ 343 LastBlockHash: []byte("LastBlockHash"), 344 LastBlockNum: 5, 345 PreviousBlockHash: []byte("PreviousBlockHash"), 346 } 347 348 cleanupDirs := func() { 349 require.NoError(t, os.RemoveAll(ledgerDir)) 350 require.NoError(t, os.RemoveAll(snapshotDir)) 351 require.NoError(t, os.Mkdir(snapshotDir, 0o755)) 352 } 353 354 createSnapshotMetadataFile := func(content uint64) { 355 mf, err := snapshot.CreateFile(metadataFile, snapshotFileFormat, testNewHashFunc) 356 require.NoError(t, err) 357 require.NoError(t, mf.EncodeUVarint(content)) 358 _, err = mf.Done() 359 require.NoError(t, err) 360 } 361 createSnapshotDataFile := func(content ...string) { 362 df, err := snapshot.CreateFile(dataFile, snapshotFileFormat, testNewHashFunc) 363 require.NoError(t, err) 364 for _, c := range content { 365 require.NoError(t, df.EncodeString(c)) 366 } 367 _, err = df.Done() 368 require.NoError(t, err) 369 } 370 371 t.Run("metadata-file-missing", func(t *testing.T) { 372 cleanupDirs() 373 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 374 require.Contains(t, err.Error(), "error while opening the snapshot file: "+metadataFile) 375 }) 376 377 t.Run("bootstapping-more-than-once", func(t *testing.T) { 378 cleanupDirs() 379 env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 380 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 381 require.EqualError(t, err, "dir "+ledgerDir+" not empty") 382 }) 383 384 t.Run("metadata-file-corrupted", func(t *testing.T) { 385 cleanupDirs() 386 mf, err := snapshot.CreateFile(metadataFile, snapshotFileFormat, testNewHashFunc) 387 require.NoError(t, err) 388 require.NoError(t, mf.Close()) 389 err = env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 390 require.Contains(t, err.Error(), "error while reading from the snapshot file: "+metadataFile) 391 }) 392 393 t.Run("data-file-missing", func(t *testing.T) { 394 cleanupDirs() 395 createSnapshotMetadataFile(1) 396 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 397 require.Contains(t, err.Error(), "error while opening the snapshot file: "+dataFile) 398 }) 399 400 t.Run("data-file-corrupt", func(t *testing.T) { 401 cleanupDirs() 402 createSnapshotMetadataFile(2) 403 createSnapshotDataFile("single-tx-id") 404 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 405 require.Contains(t, err.Error(), "error while reading from snapshot file: "+dataFile) 406 }) 407 408 t.Run("db-error", func(t *testing.T) { 409 cleanupDirs() 410 createSnapshotMetadataFile(1) 411 createSnapshotDataFile("single-tx-id") 412 env.provider.Close() 413 defer func() { 414 env = newTestEnv(t, NewConf(testPath, 0)) 415 }() 416 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 417 require.Contains(t, err.Error(), "error writing batch to leveldb") 418 }) 419 420 t.Run("bootstrappedsnapshotInfo-file-corrupt", func(t *testing.T) { 421 cleanupDirs() 422 createSnapshotMetadataFile(1) 423 createSnapshotDataFile("single-tx-id") 424 err := env.provider.ImportFromSnapshot(ledgerID, snapshotDir, snapshotInfo) 425 require.NoError(t, err) 426 env.provider.Close() 427 env = newTestEnv(t, NewConf(testPath, 0)) 428 require.NoError(t, ioutil.WriteFile(bootstrappingSnapshotInfoFile, []byte("junk-data"), 0o644)) 429 _, err = env.provider.Open(ledgerID) 430 require.Contains(t, err.Error(), "error while unmarshalling bootstrappingSnapshotInfo") 431 }) 432 } 433 434 func generateNextTestBlock(bg *testutil.BlockGenerator, d *testBlockDetails) *common.Block { 435 txContents := [][]byte{} 436 for _, txID := range d.txIDs { 437 txContents = append(txContents, []byte("dummy content for txid = "+txID)) 438 } 439 block := bg.NextBlockWithTxid(txContents, d.txIDs) 440 for i, validationCode := range d.validationCodes { 441 txflags.ValidationFlags(block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER]).SetFlag(i, validationCode) 442 } 443 return block 444 } 445 446 func verifyQueriesOnBlocksPriorToSnapshot( 447 t *testing.T, 448 bootstrappedBlockStore *BlockStore, 449 expectedBCInfo *common.BlockchainInfo, 450 blocksDetailsBeforeSnapshot []*testBlockDetails, 451 blocksBeforeSnapshot []*common.Block, 452 ) { 453 bci, err := bootstrappedBlockStore.GetBlockchainInfo() 454 require.NoError(t, err) 455 require.Equal(t, expectedBCInfo, bci) 456 457 for _, b := range blocksBeforeSnapshot { 458 blockNum := b.Header.Number 459 blockHash := protoutil.BlockHeaderHash(b.Header) 460 expectedErrStr := fmt.Sprintf( 461 "cannot serve block [%d]. The ledger is bootstrapped from a snapshot. First available block = [%d]", 462 blockNum, len(blocksBeforeSnapshot), 463 ) 464 465 _, err := bootstrappedBlockStore.RetrieveBlockByNumber(blockNum) 466 require.EqualError(t, err, expectedErrStr) 467 468 _, err = bootstrappedBlockStore.RetrieveBlocks(blockNum) 469 require.EqualError(t, err, expectedErrStr) 470 471 _, err = bootstrappedBlockStore.RetrieveTxByBlockNumTranNum(blockNum, 0) 472 require.EqualError(t, err, expectedErrStr) 473 474 _, err = bootstrappedBlockStore.RetrieveBlockByHash(blockHash) 475 require.EqualError(t, err, fmt.Sprintf("no such block hash [%x] in index", blockHash)) 476 } 477 478 bootstrappingSnapshotHeight := uint64(len(blocksDetailsBeforeSnapshot)) 479 for _, d := range blocksDetailsBeforeSnapshot { 480 for _, txID := range d.txIDs { 481 expectedErrorStr := fmt.Sprintf( 482 "details for the TXID [%s] not available. Ledger bootstrapped from a snapshot. First available block = [%d]", 483 txID, bootstrappingSnapshotHeight, 484 ) 485 _, err := bootstrappedBlockStore.RetrieveBlockByTxID(txID) 486 require.EqualError(t, err, expectedErrorStr) 487 488 _, err = bootstrappedBlockStore.RetrieveTxByID(txID) 489 require.EqualError(t, err, expectedErrorStr) 490 491 _, _, err = bootstrappedBlockStore.RetrieveTxValidationCodeByTxID(txID) 492 require.EqualError(t, err, expectedErrorStr) 493 494 exists, err := bootstrappedBlockStore.TxIDExists(txID) 495 require.NoError(t, err) 496 require.True(t, exists) 497 } 498 } 499 } 500 501 func verifyQueriesOnBlocksAddedAfterBootstrapping(t *testing.T, 502 bootstrappedBlockStore *BlockStore, 503 expectedBCInfo *common.BlockchainInfo, 504 blocksDetailsAfterSnapshot []*testBlockDetails, 505 blocksAfterSnapshot []*common.Block, 506 ) { 507 bci, err := bootstrappedBlockStore.GetBlockchainInfo() 508 require.NoError(t, err) 509 require.Equal(t, expectedBCInfo, bci) 510 511 for _, b := range blocksAfterSnapshot { 512 retrievedBlock, err := bootstrappedBlockStore.RetrieveBlockByNumber(b.Header.Number) 513 require.NoError(t, err) 514 require.Equal(t, b, retrievedBlock) 515 516 retrievedBlock, err = bootstrappedBlockStore.RetrieveBlockByHash(protoutil.BlockHeaderHash(b.Header)) 517 require.NoError(t, err) 518 require.Equal(t, b, retrievedBlock) 519 520 itr, err := bootstrappedBlockStore.RetrieveBlocks(b.Header.Number) 521 require.NoError(t, err) 522 blk, err := itr.Next() 523 require.NoError(t, err) 524 require.Equal(t, b, blk) 525 itr.Close() 526 527 retrievedTxEnv, err := bootstrappedBlockStore.RetrieveTxByBlockNumTranNum(b.Header.Number, 0) 528 require.NoError(t, err) 529 expectedTxEnv, err := protoutil.GetEnvelopeFromBlock(b.Data.Data[0]) 530 require.NoError(t, err) 531 require.Equal(t, expectedTxEnv, retrievedTxEnv) 532 } 533 534 for i, d := range blocksDetailsAfterSnapshot { 535 block := blocksAfterSnapshot[i] 536 for j, txID := range d.txIDs { 537 retrievedBlock, err := bootstrappedBlockStore.RetrieveBlockByTxID(txID) 538 require.NoError(t, err) 539 require.Equal(t, block, retrievedBlock) 540 541 retrievedTxEnv, err := bootstrappedBlockStore.RetrieveTxByID(txID) 542 require.NoError(t, err) 543 expectedTxEnv, err := protoutil.GetEnvelopeFromBlock(block.Data.Data[j]) 544 require.NoError(t, err) 545 require.Equal(t, expectedTxEnv, retrievedTxEnv) 546 547 exists, err := bootstrappedBlockStore.TxIDExists(txID) 548 require.NoError(t, err) 549 require.True(t, exists) 550 } 551 552 for j, validationCode := range d.validationCodes { 553 retrievedValidationCode, blkNum, err := bootstrappedBlockStore.RetrieveTxValidationCodeByTxID(d.txIDs[j]) 554 require.NoError(t, err) 555 require.Equal(t, validationCode, retrievedValidationCode) 556 require.Equal(t, block.Header.Number, blkNum) 557 } 558 } 559 }