• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.94
/chanbackup/backupfile.go
1
package chanbackup
2

3
import (
4
        "fmt"
5
        "io"
6
        "os"
7
        "path/filepath"
8
        "time"
9

10
        "github.com/lightningnetwork/lnd/keychain"
11
        "github.com/lightningnetwork/lnd/lnrpc"
12
)
13

14
const (
15
        // DefaultBackupFileName is the default name of the auto updated static
16
        // channel backup fie.
17
        DefaultBackupFileName = "channel.backup"
18

19
        // DefaultTempBackupFileName is the default name of the temporary SCB
20
        // file that we'll use to atomically update the primary back up file
21
        // when new channel are detected.
22
        DefaultTempBackupFileName = "temp-dont-use.backup"
23

24
        // DefaultChanBackupArchiveDirName is the default name of the directory
25
        // that we'll use to store old channel backups.
26
        DefaultChanBackupArchiveDirName = "chan-backup-archives"
27
)
28

29
var (
30
        // ErrNoBackupFileExists is returned if caller attempts to call
31
        // UpdateAndSwap with the file name not set.
32
        ErrNoBackupFileExists = fmt.Errorf("back up file name not set")
33

34
        // ErrNoTempBackupFile is returned if caller attempts to call
35
        // UpdateAndSwap with the temp back up file name not set.
36
        ErrNoTempBackupFile = fmt.Errorf("temp backup file not set")
37
)
38

39
// MultiFile represents a file on disk that a caller can use to read the packed
40
// multi backup into an unpacked one, and also atomically update the contents
41
// on disk once new channels have been opened, and old ones closed. This struct
42
// relies on an atomic file rename property which most widely use file systems
43
// have.
44
type MultiFile struct {
45
        // fileName is the file name of the main back up file.
46
        fileName string
47

48
        // tempFileName is the name of the file that we'll use to stage a new
49
        // packed multi-chan backup, and the rename to the main back up file.
50
        tempFileName string
51

52
        // tempFile is an open handle to the temp back up file.
53
        tempFile *os.File
54

55
        // archiveDir is the directory where we'll store old channel backups.
56
        archiveDir string
57

58
        // noBackupArchive indicates whether old backups should be deleted
59
        // rather than archived.
60
        noBackupArchive bool
61
}
62

63
// NewMultiFile create a new multi-file instance at the target location on the
64
// file system.
65
func NewMultiFile(fileName string, noBackupArchive bool) *MultiFile {
3✔
66
        // We'll our temporary backup file in the very same directory as the
3✔
67
        // main backup file.
3✔
68
        backupFileDir := filepath.Dir(fileName)
3✔
69
        tempFileName := filepath.Join(
3✔
70
                backupFileDir, DefaultTempBackupFileName,
3✔
71
        )
3✔
72
        archiveDir := filepath.Join(
3✔
73
                backupFileDir, DefaultChanBackupArchiveDirName,
3✔
74
        )
3✔
75

3✔
76
        return &MultiFile{
3✔
77
                fileName:        fileName,
3✔
78
                tempFileName:    tempFileName,
3✔
79
                archiveDir:      archiveDir,
3✔
80
                noBackupArchive: noBackupArchive,
3✔
81
        }
3✔
82
}
3✔
83

84
// UpdateAndSwap will attempt write a new temporary backup file to disk with
85
// the newBackup encoded, then atomically swap (via rename) the old file for
86
// the new file by updating the name of the new file to the old. It also checks
87
// if the old file should be archived first before swapping it.
88
func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
3✔
89
        // If the main backup file isn't set, then we can't proceed.
3✔
90
        if b.fileName == "" {
3✔
UNCOV
91
                return ErrNoBackupFileExists
×
UNCOV
92
        }
×
93

94
        log.Infof("Updating backup file at %v", b.fileName)
3✔
95

3✔
96
        // If the old back up file still exists, then we'll delete it before
3✔
97
        // proceeding.
3✔
98
        if _, err := os.Stat(b.tempFileName); err == nil {
3✔
UNCOV
99
                log.Infof("Found old temp backup @ %v, removing before swap",
×
UNCOV
100
                        b.tempFileName)
×
UNCOV
101

×
UNCOV
102
                err = os.Remove(b.tempFileName)
×
UNCOV
103
                if err != nil {
×
104
                        return fmt.Errorf("unable to remove temp "+
×
105
                                "backup file: %v", err)
×
106
                }
×
107
        }
108

109
        // Now that we know the staging area is clear, we'll create the new
110
        // temporary back up file.
111
        var err error
3✔
112
        b.tempFile, err = os.Create(b.tempFileName)
3✔
113
        if err != nil {
6✔
114
                return fmt.Errorf("unable to create temp file: %w", err)
3✔
115
        }
3✔
116

117
        // With the file created, we'll write the new packed multi backup and
118
        // remove the temporary file all together once this method exits.
119
        _, err = b.tempFile.Write([]byte(newBackup))
3✔
120
        if err != nil {
3✔
121
                return fmt.Errorf("unable to write backup to temp file: %w",
×
122
                        err)
×
123
        }
×
124
        if err := b.tempFile.Sync(); err != nil {
3✔
125
                return fmt.Errorf("unable to sync temp file: %w", err)
×
126
        }
