blockio.go raw

   1  package ffldb
   2  
   3  import (
   4  	"container/list"
   5  	"encoding/binary"
   6  	"fmt"
   7  	"hash/crc32"
   8  	"io"
   9  	"os"
  10  	"path/filepath"
  11  	"sync"
  12  	
  13  	"github.com/p9c/p9/pkg/chainhash"
  14  	"github.com/p9c/p9/pkg/database"
  15  	"github.com/p9c/p9/pkg/wire"
  16  )
  17  
  18  const (
  19  	// The Bitcoin protocol encodes block height as int32, so max number of blocks is 2^31. Max block size per the
  20  	// protocol is 32MiB per block.
  21  	//
  22  	// So the theoretical max at the time this comment was written is 64PiB (pebibytes).
  23  	//
  24  	// With files @ 512MiB each, this would require a maximum of 134,217,728 files. Thus, choose 9 digits of precision
  25  	// for the filenames. An additional benefit is 9 digits provides 10^9 files @ 512MiB each for a total of ~476.84PiB
  26  	// (roughly 7.4 times the current theoretical max), so there is room for the max block size to grow in the future.
  27  	blockFilenameTemplate = "%09d.fdb"
  28  	// maxOpenFiles is the max number of open files to maintain in the open blocks cache. Note that this does not
  29  	// include the current write file, so there will typically be one more than this value open.
  30  	maxOpenFiles = 25
  31  	// maxBlockFileSize is the maximum size for each file used to store blocks.
  32  	//
  33  	// NOTE: The current code uses uint32 for all offsets, so this value must be less than 2^32 (4 GiB). This is also
  34  	// why it's a typed constant.
  35  	maxBlockFileSize uint32 = 512 * 1024 * 1024 // 512 MiB
  36  	// blockLocSize is the number of bytes the serialized block location data that is stored in the block index.
  37  	//
  38  	// The serialized block location format is:
  39  	//
  40  	//  [0:4]  Block file (4 bytes)
  41  	//
  42  	//  [4:8]  File offset (4 bytes)
  43  	//
  44  	//  [8:12] Block length (4 bytes)
  45  	blockLocSize = 12
  46  )
  47  
  48  var (
  49  	// castagnoli houses the Catagnoli polynomial used for CRC-32 checksums.
  50  	castagnoli = crc32.MakeTable(crc32.Castagnoli)
  51  )
  52  
  53  type (
  54  	// filer is an interface which acts very similar to a *os.File and is typically implemented by it. It exists so the
  55  	// test code can provide mock files for properly testing corruption and file system issues.
  56  	filer interface {
  57  		io.Closer
  58  		io.WriterAt
  59  		io.ReaderAt
  60  		Truncate(size int64) error
  61  		Sync() error
  62  	}
  63  	// lockableFile represents a block file on disk that has been opened for either read or read/write access. It also
  64  	// contains a read-write mutex to support multiple concurrent readers.
  65  	lockableFile struct {
  66  		sync.RWMutex
  67  		file filer
  68  	}
  69  	// writeCursor represents the current file and offset of the block file on disk for performing all writes. It also
  70  	// contains a read-write mutex to support multiple concurrent readers which can reuse the file handle.
  71  	writeCursor struct {
  72  		sync.RWMutex
  73  		// curFile is the current block file that will be appended to when writing new blocks.
  74  		curFile *lockableFile
  75  		// curFileNum is the current block file number and is used to allow readers to use the same open file handle.
  76  		curFileNum uint32
  77  		// curOffset is the offset in the current write block file where the next new block will be written.
  78  		curOffset uint32
  79  	}
  80  	// blockStore houses information used to handle reading and writing blocks (and part of blocks) into flat files with
  81  	// support for multiple concurrent readers.
  82  	blockStore struct {
  83  		// network is the specific network to use in the flat files for each block.
  84  		network wire.BitcoinNet
  85  		// basePath is the base path used for the flat block files and metadata.
  86  		basePath string
  87  		// maxBlockFileSize is the maximum size for each file used to store blocks. It is defined on the store so the
  88  		// whitebox tests can override the value.
  89  		maxBlockFileSize uint32
  90  		// The following fields are related to the flat files which hold the actual blocks.
  91  		//
  92  		// The number of open files is limited by maxOpenFiles.
  93  		//
  94  		// obfMutex protects concurrent access to the openBlockFiles map. It is a RWMutex so multiple readers can
  95  		// simultaneously access open files.
  96  		//
  97  		// openBlockFiles houses the open file handles for existing block files which have been opened read-only along
  98  		// with an individual RWMutex. This scheme allows multiple concurrent readers to the same file while preventing
  99  		// the file from being closed out from under them.
 100  		//
 101  		// lruMutex protects concurrent access to the least recently used list and lookup map.
 102  		//
 103  		// openBlocksLRU tracks how the open files are referenced by pushing the most recently used files to the front
 104  		// of the list thereby trickling the least recently used files to end of the list. When a file needs to be
 105  		// closed due to exceeding the the max number of allowed open files, the one at the end of the list is closed.
 106  		//
 107  		// fileNumToLRUElem is a mapping between a specific block file number and the associated list element on the
 108  		// least recently used list.
 109  		//
 110  		// Thus, with the combination of these fields, the database supports concurrent non-blocking reads across
 111  		// multiple and individual files along with intelligently limiting the number of open file handles by closing
 112  		// the least recently used files as needed.
 113  		//
 114  		// NOTE: The locking order used throughout is well-defined and MUST be followed.  Failure to do so could lead to deadlocks.  In particular, the locking order is as follows:
 115  		//
 116  		//   1) obfMutex
 117  		//
 118  		//   2) lruMutex
 119  		//
 120  		//   3) writeCursor mutex
 121  		//
 122  		//   4) specific file mutexes
 123  		//
 124  		// None of the mutexes are required to be locked at the same time, and often aren't. However, if they are to be
 125  		// locked simultaneously, they MUST be locked in the order previously specified.
 126  		//
 127  		// Due to the high performance and multi-read concurrency requirements, write locks should only be held for the
 128  		// minimum time necessary.
 129  		obfMutex         sync.RWMutex
 130  		lruMutex         sync.Mutex
 131  		openBlocksLRU    *list.List // Contains uint32 block file numbers.
 132  		fileNumToLRUElem map[uint32]*list.Element
 133  		openBlockFiles   map[uint32]*lockableFile
 134  		// writeCursor houses the state for the current file and location that new blocks are written to.
 135  		writeCursor *writeCursor
 136  		// These functions are set to openFile, openWriteFile, and deleteFile by default, but are exposed here to allow
 137  		// the whitebox tests to replace them when working with mock files.
 138  		openFileFunc      func(fileNum uint32) (*lockableFile, error)
 139  		openWriteFileFunc func(fileNum uint32) (filer, error)
 140  		deleteFileFunc    func(fileNum uint32) error
 141  	}
 142  	// blockLocation identifies a particular block file and location.
 143  	blockLocation struct {
 144  		blockFileNum uint32
 145  		fileOffset   uint32
 146  		blockLen     uint32
 147  	}
 148  )
 149  
 150  // deserializeBlockLoc deserializes the passed serialized block location information. This is data stored into the block
 151  // index metadata for each block. The serialized data passed to this function MUST be at least blockLocSize bytes or it
 152  // will panic. The error check is avoided here because this information will always be coming from the block index which
 153  // includes a checksum to detect corruption. Thus it is safe to use this unchecked here.
 154  func deserializeBlockLoc(serializedLoc []byte) blockLocation {
 155  	// The serialized block location format is:
 156  	//
 157  	//  [0:4]  Block file (4 bytes)
 158  	//
 159  	//  [4:8]  File offset (4 bytes)
 160  	//
 161  	//  [8:12] Block length (4 bytes)
 162  	return blockLocation{
 163  		blockFileNum: byteOrder.Uint32(serializedLoc[0:4]),
 164  		fileOffset:   byteOrder.Uint32(serializedLoc[4:8]),
 165  		blockLen:     byteOrder.Uint32(serializedLoc[8:12]),
 166  	}
 167  }
 168  
 169  // serializeBlockLoc returns the serialization of the passed block location. This is data to be stored into the block
 170  // index metadata for each block.
 171  func serializeBlockLoc(loc blockLocation) []byte {
 172  	// The serialized block location format is:
 173  	//
 174  	//  [0:4]  Block file (4 bytes)
 175  	//
 176  	//  [4:8]  File offset (4 bytes)
 177  	//
 178  	//  [8:12] Block length (4 bytes)
 179  	var serializedData [12]byte
 180  	byteOrder.PutUint32(serializedData[0:4], loc.blockFileNum)
 181  	byteOrder.PutUint32(serializedData[4:8], loc.fileOffset)
 182  	byteOrder.PutUint32(serializedData[8:12], loc.blockLen)
 183  	return serializedData[:]
 184  }
 185  
 186  // blockFilePath return the file path for the provided block file number.
 187  func blockFilePath(dbPath string, fileNum uint32) string {
 188  	fileName := fmt.Sprintf(blockFilenameTemplate, fileNum)
 189  	return filepath.Join(dbPath, fileName)
 190  }
 191  
 192  // openWriteFile returns a file handle for the passed flat file number in read/write mode. The file will be created if
 193  // needed. It is typically used for the current file that will have all new data appended. Unlike openFile, this
 194  // function does not keep track of the open file and it is not subject to the maxOpenFiles limit.
 195  func (s *blockStore) openWriteFile(fileNum uint32) (filer, error) {
 196  	// The current block file needs to be read-write so it is possible to append to it. Also, it shouldn't be part of
 197  	// the least recently used file.
 198  	filePath := blockFilePath(s.basePath, fileNum)
 199  	file, e := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0666)
 200  	if e != nil {
 201  		str := fmt.Sprintf("failed to open file %q: %v", filePath, e)
 202  		return nil, makeDbErr(database.ErrDriverSpecific, str, e)
 203  	}
 204  	return file, nil
 205  }
 206  
 207  // openFile returns a read-only file handle for the passed flat file number. The function also keeps track of the open
 208  // files, performs least recently used tracking, and limits the number of open files to maxOpenFiles by closing the
 209  // least recently used file as needed.
 210  //
 211  // This function MUST be called with the overall files mutex (s.obfMutex) locked for WRITES.
 212  func (s *blockStore) openFile(fileNum uint32) (*lockableFile, error) {
 213  	// Open the appropriate file as read-only.
 214  	filePath := blockFilePath(s.basePath, fileNum)
 215  	file, e := os.Open(filePath)
 216  	if e != nil {
 217  		return nil, makeDbErr(
 218  			database.ErrDriverSpecific, e.Error(),
 219  			e,
 220  		)
 221  	}
 222  	blockFile := &lockableFile{file: file}
 223  	// Close the least recently used file if the file exceeds the max allowed open files. This is not done until after
 224  	// the file open in case the file fails to open, there is no need to close any files.
 225  	//
 226  	// A write lock is required on the LRU list here to protect against modifications happening as already open files
 227  	// are read from and shuffled to the front of the list.
 228  	//
 229  	// Also, add the file that was just opened to the front of the least recently used list to indicate it is the most
 230  	// recently used file and therefore should be closed last.
 231  	s.lruMutex.Lock()
 232  	lruList := s.openBlocksLRU
 233  	if lruList.Len() >= maxOpenFiles {
 234  		lruFileNum := lruList.Remove(lruList.Back()).(uint32)
 235  		oldBlockFile := s.openBlockFiles[lruFileNum]
 236  		// Close the old file under the write lock for the file in case any readers are currently reading from it so
 237  		// it's not closed out from under them.
 238  		oldBlockFile.Lock()
 239  		_ = oldBlockFile.file.Close()
 240  		oldBlockFile.Unlock()
 241  		delete(s.openBlockFiles, lruFileNum)
 242  		delete(s.fileNumToLRUElem, lruFileNum)
 243  	}
 244  	s.fileNumToLRUElem[fileNum] = lruList.PushFront(fileNum)
 245  	s.lruMutex.Unlock()
 246  	// Store a reference to it in the open block files map.
 247  	s.openBlockFiles[fileNum] = blockFile
 248  	return blockFile, nil
 249  }
 250  
 251  // deleteFile removes the block file for the passed flat file number. The file
 252  // must already be closed and it is the responsibility of the caller to do any
 253  // other state cleanup necessary.
 254  func (s *blockStore) deleteFile(fileNum uint32) (e error) {
 255  	filePath := blockFilePath(s.basePath, fileNum)
 256  	if e := os.Remove(filePath); E.Chk(e) {
 257  		return makeDbErr(database.ErrDriverSpecific, e.Error(), e)
 258  	}
 259  	return nil
 260  }
 261  
 262  // blockFile attempts to return an existing file handle for the passed flat file
 263  // number if it is already open as well as marking it as most recently used. It
 264  // will also open the file when it's not already open subject to the rules
 265  // described in openFile.
 266  //
 267  // NOTE: The returned block file will already have the read lock acquired and
 268  // the caller MUST call .RUnlock() to release it once it has finished all read
 269  // operations. This is necessary because otherwise it would be possible for a
 270  // separate goroutine to close the file after it is returned from here, but
 271  // before the caller has acquired a read lock.
 272  func (s *blockStore) blockFile(fileNum uint32) (*lockableFile, error) {
 273  	// When the requested block file is open for writes, return it.
 274  	wc := s.writeCursor
 275  	wc.RLock()
 276  	if fileNum == wc.curFileNum && wc.curFile.file != nil {
 277  		obf := wc.curFile
 278  		obf.RLock()
 279  		wc.RUnlock()
 280  		return obf, nil
 281  	}
 282  	wc.RUnlock()
 283  	// Try to return an open file under the overall files read lock.
 284  	s.obfMutex.RLock()
 285  	if obf, ok := s.openBlockFiles[fileNum]; ok {
 286  		s.lruMutex.Lock()
 287  		s.openBlocksLRU.MoveToFront(s.fileNumToLRUElem[fileNum])
 288  		s.lruMutex.Unlock()
 289  		obf.RLock()
 290  		s.obfMutex.RUnlock()
 291  		return obf, nil
 292  	}
 293  	s.obfMutex.RUnlock()
 294  	// Since the file isn't open already, need to check the open block files map again under write lock in case multiple
 295  	// readers got here and a separate one is already opening the file.
 296  	s.obfMutex.Lock()
 297  	if obf, ok := s.openBlockFiles[fileNum]; ok {
 298  		obf.RLock()
 299  		s.obfMutex.Unlock()
 300  		return obf, nil
 301  	}
 302  	// The file isn't open, so open it while potentially closing the least recently used one as needed.
 303  	obf, e := s.openFileFunc(fileNum)
 304  	if e != nil {
 305  		s.obfMutex.Unlock()
 306  		return nil, e
 307  	}
 308  	obf.RLock()
 309  	s.obfMutex.Unlock()
 310  	return obf, nil
 311  }
 312  
 313  // writeData is a helper function for writeBlock which writes the provided data at the current write offset and updates
 314  // the write cursor accordingly. The field name parameter is only used when there is an error to provide a nicer error
 315  // message.
 316  //
 317  // The write cursor will be advanced the number of bytes actually written in the event of failure.
 318  //
 319  // NOTE: This function MUST be called with the write cursor current file lock held and must only be called during a
 320  // write transaction so it is effectively locked for writes. Also, the write cursor current file must NOT be nilog.
 321  func (s *blockStore) writeData(data []byte, fieldName string) (e error) {
 322  	wc := s.writeCursor
 323  	n, e := wc.curFile.file.WriteAt(data, int64(wc.curOffset))
 324  	wc.curOffset += uint32(n)
 325  	if e != nil {
 326  		str := fmt.Sprintf(
 327  			"failed to write %s to file %d at "+
 328  				"offset %d: %v", fieldName, wc.curFileNum,
 329  			wc.curOffset-uint32(n), e,
 330  		)
 331  		return makeDbErr(database.ErrDriverSpecific, str, e)
 332  	}
 333  	return nil
 334  }
 335  
 336  // writeBlock appends the specified raw block bytes to the store's write cursor location and increments it accordingly.
 337  // When the block would exceed the max file size for the current flat file, this function will close the current file,
 338  // create the next file, update the write cursor, and write the block to the new file.
 339  //
 340  // The write cursor will also be advanced the number of bytes actually written in the event of failure. Format:
 341  // <network><block length><serialized block><checksum>
 342  func (s *blockStore) writeBlock(rawBlock []byte) (blockLocation, error) {
 343  	// Compute how many bytes will be written.
 344  	//
 345  	// 4 bytes each for block network + 4 bytes for block length + length of raw block + 4 bytes for checksum.
 346  	blockLen := uint32(len(rawBlock))
 347  	fullLen := blockLen + 12
 348  	// Move to the next block file if adding the new block would exceed the max allowed size for the current block file.
 349  	// Also detect overflow to be paranoid, even though it isn't possible currently, numbers might change in the future
 350  	// to make it possible.
 351  	//
 352  	// NOTE: The writeCursor.offset field isn't protected by the mutex since it's only read/changed during this function
 353  	// which can only be called during a write transaction, of which there can be only one at a time.
 354  	wc := s.writeCursor
 355  	finalOffset := wc.curOffset + fullLen
 356  	if finalOffset < wc.curOffset || finalOffset > s.maxBlockFileSize {
 357  		// This is done under the write cursor lock since the curFileNum field is accessed elsewhere by readers.
 358  		//
 359  		// Close the current write file to force a read-only reopen with LRU tracking. The close is done under the write
 360  		// lock for the file to prevent it from being closed out from under any readers currently reading from it.
 361  		wc.Lock()
 362  		wc.curFile.Lock()
 363  		if wc.curFile.file != nil {
 364  			_ = wc.curFile.file.Close()
 365  			wc.curFile.file = nil
 366  		}
 367  		wc.curFile.Unlock()
 368  		// Start writes into next file.
 369  		wc.curFileNum++
 370  		wc.curOffset = 0
 371  		wc.Unlock()
 372  	}
 373  	// All writes are done under the write lock for the file to ensure any readers are finished and blocked first.
 374  	wc.curFile.Lock()
 375  	defer wc.curFile.Unlock()
 376  	// Open the current file if needed.
 377  	//
 378  	// This will typically only be the case when moving to the next file to write to or on initial database load.
 379  	//
 380  	// However, it might also be the case if rollbacks happened after file writes started during a transaction commit.
 381  	if wc.curFile.file == nil {
 382  		file, e := s.openWriteFileFunc(wc.curFileNum)
 383  		if e != nil {
 384  			return blockLocation{}, e
 385  		}
 386  		wc.curFile.file = file
 387  	}
 388  	// Bitcoin network.
 389  	origOffset := wc.curOffset
 390  	hasher := crc32.New(castagnoli)
 391  	var scratch [4]byte
 392  	byteOrder.PutUint32(scratch[:], uint32(s.network))
 393  	if e := s.writeData(scratch[:], "network"); E.Chk(e) {
 394  		return blockLocation{}, e
 395  	}
 396  	_, _ = hasher.Write(scratch[:])
 397  	// Block length.
 398  	byteOrder.PutUint32(scratch[:], blockLen)
 399  	if e := s.writeData(scratch[:], "block length"); E.Chk(e) {
 400  		return blockLocation{}, e
 401  	}
 402  	_, _ = hasher.Write(scratch[:])
 403  	// Serialized block.
 404  	if e := s.writeData(rawBlock[:], "block"); E.Chk(e) {
 405  		return blockLocation{}, e
 406  	}
 407  	_, _ = hasher.Write(rawBlock)
 408  	// Castagnoli CRC-32 as a checksum of all the previous.
 409  	if e := s.writeData(hasher.Sum(nil), "checksum"); E.Chk(e) {
 410  		return blockLocation{}, e
 411  	}
 412  	loc := blockLocation{
 413  		blockFileNum: wc.curFileNum,
 414  		fileOffset:   origOffset,
 415  		blockLen:     fullLen,
 416  	}
 417  	return loc, nil
 418  }
 419  
 420  // readBlock reads the specified block record and returns the serialized block. It ensures the integrity of the block
 421  // data by checking that the serialized network matches the current network associated with the block store and
 422  // comparing the calculated checksum against the one stored in the flat file.
 423  //
 424  // This function also automatically handles all file management such as opening and closing files as necessary to stay
 425  // within the maximum allowed open files limit.
 426  //
 427  // Returns ErrDriverSpecific if the data fails to read for any reason and ErrCorruption if the checksum of the read data
 428  // doesn't match the checksum read from the file. Format: <network><block length><serialized block><checksum>
 429  func (s *blockStore) readBlock(hash *chainhash.Hash, loc blockLocation) ([]byte, error) {
 430  	// Get the referenced block file handle opening the file as needed. The function also handles closing files as
 431  	// needed to avoid going over the max allowed open files.
 432  	blockFile, e := s.blockFile(loc.blockFileNum)
 433  	if e != nil {
 434  		return nil, e
 435  	}
 436  	serializedData := make([]byte, loc.blockLen)
 437  	n, e := blockFile.file.ReadAt(serializedData, int64(loc.fileOffset))
 438  	blockFile.RUnlock()
 439  	if e != nil {
 440  		str := fmt.Sprintf(
 441  			"failed to read block %s from file %d, "+
 442  				"offset %d: %v", hash, loc.blockFileNum, loc.fileOffset,
 443  			e,
 444  		)
 445  		return nil, makeDbErr(database.ErrDriverSpecific, str, e)
 446  	}
 447  	// Calculate the checksum of the read data and ensure it matches the serialized checksum. This will detect any data
 448  	// corruption in the flat file without having to do much more expensive merkle root calculations on the loaded
 449  	// block.
 450  	serializedChecksum := binary.BigEndian.Uint32(serializedData[n-4:])
 451  	calculatedChecksum := crc32.Checksum(serializedData[:n-4], castagnoli)
 452  	if serializedChecksum != calculatedChecksum {
 453  		str := fmt.Sprintf(
 454  			"block data for block %s checksum "+
 455  				"does not match - got %x, want %x", hash,
 456  			calculatedChecksum, serializedChecksum,
 457  		)
 458  		return nil, makeDbErr(database.ErrCorruption, str, nil)
 459  	}
 460  	// The network associated with the block must match the current active network, otherwise somebody probably put the
 461  	// block files for the wrong network in the directory.
 462  	serializedNet := byteOrder.Uint32(serializedData[:4])
 463  	if serializedNet != uint32(s.network) {
 464  		str := fmt.Sprintf(
 465  			"block data for block %s is for the "+
 466  				"wrong network - got %d, want %d", hash, serializedNet,
 467  			uint32(s.network),
 468  		)
 469  		return nil, makeDbErr(database.ErrDriverSpecific, str, nil)
 470  	}
 471  	// The raw block excludes the network, length of the block, and checksum.
 472  	return serializedData[8 : n-4], nil
 473  }
 474  
 475  // readBlockRegion reads the specified amount of data at the provided offset for a given block location. The offset is
 476  // relative to the start of the serialized block (as opposed to the beginning of the block record).
 477  //
 478  // This function automatically handles all file management such as opening and closing files as necessary to stay within
 479  // the maximum allowed open files limit.
 480  //
 481  // Returns ErrDriverSpecific if the data fails to read for any reason.
 482  func (s *blockStore) readBlockRegion(loc blockLocation, offset, numBytes uint32) ([]byte, error) {
 483  	// Get the referenced block file handle opening the file as needed. The function also handles closing files as
 484  	// needed to avoid going over the max allowed open files.
 485  	blockFile, e := s.blockFile(loc.blockFileNum)
 486  	if e != nil {
 487  		return nil, e
 488  	}
 489  	// Regions are offsets into the actual block, however the serialized data for a block includes an initial 4 bytes
 490  	// for network + 4 bytes for block length. Thus, add 8 bytes to adjust.
 491  	readOffset := loc.fileOffset + 8 + offset
 492  	serializedData := make([]byte, numBytes)
 493  	_, e = blockFile.file.ReadAt(serializedData, int64(readOffset))
 494  	blockFile.RUnlock()
 495  	if e != nil {
 496  		str := fmt.Sprintf(
 497  			"failed to read region from block file %d, "+
 498  				"offset %d, len %d: %v", loc.blockFileNum, readOffset,
 499  			numBytes, e,
 500  		)
 501  		return nil, makeDbErr(database.ErrDriverSpecific, str, e)
 502  	}
 503  	return serializedData, nil
 504  }
 505  
 506  // syncBlocks performs a file system sync on the flat file associated with the store's current write cursor. It is safe
 507  // to call even when there is not a current write file in which case it will have no effect.
 508  //
 509  // This is used when flushing cached metadata updates to disk to ensure all the block data is fully written before
 510  // updating the metadata. This ensures the metadata and block data can be properly reconciled in failure scenarios.
 511  func (s *blockStore) syncBlocks() (e error) {
 512  	wc := s.writeCursor
 513  	wc.RLock()
 514  	defer wc.RUnlock()
 515  	// Nothing to do if there is no current file associated with the write cursor.
 516  	wc.curFile.RLock()
 517  	defer wc.curFile.RUnlock()
 518  	if wc.curFile.file == nil {
 519  		return nil
 520  	}
 521  	// Sync the file to disk.
 522  	if e := wc.curFile.file.Sync(); E.Chk(e) {
 523  		str := fmt.Sprintf(
 524  			"failed to sync file %d: %v", wc.curFileNum,
 525  			e,
 526  		)
 527  		return makeDbErr(database.ErrDriverSpecific, str, e)
 528  	}
 529  	return nil
 530  }
 531  
 532  // handleRollback rolls the block files on disk back to the provided file number and offset. This involves potentially
 533  // deleting and truncating the files that were partially written.
 534  //
 535  // There are effectively two scenarios to consider here:
 536  //
 537  //   1) Transient write failures from which recovery is possible
 538  //
 539  //   2) More permanent failures such as hard disk death and/or removal
 540  //
 541  // In either case, the write cursor will be repositioned to the old block file offset regardless of any other errors
 542  // that occur while attempting to undo writes.
 543  //
 544  // For the first scenario, this will lead to any data which failed to be undone being overwritten and thus behaves as
 545  // desired as the system continues to run. For the second scenario, the metadata which stores the current write cursor
 546  // position within the block files will not have been updated yet and thus if the system eventually recovers (perhaps
 547  // the hard drive is reconnected), it will also lead to any data which failed to be undone being overwritten and thus
 548  // behaves as desired.
 549  //
 550  // Therefore, any errors are simply logged at a warning level rather than being returned since there is nothing more
 551  // that could be done about it anyways.
 552  func (s *blockStore) handleRollback(oldBlockFileNum, oldBlockOffset uint32) {
 553  	// Grab the write cursor mutex since it is modified throughout this function.
 554  	wc := s.writeCursor
 555  	wc.Lock()
 556  	defer wc.Unlock()
 557  	// Nothing to do if the rollback point is the same as the current write cursor.
 558  	if wc.curFileNum == oldBlockFileNum && wc.curOffset == oldBlockOffset {
 559  		return
 560  	}
 561  	// Regardless of any failures that happen below, reposition the write cursor to the old block file and offset.
 562  	defer func() {
 563  		wc.curFileNum = oldBlockFileNum
 564  		wc.curOffset = oldBlockOffset
 565  	}()
 566  	D.F(
 567  		"ROLLBACK: Rolling back to file %d, offset %d",
 568  		oldBlockFileNum,
 569  		oldBlockOffset,
 570  	)
 571  	// Close the current write file if it needs to be deleted. Then delete all files that are newer than the provided
 572  	// rollback file while also moving the write cursor file backwards accordingly.
 573  	if wc.curFileNum > oldBlockFileNum {
 574  		wc.curFile.Lock()
 575  		if wc.curFile.file != nil {
 576  			_ = wc.curFile.file.Close()
 577  			wc.curFile.file = nil
 578  		}
 579  		wc.curFile.Unlock()
 580  	}
 581  	for ; wc.curFileNum > oldBlockFileNum; wc.curFileNum-- {
 582  		if e := s.deleteFileFunc(wc.curFileNum); E.Chk(e) {
 583  			W.Ln(
 584  				"ROLLBACK: Failed to delete block file number %d: %v %s",
 585  				wc.curFileNum, e,
 586  			)
 587  			return
 588  		}
 589  	}
 590  	// Open the file for the current write cursor if needed.
 591  	wc.curFile.Lock()
 592  	if wc.curFile.file == nil {
 593  		obf, e := s.openWriteFileFunc(wc.curFileNum)
 594  		if e != nil {
 595  			wc.curFile.Unlock()
 596  			D.Ln("ROLLBACK:", e)
 597  			return
 598  		}
 599  		wc.curFile.file = obf
 600  	}
 601  	// Truncate the to the provided rollback offset.
 602  	if e := wc.curFile.file.Truncate(int64(oldBlockOffset)); E.Chk(e) {
 603  		wc.curFile.Unlock()
 604  		W.Ln(
 605  			"ROLLBACK: Failed to truncate file %d: %v %s",
 606  			wc.curFileNum,
 607  			e,
 608  		)
 609  		return
 610  	}
 611  	// Sync the file to disk.
 612  	e := wc.curFile.file.Sync()
 613  	wc.curFile.Unlock()
 614  	if e != nil {
 615  		W.Ln(
 616  			"ROLLBACK: Failed to sync file %d: %v %s",
 617  			wc.curFileNum,
 618  			e,
 619  		)
 620  		return
 621  	}
 622  }
 623  
 624  // scanBlockFiles searches the database directory for all flat block files to find the end of the most recent file.
 625  //
 626  // This position is considered the current write cursor which is also stored in the metadata.
 627  //
 628  // Thus, it is used to detect unexpected shutdowns in the middle of writes so the block files can be reconciled.
 629  func scanBlockFiles(dbPath string) (int, uint32) {
 630  	lastFile := -1
 631  	fileLen := uint32(0)
 632  	for i := 0; ; i++ {
 633  		filePath := blockFilePath(dbPath, uint32(i))
 634  		st, e := os.Stat(filePath)
 635  		if e != nil {
 636  			T.Ln(e)
 637  			break
 638  		}
 639  		lastFile = i
 640  		fileLen = uint32(st.Size())
 641  	}
 642  	T.F("Scan found latest block file #%d with length %d", lastFile, fileLen)
 643  	return lastFile, fileLen
 644  }
 645  
 646  // newBlockStore returns a new block store with the current block file number and offset set and all fields initialized.
 647  func newBlockStore(basePath string, network wire.BitcoinNet) *blockStore {
 648  	// Look for the end of the latest block to file to determine what the write cursor position is from the viewpoint of
 649  	// the block files on disk.
 650  	fileNum, fileOff := scanBlockFiles(basePath)
 651  	if fileNum == -1 {
 652  		fileNum = 0
 653  		fileOff = 0
 654  	}
 655  	store := &blockStore{
 656  		network:          network,
 657  		basePath:         basePath,
 658  		maxBlockFileSize: maxBlockFileSize,
 659  		openBlockFiles:   make(map[uint32]*lockableFile),
 660  		openBlocksLRU:    list.New(),
 661  		fileNumToLRUElem: make(map[uint32]*list.Element),
 662  		writeCursor: &writeCursor{
 663  			curFile:    &lockableFile{},
 664  			curFileNum: uint32(fileNum),
 665  			curOffset:  fileOff,
 666  		},
 667  	}
 668  	store.openFileFunc = store.openFile
 669  	store.openWriteFileFunc = store.openWriteFile
 670  	store.deleteFileFunc = store.deleteFile
 671  	return store
 672  }
 673