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