Skip to content

Commit

Permalink
Merge pull request #2313 from Roasbeef/static-chan-backups
Browse files Browse the repository at this point in the history
multi: implement new safe static channel backup and recovery scheme, RPCs, and cli commands
  • Loading branch information
Roasbeef committed Apr 1, 2019
2 parents 8087ea4 + f216027 commit c37ea68
Show file tree
Hide file tree
Showing 31 changed files with 4,733 additions and 1,355 deletions.
2 changes: 1 addition & 1 deletion chainregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ type chainControl struct {

signer input.Signer

keyRing keychain.KeyRing
keyRing keychain.SecretKeyRing

wc lnwallet.WalletController

Expand Down
8 changes: 5 additions & 3 deletions chanbackup/backupfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
return ErrNoBackupFileExists
}

log.Infof("Updating backup file at %v", b.fileName)

// If the old back up file still exists, then we'll delete it before
// proceeding.
if _, err := os.Stat(b.tempFileName); err == nil {
Expand All @@ -94,17 +96,17 @@ func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
var err error
b.tempFile, err = os.Create(b.tempFileName)
if err != nil {
return err
return fmt.Errorf("unable to create temp file: %v", err)
}

// With the file created, we'll write the new packed multi backup and
// remove the temporary file all together once this method exits.
_, err = b.tempFile.Write([]byte(newBackup))
if err != nil {
return err
return fmt.Errorf("unable to write backup to temp file: %v", err)
}
if err := b.tempFile.Sync(); err != nil {
return err
return fmt.Errorf("unable to sync temp file: %v", err)
}
defer os.Remove(b.tempFileName)

Expand Down
5 changes: 5 additions & 0 deletions chanbackup/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const (
// backup. The serialized format for this version is simply: version ||
// numBackups || SCBs...
DefaultMultiVersion = 0

// NilMultiSizePacked is the size of a "nil" packed Multi (45 bytes).
// This consists of the 24 byte chacha nonce, the 16 byte MAC, one byte
// for the version, and 4 bytes to signal zero entries.
NilMultiSizePacked = 24 + 16 + 1 + 4
)

// Multi is a form of static channel backup that is amenable to being
Expand Down
82 changes: 49 additions & 33 deletions chanbackup/pubsub.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chanbackup

import (
"bytes"
"fmt"
"net"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -47,9 +48,9 @@ type ChannelEvent struct {
// ChannelSubscription represents an intent to be notified of any updates to
// the primary channel state.
type ChannelSubscription struct {
// ChanUpdates is a read-only channel that will be sent upon once the
// primary channel state is updated.
ChanUpdates <-chan ChannelEvent
// ChanUpdates is a channel that will be sent upon once the primary
// channel state is updated.
ChanUpdates chan ChannelEvent

// Cancel is a closure that allows the caller to cancel their
// subscription and free up any resources allocated.
Expand Down Expand Up @@ -160,6 +161,36 @@ func (s *SubSwapper) Stop() error {
return nil
}

// updateBackupFile updates the backup file in place given the current state of
// the SubSwapper.
func (s *SubSwapper) updateBackupFile() error {
// With our updated channel state obtained, we'll create a new multi
// from our series of singles.
var newMulti Multi
for _, backup := range s.backupState {
newMulti.StaticBackups = append(
newMulti.StaticBackups, backup,
)
}

// Now that our multi has been assembled, we'll attempt to pack
// (encrypt+encode) the new channel state to our target reader.
var b bytes.Buffer
err := newMulti.PackToWriter(&b, s.keyRing)
if err != nil {
return fmt.Errorf("unable to pack multi backup: %v", err)
}

// Finally, we'll swap out the old backup for this new one in a single
// atomic step.
err = s.Swapper.UpdateAndSwap(PackedMulti(b.Bytes()))
if err != nil {
return fmt.Errorf("unable to update multi backup: %v", err)
}

return nil
}

// backupFileUpdater is the primary goroutine of the SubSwapper which is
// responsible for listening for changes to the channel, and updating the
// persistent multi backup state with a new packed multi of the latest channel
Expand All @@ -172,6 +203,12 @@ func (s *SubSwapper) backupUpdater() {

log.Debugf("SubSwapper's backupUpdater is active!")

// Before we enter our main loop, we'll update the on-disk state with
// the latest Single state, as nodes may have new advertised addresses.
if err := s.updateBackupFile(); err != nil {
log.Errorf("Unable to refresh backup file: %v", err)
}

for {
select {
// The channel state has been modified! We'll evaluate all
Expand All @@ -183,7 +220,7 @@ func (s *SubSwapper) backupUpdater() {
// For all new open channels, we'll create a new SCB
// given the required information.
for _, newChan := range chanUpdate.NewChans {
log.Debugf("Adding chanenl %v to backup state",
log.Debugf("Adding channel %v to backup state",
newChan.FundingOutpoint)

s.backupState[newChan.FundingOutpoint] = NewSingle(
Expand All @@ -204,41 +241,20 @@ func (s *SubSwapper) backupUpdater() {

newStateSize := len(s.backupState)

// With our updated channel state obtained, we'll
// create a new multi from our series of singles.
var newMulti Multi
for _, backup := range s.backupState {
newMulti.StaticBackups = append(
newMulti.StaticBackups, backup,
)
}

// Now that our multi has been assembled, we'll attempt
// to pack (encrypt+encode) the new channel state to
// our target reader.
var b bytes.Buffer
err := newMulti.PackToWriter(&b, s.keyRing)
if err != nil {
log.Errorf("unable to pack multi backup: %v",
err)
continue
}

log.Infof("Updating on-disk multi SCB backup: "+
"num_old_chans=%v, num_new_chans=%v",
oldStateSize, newStateSize)

// Finally, we'll swap out the old backup for this new
// one in a single atomic step.
err = s.Swapper.UpdateAndSwap(
PackedMulti(b.Bytes()),
)
if err != nil {
log.Errorf("unable to update multi "+
"backup: %v", err)
continue
// With out new state constructed, we'll, atomically
// update the on-disk backup state.
if err := s.updateBackupFile(); err != nil {
log.Errorf("unable to update backup file: %v",
err)
}

// TODO(roasbeef): refresh periodically on a time basis due to
// possible addr changes from node

// Exit at once if a quit signal is detected.
case <-s.quit:
return
Expand Down
22 changes: 16 additions & 6 deletions chanbackup/pubsub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,23 @@ func TestSubSwapperIdempotentStartStop(t *testing.T) {

keyRing := &mockKeyRing{}

var (
swapper mockSwapper
chanNotifier mockChannelNotifier
)
var chanNotifier mockChannelNotifier

subSwapper, err := NewSubSwapper(nil, &chanNotifier, keyRing, &swapper)
swapper := newMockSwapper()
subSwapper, err := NewSubSwapper(nil, &chanNotifier, keyRing, swapper)
if err != nil {
t.Fatalf("unable to init subSwapper: %v", err)
}

subSwapper.Start()
if err := subSwapper.Start(); err != nil {
t.Fatalf("unable to start swapper: %v", err)
}

// The swapper should write the initial channel state as soon as it's
// active.
backupSet := make(map[wire.OutPoint]Single)
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)

subSwapper.Start()

subSwapper.Stop()
Expand Down Expand Up @@ -188,6 +194,10 @@ func TestSubSwapperUpdater(t *testing.T) {
}
defer subSwapper.Stop()

// The swapper should write the initial channel state as soon as it's
// active.
assertExpectedBackupSwap(t, swapper, subSwapper, keyRing, backupSet)

// Now that the sub-swapper is active, we'll notify to add a brand new
// channel to the channel state.
newChannel, err := genRandomOpenChannelShell()
Expand Down
Loading

0 comments on commit c37ea68

Please sign in to comment.