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  }