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