OSDN Git Service

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