|
6 | 6 | "io" |
7 | 7 | "os" |
8 | 8 | "path/filepath" |
| 9 | + "runtime" |
9 | 10 | "sync" |
10 | 11 | "time" |
11 | 12 |
|
@@ -109,6 +110,63 @@ type headerFile struct { |
109 | 110 | file File |
110 | 111 | } |
111 | 112 |
|
| 113 | +// truncateHeaders truncates one or more headers from the end of the header |
| 114 | +// file. This can be used in the case of a re-org to remove headers from the end |
| 115 | +// of the main chain. |
| 116 | +// |
| 117 | +// The numHeaders parameter specifies how many headers to truncate. If |
| 118 | +// numHeaders is 1, this is equivalent to the old singleTruncate behavior. |
| 119 | +// |
| 120 | +// This function handles platform-specific differences in file truncation. On |
| 121 | +// Windows, the file is closed, truncated, and reopened due to Windows |
| 122 | +// limitations on truncating open files. On other platforms, the file is |
| 123 | +// truncated directly without closing. |
| 124 | +func (h *headerFile) truncateHeaders(numHeaders uint32, |
| 125 | + headerType HeaderType) error { |
| 126 | + |
| 127 | + // If numHeaders is 0, treat it as a no-op and return no error. |
| 128 | + if numHeaders == 0 { |
| 129 | + return nil |
| 130 | + } |
| 131 | + |
| 132 | + // In order to truncate the file, we'll need to grab the absolute size |
| 133 | + // of the file as it stands currently. |
| 134 | + fileInfo, err := h.file.Stat() |
| 135 | + if err != nil { |
| 136 | + return err |
| 137 | + } |
| 138 | + fileSize := fileInfo.Size() |
| 139 | + |
| 140 | + // Calculate the total bytes to truncate based on number of headers. |
| 141 | + headerTypeSize, err := headerType.Size() |
| 142 | + if err != nil { |
| 143 | + return err |
| 144 | + } |
| 145 | + truncateLength := int64(numHeaders) * int64(headerTypeSize) |
| 146 | + |
| 147 | + // Finally, we'll use both of these values to calculate the new size of |
| 148 | + // the file and truncate it accordingly. |
| 149 | + newSize := fileSize - truncateLength |
| 150 | + |
| 151 | + // On Windows, we need to close, truncate, and reopen the file. |
| 152 | + if runtime.GOOS == "windows" { |
| 153 | + fileName := h.file.Name() |
| 154 | + if err = h.file.Close(); err != nil { |
| 155 | + return err |
| 156 | + } |
| 157 | + |
| 158 | + if err = os.Truncate(fileName, newSize); err != nil { |
| 159 | + return err |
| 160 | + } |
| 161 | + |
| 162 | + fileFlags := os.O_RDWR | os.O_APPEND | os.O_CREATE |
| 163 | + h.file, err = os.OpenFile(fileName, fileFlags, 0644) |
| 164 | + return err |
| 165 | + } |
| 166 | + |
| 167 | + return h.file.Truncate(newSize) |
| 168 | +} |
| 169 | + |
112 | 170 | // headerStore combines a on-disk set of headers within a flat file in addition |
113 | 171 | // to a database which indexes that flat file. Together, these two abstractions |
114 | 172 | // can be used in order to build an indexed header store for any type of |
@@ -247,12 +305,9 @@ func NewBlockHeaderStore(filePath string, db walletdb.DB, |
247 | 305 |
|
248 | 306 | // Otherwise, we'll need to truncate the file until it matches the |
249 | 307 | // current index tip. |
250 | | - for fileHeight > tipHeight { |
251 | | - if err := bhs.singleTruncate(); err != nil { |
252 | | - return nil, err |
253 | | - } |
254 | | - |
255 | | - fileHeight-- |
| 308 | + err = bhs.truncateHeaders(fileHeight-tipHeight, bhs.indexType) |
| 309 | + if err != nil { |
| 310 | + return nil, err |
256 | 311 | } |
257 | 312 |
|
258 | 313 | return bhs, nil |
@@ -362,13 +417,12 @@ func (h *blockHeaderStore) RollbackLastBlock() (*BlockStamp, error) { |
362 | 417 | if err != nil { |
363 | 418 | return nil, err |
364 | 419 | } |
365 | | - |
366 | 420 | prevHeaderHash := prevHeader.BlockHash() |
367 | 421 |
|
368 | 422 | // Now that we have the information we need to return from this |
369 | 423 | // function, we can now truncate the header file, and then use the hash |
370 | 424 | // of the prevHeader to set the proper index chain tip. |
371 | | - if err := h.singleTruncate(); err != nil { |
| 425 | + if err := h.truncateHeaders(1, h.indexType); err != nil { |
372 | 426 | return nil, err |
373 | 427 | } |
374 | 428 | if err := h.truncateIndex(&prevHeaderHash, true); err != nil { |
@@ -767,16 +821,11 @@ func NewFilterHeaderStore(filePath string, db walletdb.DB, |
767 | 821 |
|
768 | 822 | // Otherwise, we'll need to truncate the file until it matches the |
769 | 823 | // current index tip. |
770 | | - for fileHeight > tipHeight { |
771 | | - if err := fhs.singleTruncate(); err != nil { |
772 | | - return nil, err |
773 | | - } |
774 | | - |
775 | | - fileHeight-- |
| 824 | + err = fhs.truncateHeaders(fileHeight-tipHeight, fhs.indexType) |
| 825 | + if err != nil { |
| 826 | + return nil, err |
776 | 827 | } |
777 | 828 |
|
778 | | - // TODO(roasbeef): make above into func |
779 | | - |
780 | 829 | return fhs, nil |
781 | 830 | } |
782 | 831 |
|
@@ -995,7 +1044,7 @@ func (f *filterHeaderStore) RollbackLastBlock( |
995 | 1044 |
|
996 | 1045 | // Now that we have the information we need to return from this |
997 | 1046 | // function, we can now truncate both the header file and the index. |
998 | | - if err := f.singleTruncate(); err != nil { |
| 1047 | + if err := f.truncateHeaders(1, f.indexType); err != nil { |
999 | 1048 | return nil, err |
1000 | 1049 | } |
1001 | 1050 | if err := f.truncateIndex(newTip, false); err != nil { |
|
0 commit comments