github.com/btcsuite/btcd@v0.24.0/database/ffldb/reconcile.go (about) 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 package ffldb 6 7 import ( 8 "fmt" 9 "hash/crc32" 10 11 "github.com/btcsuite/btcd/database" 12 ) 13 14 // The serialized write cursor location format is: 15 // 16 // [0:4] Block file (4 bytes) 17 // [4:8] File offset (4 bytes) 18 // [8:12] Castagnoli CRC-32 checksum (4 bytes) 19 20 // serializeWriteRow serialize the current block file and offset where new 21 // will be written into a format suitable for storage into the metadata. 22 func serializeWriteRow(curBlockFileNum, curFileOffset uint32) []byte { 23 var serializedRow [12]byte 24 byteOrder.PutUint32(serializedRow[0:4], curBlockFileNum) 25 byteOrder.PutUint32(serializedRow[4:8], curFileOffset) 26 checksum := crc32.Checksum(serializedRow[:8], castagnoli) 27 byteOrder.PutUint32(serializedRow[8:12], checksum) 28 return serializedRow[:] 29 } 30 31 // deserializeWriteRow deserializes the write cursor location stored in the 32 // metadata. Returns ErrCorruption if the checksum of the entry doesn't match. 33 func deserializeWriteRow(writeRow []byte) (uint32, uint32, error) { 34 // Ensure the checksum matches. The checksum is at the end. 35 gotChecksum := crc32.Checksum(writeRow[:8], castagnoli) 36 wantChecksumBytes := writeRow[8:12] 37 wantChecksum := byteOrder.Uint32(wantChecksumBytes) 38 if gotChecksum != wantChecksum { 39 str := fmt.Sprintf("metadata for write cursor does not match "+ 40 "the expected checksum - got %d, want %d", gotChecksum, 41 wantChecksum) 42 return 0, 0, makeDbErr(database.ErrCorruption, str, nil) 43 } 44 45 fileNum := byteOrder.Uint32(writeRow[0:4]) 46 fileOffset := byteOrder.Uint32(writeRow[4:8]) 47 return fileNum, fileOffset, nil 48 } 49 50 // reconcileDB reconciles the metadata with the flat block files on disk. It 51 // will also initialize the underlying database if the create flag is set. 52 func reconcileDB(pdb *db, create bool) (database.DB, error) { 53 // Perform initial internal bucket and value creation during database 54 // creation. 55 if create { 56 if err := initDB(pdb.cache.ldb); err != nil { 57 return nil, err 58 } 59 } 60 61 // Load the current write cursor position from the metadata. 62 var curFileNum, curOffset uint32 63 err := pdb.View(func(tx database.Tx) error { 64 writeRow := tx.Metadata().Get(writeLocKeyName) 65 if writeRow == nil { 66 str := "write cursor does not exist" 67 return makeDbErr(database.ErrCorruption, str, nil) 68 } 69 70 var err error 71 curFileNum, curOffset, err = deserializeWriteRow(writeRow) 72 return err 73 }) 74 if err != nil { 75 return nil, err 76 } 77 78 // When the write cursor position found by scanning the block files on 79 // disk is AFTER the position the metadata believes to be true, truncate 80 // the files on disk to match the metadata. This can be a fairly common 81 // occurrence in unclean shutdown scenarios while the block files are in 82 // the middle of being written. Since the metadata isn't updated until 83 // after the block data is written, this is effectively just a rollback 84 // to the known good point before the unclean shutdown. 85 wc := pdb.store.writeCursor 86 if wc.curFileNum > curFileNum || (wc.curFileNum == curFileNum && 87 wc.curOffset > curOffset) { 88 89 log.Info("Detected unclean shutdown - Repairing...") 90 log.Debugf("Metadata claims file %d, offset %d. Block data is "+ 91 "at file %d, offset %d", curFileNum, curOffset, 92 wc.curFileNum, wc.curOffset) 93 pdb.store.handleRollback(curFileNum, curOffset) 94 log.Infof("Database sync complete") 95 } 96 97 // When the write cursor position found by scanning the block files on 98 // disk is BEFORE the position the metadata believes to be true, return 99 // a corruption error. Since sync is called after each block is written 100 // and before the metadata is updated, this should only happen in the 101 // case of missing, deleted, or truncated block files, which generally 102 // is not an easily recoverable scenario. In the future, it might be 103 // possible to rescan and rebuild the metadata from the block files, 104 // however, that would need to happen with coordination from a higher 105 // layer since it could invalidate other metadata. 106 if wc.curFileNum < curFileNum || (wc.curFileNum == curFileNum && 107 wc.curOffset < curOffset) { 108 109 str := fmt.Sprintf("metadata claims file %d, offset %d, but "+ 110 "block data is at file %d, offset %d", curFileNum, 111 curOffset, wc.curFileNum, wc.curOffset) 112 log.Warnf("***Database corruption detected***: %v", str) 113 return nil, makeDbErr(database.ErrCorruption, str, nil) 114 } 115 116 return pdb, nil 117 }