Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chanbackup: archive old channel backups #9232

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions chanbackup/backupfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package chanbackup

import (
"fmt"
"io"
"os"
"path/filepath"
"time"

"github.com/lightningnetwork/lnd/keychain"
)
Expand All @@ -17,6 +19,10 @@ const (
// file that we'll use to atomically update the primary back up file
// when new channel are detected.
DefaultTempBackupFileName = "temp-dont-use.backup"

// DefaultChanBackupDirName is the default name of the directory that
// we'll use to store old channel backups.
DefaultChanBackupArchiveDirName = "channelarchives"
)

var (
Expand Down Expand Up @@ -44,22 +50,33 @@ type MultiFile struct {

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

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

// deleteOldBackup indicates whether old backups should be deleted
// rather than archived.
deleteOldBackup bool
}

// NewMultiFile create a new multi-file instance at the target location on the
// file system.
func NewMultiFile(fileName string) *MultiFile {

func NewMultiFile(fileName string, deleteOldBackup bool) *MultiFile {
// We'll our temporary backup file in the very same directory as the
// main backup file.
backupFileDir := filepath.Dir(fileName)
tempFileName := filepath.Join(
backupFileDir, DefaultTempBackupFileName,
)
archiveDir := filepath.Join(
backupFileDir, DefaultChanBackupArchiveDirName,
)

return &MultiFile{
fileName: fileName,
tempFileName: tempFileName,
fileName: fileName,
tempFileName: tempFileName,
archiveDir: archiveDir,
deleteOldBackup: deleteOldBackup,
}
}

Expand Down Expand Up @@ -117,6 +134,21 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
return fmt.Errorf("unable to close file: %w", err)
}

if !b.deleteOldBackup {
// We check if the main backup file exists, if it does we
// archive it before replacing it with the new backup.
if _, err := os.Stat(b.fileName); err == nil {
log.Infof("Archiving old backup file at %v", b.fileName)

if err := createArchiveFile(
b.archiveDir, b.fileName,
); err != nil {
return fmt.Errorf("unable to archive old "+
"backup file: %w", err)
}
}
}

// Finally, we'll attempt to atomically rename the temporary file to
// the main back up file. If this succeeds, then we'll only have a
// single file on disk once this method exits.
Expand Down Expand Up @@ -147,3 +179,48 @@ func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
packedMulti := PackedMulti(multiBytes)
return packedMulti.Unpack(keyChain)
}

func createArchiveFile(archiveDir string, fileName string) error {
// Generate archive file path with timestamped name.
baseFileName := filepath.Base(fileName)
timestamp := time.Now().Format("2006-01-02-15-04-05")

archiveFileName := fmt.Sprintf("%s-%s", baseFileName, timestamp)
archiveFilePath := filepath.Join(archiveDir, archiveFileName)

oldBackupFile, err := os.Open(fileName)
if err != nil {
return fmt.Errorf("unable to open old backup file: %w", err)
}
defer func() {
if cerr := oldBackupFile.Close(); cerr != nil && err == nil {
err = fmt.Errorf("error closing old backup file: %w",
cerr)
}
}()

// Ensure the archive directory exists. If it doesn't we create it.
const archiveDirPermissions = 0o755
err = os.MkdirAll(archiveDir, archiveDirPermissions)
if err != nil {
return fmt.Errorf("unable to create archive directory: %w", err)
}

// Create new archive file.
archiveFile, err := os.Create(archiveFilePath)
if err != nil {
return fmt.Errorf("unable to create archive file: %w", err)
}
defer func() {
if cerr := archiveFile.Close(); cerr != nil && err == nil {
err = fmt.Errorf("error closing archive file: %w", cerr)
}
}()

// Copy contents of old backup to the newly created archive file.
if _, err := io.Copy(archiveFile, oldBackupFile); err != nil {
return fmt.Errorf("error copying to archive file: %w", err)
}

return nil
}
4 changes: 2 additions & 2 deletions chanbackup/backupfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestUpdateAndSwap(t *testing.T) {
},
}
for i, testCase := range testCases {
backupFile := NewMultiFile(testCase.fileName)
backupFile := NewMultiFile(testCase.fileName, false)

// To start with, we'll make a random byte slice that'll pose
// as our packed multi backup.
Expand Down Expand Up @@ -238,7 +238,7 @@ func TestExtractMulti(t *testing.T) {
}
for i, testCase := range testCases {
// First, we'll make our backup file with the specified name.
backupFile := NewMultiFile(testCase.fileName)
backupFile := NewMultiFile(testCase.fileName, false)

// With our file made, we'll now attempt to read out the
// multi-file.
Expand Down
15 changes: 12 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ const (
bitcoindBackendName = "bitcoind"
btcdBackendName = "btcd"
neutrinoBackendName = "neutrino"

// defaultDisableBackupArchive indicates whether to disable archiving of
// channel backups. When false (default), old backups are archived to a
// designated location. When true, old backups are simply deleted or
// replaced.
defaultDisableBackupArchive = false
)

var (
Expand Down Expand Up @@ -364,6 +370,8 @@ type Config struct {
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
BackupFilePath string `long:"backupfilepath" description:"The target location of the channel backup file"`

DisableBackupArchive bool `long:"disable-backup-archive" description:"If set to true, channel backups will be deleted or replaced rather than being archived to a separate location."`

FeeURL string `long:"feeurl" description:"DEPRECATED: Use 'fee.url' option. Optional URL for external fee estimation. If no URL is specified, the method for fee estimation will depend on the chosen backend and network. Must be set for neutrino on mainnet." hidden:"true"`

Bitcoin *lncfg.Chain `group:"Bitcoin" namespace:"bitcoin"`
Expand Down Expand Up @@ -738,9 +746,10 @@ func DefaultConfig() Config {
ServerPingTimeout: defaultGrpcServerPingTimeout,
ClientPingMinWait: defaultGrpcClientPingMinWait,
},
LogConfig: build.DefaultLogConfig(),
WtClient: lncfg.DefaultWtClientCfg(),
HTTPHeaderTimeout: DefaultHTTPHeaderTimeout,
LogConfig: build.DefaultLogConfig(),
WtClient: lncfg.DefaultWtClientCfg(),
HTTPHeaderTimeout: DefaultHTTPHeaderTimeout,
DisableBackupArchive: defaultDisableBackupArchive,
}
}

Expand Down
4 changes: 4 additions & 0 deletions sample-lnd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@
; Example:
; backupfilepath=~/.lnd/data/chain/bitcoin/mainnet/channel.backup

; When false (default), old channel backups are archived to a designated location.
; When true, old backups are simply deleted or replaced.
; disable-backup-archive=true

; The maximum capacity of the block cache in bytes. Increasing this will result
; in more blocks being kept in memory but will increase performance when the
; same block is required multiple times.
Expand Down
4 changes: 3 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
chanNotifier: s.channelNotifier,
addrs: dbs.ChanStateDB,
}
backupFile := chanbackup.NewMultiFile(cfg.BackupFilePath)
backupFile := chanbackup.NewMultiFile(
cfg.BackupFilePath, cfg.DisableBackupArchive,
)
startingChans, err := chanbackup.FetchStaticChanBackups(
s.chanStateDB, s.addrSource,
)
Expand Down