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