×
127
        defer os.Remove(b.tempFileName)
3✔
128

3✔
129
        log.Infof("Swapping old multi backup file from %v to %v",
3✔
130
                b.tempFileName, b.fileName)
3✔
131

3✔
132
        // Before we rename the swap (atomic name swap), we'll make
3✔
133
        // sure to close the current file as some OSes don't support
3✔
134
        // renaming a file that's already open (Windows).
3✔
135
        if err := b.tempFile.Close(); err != nil {
3✔
136
                return fmt.Errorf("unable to close file: %w", err)
×
137
        }
×
138

139
        // Archive the old channel backup file before replacing.
140
        if err := b.createArchiveFile(); err != nil {
3✔
141
                return fmt.Errorf("unable to archive old channel "+
×
142
                        "backup file: %w", err)
×
143
        }
×
144

145
        // Finally, we'll attempt to atomically rename the temporary file to
146
        // the main back up file. If this succeeds, then we'll only have a
147
        // single file on disk once this method exits.
148
        return os.Rename(b.tempFileName, b.fileName)
3✔
149
}
150

151
// ExtractMulti attempts to extract the packed multi backup we currently point
152
// to into an unpacked version. This method will fail if no backup file
153
// currently exists as the specified location.
154
func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
3✔
155
        var err error
3✔
156

3✔
157
        // We'll return an error if the main file isn't currently set.
3✔
158
        if b.fileName == "" {
3✔
UNCOV
159
                return nil, ErrNoBackupFileExists
×
UNCOV
160
        }
×
161

162
        // Now that we've confirmed the target file is populated, we'll read
163
        // all the contents of the file. This function ensures that file is
164
        // always closed, even if we can't read the contents.
165
        multiBytes, err := os.ReadFile(b.fileName)
3✔
166
        if err != nil {
6✔
167
                return nil, err
3✔
168
        }
3✔
169

170
        // Finally, we'll attempt to unpack the file and return the unpack
171
        // version to the caller.
172
        packedMulti := PackedMulti(multiBytes)
3✔
173
        return packedMulti.Unpack(keyChain)
3✔
174
}
175

176
// createArchiveFile creates an archive file with a timestamped name in the
177
// specified archive directory, and copies the contents of the main backup file
178
// to the new archive file.
179
func (b *MultiFile) createArchiveFile() error {
3✔
180
        // User can skip archiving of old backup files to save disk space.
3✔
181
        if b.noBackupArchive {
3✔
UNCOV
182
                log.Debug("Skipping archive of old backup file as configured")
×
UNCOV
183
                return nil
×
UNCOV
184
        }
×
185

186
        // Check for old channel backup file.
187
        oldFileExists := lnrpc.FileExists(b.fileName)
3✔
188
        if !oldFileExists {
6✔
189
                log.Debug("No old channel backup file to archive")
3✔
190
                return nil
3✔
191
        }
3✔
192

193
        log.Infof("Archiving old channel backup to %v", b.archiveDir)
3✔
194

3✔
195
        // Generate archive file path with timestamped name.
3✔
196
        baseFileName := filepath.Base(b.fileName)
3✔
197
        timestamp := time.Now().Format("2006-01-02-15-04-05")
3✔
198

3✔
199
        archiveFileName := fmt.Sprintf("%s-%s", baseFileName, timestamp)
3✔
200
        archiveFilePath := filepath.Join(b.archiveDir, archiveFileName)
3✔
201

3✔
202
        oldBackupFile, err := os.Open(b.fileName)
3✔
203
        if err != nil {
3✔
204
                return fmt.Errorf("unable to open old channel backup file: "+
×
205
                        "%w", err)
×
206
        }
×
207
        defer func() {
6✔
208
                err := oldBackupFile.Close()
3✔
209
                if err != nil {
3✔
210
                        log.Errorf("unable to close old channel backup file: "+
×
211
                                "%v", err)
×
212
                }
×
213
        }()
214

215
        // Ensure the archive directory exists. If it doesn't we create it.
216
        const archiveDirPermissions = 0o700
3✔
217
        err = os.MkdirAll(b.archiveDir, archiveDirPermissions)
3✔
218
        if err != nil {
3✔
219
                return fmt.Errorf("unable to create archive directory: %w", err)
×
220
        }
×
221

222
        // Create new archive file.
223
        archiveFile, err := os.Create(archiveFilePath)
3✔
224
        if err != nil {
3✔
UNCOV
225
                return fmt.Errorf("unable to create archive file: %w", err)
×
UNCOV
226
        }
×
227
        defer func() {
6✔
228
                err := archiveFile.Close()
3✔
229
                if err != nil {
3✔
230
                        log.Errorf("unable to close archive file: %v", err)
×
231
                }
×
232
        }()
233

234
        // Copy contents of old backup to the newly created archive files.
235
        _, err = io.Copy(archiveFile, oldBackupFile)
3✔
236
        if err != nil {
3✔
237
                return fmt.Errorf("unable to copy to archive file: %w", err)
×
238
        }
×
239
        err = archiveFile.Sync()
3✔
240
        if err != nil {
3✔
241
                return fmt.Errorf("unable to sync archive file: %w", err)
×
242
        }
×
243

244
        return nil
3✔
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc