github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/database/ffldb/whitebox_test.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Copyright (c) 2016 The Dash developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 // This file is part of the ffldb package rather than the ffldb_test package as 7 // it provides whitebox testing. 8 9 package ffldb 10 11 import ( 12 "compress/bzip2" 13 "encoding/binary" 14 "fmt" 15 "hash/crc32" 16 "io" 17 "os" 18 "path/filepath" 19 "testing" 20 21 "github.com/btcsuite/goleveldb/leveldb" 22 ldberrors "github.com/btcsuite/goleveldb/leveldb/errors" 23 "github.com/dashpay/godash/chaincfg" 24 "github.com/dashpay/godash/database" 25 "github.com/dashpay/godash/wire" 26 "github.com/dashpay/godashutil" 27 ) 28 29 var ( 30 // blockDataNet is the expected network in the test block data. 31 blockDataNet = wire.MainNet 32 33 // blockDataFile is the path to a file containing the first 256 blocks 34 // of the block chain. 35 blockDataFile = filepath.Join("..", "testdata", "blocks1-256.bz2") 36 37 // errSubTestFail is used to signal that a sub test returned false. 38 errSubTestFail = fmt.Errorf("sub test failure") 39 ) 40 41 // loadBlocks loads the blocks contained in the testdata directory and returns 42 // a slice of them. 43 func loadBlocks(t *testing.T, dataFile string, network wire.BitcoinNet) ([]*godashutil.Block, error) { 44 // Open the file that contains the blocks for reading. 45 fi, err := os.Open(dataFile) 46 if err != nil { 47 t.Errorf("failed to open file %v, err %v", dataFile, err) 48 return nil, err 49 } 50 defer func() { 51 if err := fi.Close(); err != nil { 52 t.Errorf("failed to close file %v %v", dataFile, 53 err) 54 } 55 }() 56 dr := bzip2.NewReader(fi) 57 58 // Set the first block as the genesis block. 59 blocks := make([]*godashutil.Block, 0, 256) 60 genesis := godashutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) 61 blocks = append(blocks, genesis) 62 63 // Load the remaining blocks. 64 for height := 1; ; height++ { 65 var net uint32 66 err := binary.Read(dr, binary.LittleEndian, &net) 67 if err == io.EOF { 68 // Hit end of file at the expected offset. No error. 69 break 70 } 71 if err != nil { 72 t.Errorf("Failed to load network type for block %d: %v", 73 height, err) 74 return nil, err 75 } 76 if net != uint32(network) { 77 t.Errorf("Block doesn't match network: %v expects %v", 78 net, network) 79 return nil, err 80 } 81 82 var blockLen uint32 83 err = binary.Read(dr, binary.LittleEndian, &blockLen) 84 if err != nil { 85 t.Errorf("Failed to load block size for block %d: %v", 86 height, err) 87 return nil, err 88 } 89 90 // Read the block. 91 blockBytes := make([]byte, blockLen) 92 _, err = io.ReadFull(dr, blockBytes) 93 if err != nil { 94 t.Errorf("Failed to load block %d: %v", height, err) 95 return nil, err 96 } 97 98 // Deserialize and store the block. 99 block, err := godashutil.NewBlockFromBytes(blockBytes) 100 if err != nil { 101 t.Errorf("Failed to parse block %v: %v", height, err) 102 return nil, err 103 } 104 blocks = append(blocks, block) 105 } 106 107 return blocks, nil 108 } 109 110 // checkDbError ensures the passed error is a database.Error with an error code 111 // that matches the passed error code. 112 func checkDbError(t *testing.T, testName string, gotErr error, wantErrCode database.ErrorCode) bool { 113 dbErr, ok := gotErr.(database.Error) 114 if !ok { 115 t.Errorf("%s: unexpected error type - got %T, want %T", 116 testName, gotErr, database.Error{}) 117 return false 118 } 119 if dbErr.ErrorCode != wantErrCode { 120 t.Errorf("%s: unexpected error code - got %s (%s), want %s", 121 testName, dbErr.ErrorCode, dbErr.Description, 122 wantErrCode) 123 return false 124 } 125 126 return true 127 } 128 129 // testContext is used to store context information about a running test which 130 // is passed into helper functions. 131 type testContext struct { 132 t *testing.T 133 db database.DB 134 files map[uint32]*lockableFile 135 maxFileSizes map[uint32]int64 136 blocks []*godashutil.Block 137 } 138 139 // TestConvertErr ensures the leveldb error to database error conversion works 140 // as expected. 141 func TestConvertErr(t *testing.T) { 142 t.Parallel() 143 144 tests := []struct { 145 err error 146 wantErrCode database.ErrorCode 147 }{ 148 {&ldberrors.ErrCorrupted{}, database.ErrCorruption}, 149 {leveldb.ErrClosed, database.ErrDbNotOpen}, 150 {leveldb.ErrSnapshotReleased, database.ErrTxClosed}, 151 {leveldb.ErrIterReleased, database.ErrTxClosed}, 152 } 153 154 for i, test := range tests { 155 gotErr := convertErr("test", test.err) 156 if gotErr.ErrorCode != test.wantErrCode { 157 t.Errorf("convertErr #%d unexpected error - got %v, "+ 158 "want %v", i, gotErr.ErrorCode, test.wantErrCode) 159 continue 160 } 161 } 162 } 163 164 // TestCornerCases ensures several corner cases which can happen when opening 165 // a database and/or block files work as expected. 166 func TestCornerCases(t *testing.T) { 167 t.Parallel() 168 169 // Create a file at the datapase path to force the open below to fail. 170 dbPath := filepath.Join(os.TempDir(), "ffldb-errors") 171 _ = os.RemoveAll(dbPath) 172 fi, err := os.Create(dbPath) 173 if err != nil { 174 t.Errorf("os.Create: unexpected error: %v", err) 175 return 176 } 177 fi.Close() 178 179 // Ensure creating a new database fails when a file exists where a 180 // directory is needed. 181 testName := "openDB: fail due to file at target location" 182 wantErrCode := database.ErrDriverSpecific 183 idb, err := openDB(dbPath, blockDataNet, true) 184 if !checkDbError(t, testName, err, wantErrCode) { 185 if err == nil { 186 idb.Close() 187 } 188 _ = os.RemoveAll(dbPath) 189 return 190 } 191 192 // Remove the file and create the database to run tests against. It 193 // should be successful this time. 194 _ = os.RemoveAll(dbPath) 195 idb, err = openDB(dbPath, blockDataNet, true) 196 if err != nil { 197 t.Errorf("openDB: unexpected error: %v", err) 198 return 199 } 200 defer os.RemoveAll(dbPath) 201 defer idb.Close() 202 203 // Ensure attempting to write to a file that can't be created returns 204 // the expected error. 205 testName = "writeBlock: open file failure" 206 filePath := blockFilePath(dbPath, 0) 207 if err := os.Mkdir(filePath, 0755); err != nil { 208 t.Errorf("os.Mkdir: unexpected error: %v", err) 209 return 210 } 211 store := idb.(*db).store 212 _, err = store.writeBlock([]byte{0x00}) 213 if !checkDbError(t, testName, err, database.ErrDriverSpecific) { 214 return 215 } 216 _ = os.RemoveAll(filePath) 217 218 // Close the underlying leveldb database out from under the database. 219 ldb := idb.(*db).cache.ldb 220 ldb.Close() 221 222 // Ensure initilization errors in the underlying database work as 223 // expected. 224 testName = "initDB: reinitialization" 225 wantErrCode = database.ErrDbNotOpen 226 err = initDB(ldb) 227 if !checkDbError(t, testName, err, wantErrCode) { 228 return 229 } 230 231 // Ensure the View handles errors in the underlying leveldb database 232 // properly. 233 testName = "View: underlying leveldb error" 234 wantErrCode = database.ErrDbNotOpen 235 err = idb.View(func(tx database.Tx) error { 236 return nil 237 }) 238 if !checkDbError(t, testName, err, wantErrCode) { 239 return 240 } 241 242 // Ensure the Update handles errors in the underlying leveldb database 243 // properly. 244 testName = "Update: underlying leveldb error" 245 err = idb.Update(func(tx database.Tx) error { 246 return nil 247 }) 248 if !checkDbError(t, testName, err, wantErrCode) { 249 return 250 } 251 } 252 253 // resetDatabase removes everything from the opened database associated with the 254 // test context including all metadata and the mock files. 255 func resetDatabase(tc *testContext) bool { 256 // Reset the metadata. 257 err := tc.db.Update(func(tx database.Tx) error { 258 // Remove all the keys using a cursor while also generating a 259 // list of buckets. It's not safe to remove keys during ForEach 260 // iteration nor is it safe to remove buckets during cursor 261 // iteration, so this dual approach is needed. 262 var bucketNames [][]byte 263 cursor := tx.Metadata().Cursor() 264 for ok := cursor.First(); ok; ok = cursor.Next() { 265 if cursor.Value() != nil { 266 if err := cursor.Delete(); err != nil { 267 return err 268 } 269 } else { 270 bucketNames = append(bucketNames, cursor.Key()) 271 } 272 } 273 274 // Remove the buckets. 275 for _, k := range bucketNames { 276 if err := tx.Metadata().DeleteBucket(k); err != nil { 277 return err 278 } 279 } 280 281 _, err := tx.Metadata().CreateBucket(blockIdxBucketName) 282 return err 283 }) 284 if err != nil { 285 tc.t.Errorf("Update: unexpected error: %v", err) 286 return false 287 } 288 289 // Reset the mock files. 290 store := tc.db.(*db).store 291 wc := store.writeCursor 292 wc.curFile.Lock() 293 if wc.curFile.file != nil { 294 wc.curFile.file.Close() 295 wc.curFile.file = nil 296 } 297 wc.curFile.Unlock() 298 wc.Lock() 299 wc.curFileNum = 0 300 wc.curOffset = 0 301 wc.Unlock() 302 tc.files = make(map[uint32]*lockableFile) 303 tc.maxFileSizes = make(map[uint32]int64) 304 return true 305 } 306 307 // testWriteFailures tests various failures paths when writing to the block 308 // files. 309 func testWriteFailures(tc *testContext) bool { 310 if !resetDatabase(tc) { 311 return false 312 } 313 314 // Ensure file sync errors during flush return the expected error. 315 store := tc.db.(*db).store 316 testName := "flush: file sync failure" 317 store.writeCursor.Lock() 318 oldFile := store.writeCursor.curFile 319 store.writeCursor.curFile = &lockableFile{ 320 file: &mockFile{forceSyncErr: true, maxSize: -1}, 321 } 322 store.writeCursor.Unlock() 323 err := tc.db.(*db).cache.flush() 324 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 325 return false 326 } 327 store.writeCursor.Lock() 328 store.writeCursor.curFile = oldFile 329 store.writeCursor.Unlock() 330 331 // Force errors in the various error paths when writing data by using 332 // mock files with a limited max size. 333 block0Bytes, _ := tc.blocks[0].Bytes() 334 tests := []struct { 335 fileNum uint32 336 maxSize int64 337 }{ 338 // Force an error when writing the network bytes. 339 {fileNum: 0, maxSize: 2}, 340 341 // Force an error when writing the block size. 342 {fileNum: 0, maxSize: 6}, 343 344 // Force an error when writing the block. 345 {fileNum: 0, maxSize: 17}, 346 347 // Force an error when writing the checksum. 348 {fileNum: 0, maxSize: int64(len(block0Bytes)) + 10}, 349 350 // Force an error after writing enough blocks for force multiple 351 // files. 352 {fileNum: 15, maxSize: 1}, 353 } 354 355 for i, test := range tests { 356 if !resetDatabase(tc) { 357 return false 358 } 359 360 // Ensure storing the specified number of blocks using a mock 361 // file that fails the write fails when the transaction is 362 // committed, not when the block is stored. 363 tc.maxFileSizes = map[uint32]int64{test.fileNum: test.maxSize} 364 err := tc.db.Update(func(tx database.Tx) error { 365 for i, block := range tc.blocks { 366 err := tx.StoreBlock(block) 367 if err != nil { 368 tc.t.Errorf("StoreBlock (%d): unexpected "+ 369 "error: %v", i, err) 370 return errSubTestFail 371 } 372 } 373 374 return nil 375 }) 376 testName := fmt.Sprintf("Force update commit failure - test "+ 377 "%d, fileNum %d, maxsize %d", i, test.fileNum, 378 test.maxSize) 379 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 380 tc.t.Errorf("%v", err) 381 return false 382 } 383 384 // Ensure the commit rollback removed all extra files and data. 385 if len(tc.files) != 1 { 386 tc.t.Errorf("Update rollback: new not removed - want "+ 387 "1 file, got %d", len(tc.files)) 388 return false 389 } 390 if _, ok := tc.files[0]; !ok { 391 tc.t.Error("Update rollback: file 0 does not exist") 392 return false 393 } 394 file := tc.files[0].file.(*mockFile) 395 if len(file.data) != 0 { 396 tc.t.Errorf("Update rollback: file did not truncate - "+ 397 "want len 0, got len %d", len(file.data)) 398 return false 399 } 400 } 401 402 return true 403 } 404 405 // testBlockFileErrors ensures the database returns expected errors with various 406 // file-related issues such as closed and missing files. 407 func testBlockFileErrors(tc *testContext) bool { 408 if !resetDatabase(tc) { 409 return false 410 } 411 412 // Ensure errors in blockFile and openFile when requesting invalid file 413 // numbers. 414 store := tc.db.(*db).store 415 testName := "blockFile invalid file open" 416 _, err := store.blockFile(^uint32(0)) 417 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 418 return false 419 } 420 testName = "openFile invalid file open" 421 _, err = store.openFile(^uint32(0)) 422 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 423 return false 424 } 425 426 // Insert the first block into the mock file. 427 err = tc.db.Update(func(tx database.Tx) error { 428 err := tx.StoreBlock(tc.blocks[0]) 429 if err != nil { 430 tc.t.Errorf("StoreBlock: unexpected error: %v", err) 431 return errSubTestFail 432 } 433 434 return nil 435 }) 436 if err != nil { 437 if err != errSubTestFail { 438 tc.t.Errorf("Update: unexpected error: %v", err) 439 } 440 return false 441 } 442 443 // Ensure errors in readBlock and readBlockRegion when requesting a file 444 // number that doesn't exist. 445 block0Hash := tc.blocks[0].Sha() 446 testName = "readBlock invalid file number" 447 invalidLoc := blockLocation{ 448 blockFileNum: ^uint32(0), 449 blockLen: 80, 450 } 451 _, err = store.readBlock(block0Hash, invalidLoc) 452 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 453 return false 454 } 455 testName = "readBlockRegion invalid file number" 456 _, err = store.readBlockRegion(invalidLoc, 0, 80) 457 if !checkDbError(tc.t, testName, err, database.ErrDriverSpecific) { 458 return false 459 } 460 461 // Close the block file out from under the database. 462 store.writeCursor.curFile.Lock() 463 store.writeCursor.curFile.file.Close() 464 store.writeCursor.curFile.Unlock() 465 466 // Ensure failures in FetchBlock and FetchBlockRegion(s) since the 467 // underlying file they need to read from has been closed. 468 err = tc.db.View(func(tx database.Tx) error { 469 testName = "FetchBlock closed file" 470 wantErrCode := database.ErrDriverSpecific 471 _, err := tx.FetchBlock(block0Hash) 472 if !checkDbError(tc.t, testName, err, wantErrCode) { 473 return errSubTestFail 474 } 475 476 testName = "FetchBlockRegion closed file" 477 regions := []database.BlockRegion{ 478 { 479 Hash: block0Hash, 480 Len: 80, 481 Offset: 0, 482 }, 483 } 484 _, err = tx.FetchBlockRegion(®ions[0]) 485 if !checkDbError(tc.t, testName, err, wantErrCode) { 486 return errSubTestFail 487 } 488 489 testName = "FetchBlockRegions closed file" 490 _, err = tx.FetchBlockRegions(regions) 491 if !checkDbError(tc.t, testName, err, wantErrCode) { 492 return errSubTestFail 493 } 494 495 return nil 496 }) 497 if err != nil { 498 if err != errSubTestFail { 499 tc.t.Errorf("View: unexpected error: %v", err) 500 } 501 return false 502 } 503 504 return true 505 } 506 507 // testCorruption ensures the database returns expected errors under various 508 // corruption scenarios. 509 func testCorruption(tc *testContext) bool { 510 if !resetDatabase(tc) { 511 return false 512 } 513 514 // Insert the first block into the mock file. 515 err := tc.db.Update(func(tx database.Tx) error { 516 err := tx.StoreBlock(tc.blocks[0]) 517 if err != nil { 518 tc.t.Errorf("StoreBlock: unexpected error: %v", err) 519 return errSubTestFail 520 } 521 522 return nil 523 }) 524 if err != nil { 525 if err != errSubTestFail { 526 tc.t.Errorf("Update: unexpected error: %v", err) 527 } 528 return false 529 } 530 531 // Ensure corruption is detected by intentionally modifying the bytes 532 // stored to the mock file and reading the block. 533 block0Bytes, _ := tc.blocks[0].Bytes() 534 block0Hash := tc.blocks[0].Sha() 535 tests := []struct { 536 offset uint32 537 fixChecksum bool 538 wantErrCode database.ErrorCode 539 }{ 540 // One of the network bytes. The checksum needs to be fixed so 541 // the invalid network is detected. 542 {2, true, database.ErrDriverSpecific}, 543 544 // The same network byte, but this time don't fix the checksum 545 // to ensure the corruption is detected. 546 {2, false, database.ErrCorruption}, 547 548 // One of the block length bytes. 549 {6, false, database.ErrCorruption}, 550 551 // Random header byte. 552 {17, false, database.ErrCorruption}, 553 554 // Random transaction byte. 555 {90, false, database.ErrCorruption}, 556 557 // Random checksum byte. 558 {uint32(len(block0Bytes)) + 10, false, database.ErrCorruption}, 559 } 560 err = tc.db.View(func(tx database.Tx) error { 561 data := tc.files[0].file.(*mockFile).data 562 for i, test := range tests { 563 // Corrupt the byte at the offset by a single bit. 564 data[test.offset] ^= 0x10 565 566 // Fix the checksum if requested to force other errors. 567 fileLen := len(data) 568 var oldChecksumBytes [4]byte 569 copy(oldChecksumBytes[:], data[fileLen-4:]) 570 if test.fixChecksum { 571 toSum := data[:fileLen-4] 572 cksum := crc32.Checksum(toSum, castagnoli) 573 binary.BigEndian.PutUint32(data[fileLen-4:], cksum) 574 } 575 576 testName := fmt.Sprintf("FetchBlock (test #%d): "+ 577 "corruption", i) 578 _, err := tx.FetchBlock(block0Hash) 579 if !checkDbError(tc.t, testName, err, test.wantErrCode) { 580 return errSubTestFail 581 } 582 583 // Reset the corrupted data back to the original. 584 data[test.offset] ^= 0x10 585 if test.fixChecksum { 586 copy(data[fileLen-4:], oldChecksumBytes[:]) 587 } 588 } 589 590 return nil 591 }) 592 if err != nil { 593 if err != errSubTestFail { 594 tc.t.Errorf("View: unexpected error: %v", err) 595 } 596 return false 597 } 598 599 return true 600 } 601 602 // TestFailureScenarios ensures several failure scenarios such as database 603 // corruption, block file write failures, and rollback failures are handled 604 // correctly. 605 func TestFailureScenarios(t *testing.T) { 606 // Create a new database to run tests against. 607 dbPath := filepath.Join(os.TempDir(), "ffldb-failurescenarios") 608 _ = os.RemoveAll(dbPath) 609 idb, err := database.Create(dbType, dbPath, blockDataNet) 610 if err != nil { 611 t.Errorf("Failed to create test database (%s) %v", dbType, err) 612 return 613 } 614 defer os.RemoveAll(dbPath) 615 defer idb.Close() 616 617 // Create a test context to pass around. 618 tc := &testContext{ 619 t: t, 620 db: idb, 621 files: make(map[uint32]*lockableFile), 622 maxFileSizes: make(map[uint32]int64), 623 } 624 625 // Change the maximum file size to a small value to force multiple flat 626 // files with the test data set and replace the file-related functions 627 // to make use of mock files in memory. This allows injection of 628 // various file-related errors. 629 store := idb.(*db).store 630 store.maxBlockFileSize = 1024 // 1KiB 631 store.openWriteFileFunc = func(fileNum uint32) (filer, error) { 632 if file, ok := tc.files[fileNum]; ok { 633 // "Reopen" the file. 634 file.Lock() 635 mock := file.file.(*mockFile) 636 mock.Lock() 637 mock.closed = false 638 mock.Unlock() 639 file.Unlock() 640 return mock, nil 641 } 642 643 // Limit the max size of the mock file as specified in the test 644 // context. 645 maxSize := int64(-1) 646 if maxFileSize, ok := tc.maxFileSizes[fileNum]; ok { 647 maxSize = int64(maxFileSize) 648 } 649 file := &mockFile{maxSize: int64(maxSize)} 650 tc.files[fileNum] = &lockableFile{file: file} 651 return file, nil 652 } 653 store.openFileFunc = func(fileNum uint32) (*lockableFile, error) { 654 // Force error when trying to open max file num. 655 if fileNum == ^uint32(0) { 656 return nil, makeDbErr(database.ErrDriverSpecific, 657 "test", nil) 658 } 659 if file, ok := tc.files[fileNum]; ok { 660 // "Reopen" the file. 661 file.Lock() 662 mock := file.file.(*mockFile) 663 mock.Lock() 664 mock.closed = false 665 mock.Unlock() 666 file.Unlock() 667 return file, nil 668 } 669 file := &lockableFile{file: &mockFile{}} 670 tc.files[fileNum] = file 671 return file, nil 672 } 673 store.deleteFileFunc = func(fileNum uint32) error { 674 if file, ok := tc.files[fileNum]; ok { 675 file.Lock() 676 file.file.Close() 677 file.Unlock() 678 delete(tc.files, fileNum) 679 return nil 680 } 681 682 str := fmt.Sprintf("file %d does not exist", fileNum) 683 return makeDbErr(database.ErrDriverSpecific, str, nil) 684 } 685 686 // Load the test blocks and save in the test context for use throughout 687 // the tests. 688 blocks, err := loadBlocks(t, blockDataFile, blockDataNet) 689 if err != nil { 690 t.Errorf("loadBlocks: Unexpected error: %v", err) 691 return 692 } 693 tc.blocks = blocks 694 695 // Test various failures paths when writing to the block files. 696 if !testWriteFailures(tc) { 697 return 698 } 699 700 // Test various file-related issues such as closed and missing files. 701 if !testBlockFileErrors(tc) { 702 return 703 } 704 705 // Test various corruption scenarios. 706 testCorruption(tc) 707 }