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